feat(api): media detail + info + image-folder endpoints (#141/#161)
Add REST endpoints backing the SPA media detail pages and image browser:
- GET /api/movies/{id}, /api/shows/{id}, /api/seasons/{id}, /api/artists/{id}
wrapping the existing detail queries; 404 on None.
- GET /api/media-items/{id}/info wrapping GetMediaItemInfo; UnableToLocateMediaItem
-> 404, other errors -> 422.
- GET /api/images/folders?parentId= and PUT /api/images/folders/{id}/duration
(validates null-or-positive -> 400; existence guard via new ImageFolderExists
query -> 404).
- Extend GetLibraryBrowseItems parentId drill-in to Episode (episodes of a season,
episode-number order) and MusicVideo (an artist's music videos, album/track order),
alongside the existing TelevisionSeason branch.
Response DTOs live in ErsatzTV.Core/Api/* and never expose Application VMs. Artwork
values are rooted for the SPA via a shared ErsatzTV.Core/Api/ApiArtwork helper
(mirrors the #180/#181 browse-handler logic; handles jellyfin/emby proxy prefixes,
http passthrough, empty). Regenerated OpenAPI v1.json + web v1.d.ts. New controllers
registered in ApiControllerSecurityTests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Vendored
+125
@@ -3,6 +3,12 @@
|
||||
|
||||
export interface components {
|
||||
schemas: {
|
||||
"ActorResponseModel": {
|
||||
"id": number;
|
||||
"name": string;
|
||||
"role": null | string;
|
||||
"thumb": string;
|
||||
};
|
||||
"AddItemsToCollectionRequest": {
|
||||
"movieIds": null | Array<number>;
|
||||
"showIds": null | Array<number>;
|
||||
@@ -17,6 +23,18 @@ export interface components {
|
||||
};
|
||||
"AddTraktListRequest": {
|
||||
"url": null | string;
|
||||
};
|
||||
"ArtistDetailResponseModel": {
|
||||
"id": number;
|
||||
"name": string;
|
||||
"disambiguation": null | string;
|
||||
"biography": null | string;
|
||||
"thumbnail": string;
|
||||
"fanArt": string;
|
||||
"genres": Array<string>;
|
||||
"styles": Array<string>;
|
||||
"moods": Array<string>;
|
||||
"languages": Array<string>;
|
||||
};
|
||||
"ArtworkContentTypeModel": {
|
||||
"path": null | string;
|
||||
@@ -687,6 +705,14 @@ export interface components {
|
||||
"lastAccess": string;
|
||||
};
|
||||
"IFormFile": string;
|
||||
"ImageFolderResponseModel": {
|
||||
"libraryFolderId": number;
|
||||
"name": string;
|
||||
"fullPath": string;
|
||||
"subfolderCount": number;
|
||||
"imageCount": number;
|
||||
"durationSeconds": null | number;
|
||||
};
|
||||
"LibraryBrowseItemResponseModel": {
|
||||
"id": number;
|
||||
"mediaType": components["schemas"]["LibraryBrowseMediaType"];
|
||||
@@ -739,8 +765,54 @@ export interface components {
|
||||
"poster"?: null | string;
|
||||
"state": components["schemas"]["MediaItemState"];
|
||||
"hasMediaInfo"?: boolean;
|
||||
};
|
||||
"MediaItemInfoChapterResponseModel": {
|
||||
"title": null | string;
|
||||
"startTime": string;
|
||||
"endTime": string;
|
||||
};
|
||||
"MediaItemInfoResponseModel": {
|
||||
"id": number;
|
||||
"title": string;
|
||||
"kind": string;
|
||||
"libraryKind": string;
|
||||
"serverName": null | string;
|
||||
"libraryName": string;
|
||||
"state": components["schemas"]["MediaItemState"];
|
||||
"duration": string;
|
||||
"sampleAspectRatio": null | string;
|
||||
"displayAspectRatio": null | string;
|
||||
"rFrameRate": null | string;
|
||||
"videoScanKind": components["schemas"]["VideoScanKind"];
|
||||
"interlacedRatio": null | number;
|
||||
"width": number;
|
||||
"height": number;
|
||||
"streams": Array<components["schemas"]["MediaItemInfoStreamResponseModel"]>;
|
||||
"chapters": Array<components["schemas"]["MediaItemInfoChapterResponseModel"]>;
|
||||
};
|
||||
"MediaItemInfoStreamResponseModel": {
|
||||
"index": null | number;
|
||||
"kind": components["schemas"]["MediaStreamKind"];
|
||||
"title": null | string;
|
||||
"codec": null | string;
|
||||
"profile": null | string;
|
||||
"language": null | string;
|
||||
"channels": null | number;
|
||||
"default": null | boolean;
|
||||
"forced": null | boolean;
|
||||
"attachedPic": null | boolean;
|
||||
"pixelFormat": null | string;
|
||||
"colorRange": null | string;
|
||||
"colorSpace": null | string;
|
||||
"colorTransfer": null | string;
|
||||
"colorPrimaries": null | string;
|
||||
"bitsPerRawSample": null | number;
|
||||
"mimeType": null | string;
|
||||
"fileName": null | string;
|
||||
"isExtracted": null | boolean;
|
||||
};
|
||||
"MediaItemState": "Normal" | "FileNotFound" | "Unavailable" | "RemoteOnly";
|
||||
"MediaSourceKind": "Local" | "Plex" | "Jellyfin" | "Emby";
|
||||
"MediaSourceLibraryResponseModel": {
|
||||
"id": number;
|
||||
"name": string;
|
||||
@@ -754,6 +826,26 @@ export interface components {
|
||||
"name": string;
|
||||
"connectionAddress": null | string;
|
||||
"libraries": Array<components["schemas"]["MediaSourceLibraryResponseModel"]>;
|
||||
};
|
||||
"MediaStreamKind": "Video" | "Audio" | "Subtitle" | "Attachment" | "ExternalSubtitle";
|
||||
"MovieDetailResponseModel": {
|
||||
"id": number;
|
||||
"title": string;
|
||||
"year": null | string;
|
||||
"plot": null | string;
|
||||
"genres": Array<string>;
|
||||
"tags": Array<string>;
|
||||
"studios": Array<string>;
|
||||
"contentRatings": Array<string>;
|
||||
"languages": Array<string>;
|
||||
"actors": Array<components["schemas"]["ActorResponseModel"]>;
|
||||
"directors": Array<string>;
|
||||
"writers": Array<string>;
|
||||
"path": null | string;
|
||||
"localPath": null | string;
|
||||
"state": components["schemas"]["MediaItemState"];
|
||||
"poster": string;
|
||||
"fanArt": string;
|
||||
};
|
||||
"MultiCollectionItemViewModel": {
|
||||
"multiCollectionId": number;
|
||||
@@ -1121,6 +1213,32 @@ export interface components {
|
||||
"otherVideos": components["schemas"]["SearchResultGroupResponseModel"];
|
||||
"images": components["schemas"]["SearchResultGroupResponseModel"];
|
||||
"remoteStreams": components["schemas"]["SearchResultGroupResponseModel"];
|
||||
};
|
||||
"SeasonDetailResponseModel": {
|
||||
"id": number;
|
||||
"showId": number;
|
||||
"title": string;
|
||||
"year": null | string;
|
||||
"name": string;
|
||||
"poster": string;
|
||||
"fanArt": string;
|
||||
};
|
||||
"ShowDetailResponseModel": {
|
||||
"id": number;
|
||||
"libraryId": number;
|
||||
"mediaSourceKind": components["schemas"]["MediaSourceKind"];
|
||||
"title": string;
|
||||
"year": null | string;
|
||||
"plot": null | string;
|
||||
"poster": string;
|
||||
"fanArt": string;
|
||||
"genres": Array<string>;
|
||||
"tags": Array<string>;
|
||||
"studios": Array<string>;
|
||||
"networks": Array<string>;
|
||||
"contentRatings": Array<string>;
|
||||
"languages": Array<string>;
|
||||
"actors": Array<components["schemas"]["ActorResponseModel"]>;
|
||||
};
|
||||
"SmartCollectionResponseModel": {
|
||||
"id": number;
|
||||
@@ -1318,6 +1436,12 @@ export interface components {
|
||||
};
|
||||
"UpdateHdhrSettingsRequest": {
|
||||
"tunerCount": number;
|
||||
};
|
||||
"UpdateImageFolderDurationRequest": {
|
||||
"durationSeconds": null | number;
|
||||
};
|
||||
"UpdateImageFolderDurationResponseModel": {
|
||||
"durationSeconds": null | number;
|
||||
};
|
||||
"UpdateLoggingSettingsRequest": {
|
||||
"defaultMinimumLogLevel": components["schemas"]["LogEventLevel"];
|
||||
@@ -1383,6 +1507,7 @@ export interface components {
|
||||
"blockBehavior": components["schemas"]["XmltvBlockBehavior"];
|
||||
};
|
||||
"VaapiDriver": "Default" | "iHD" | "i965" | "RadeonSI" | "Nouveau";
|
||||
"VideoScanKind": "Unknown" | "Progressive" | "Interlaced";
|
||||
"WatermarkFullResponseModel": {
|
||||
"id": number;
|
||||
"name": string;
|
||||
|
||||
Reference in New Issue
Block a user