import { z } from "zod"; import { AssetStatus, LocationStatus } from "./assets.ts"; import { Id, JsonObject, NonNegInt, Timestamp } from "./common.ts"; import { MediaKind } from "./content.ts"; // LIBV-1 入库版本(GET /library/{id}/versions):一集(或一部电影)可挂多个本地文件,每个文件是一个版本。 // 规格来自文件名解析 + ffprobe(domain versionSpecOf);季 / 作品给汇总; // 配了版本目标时每集给出比对结果 matches(目前只比一个目标,数组形态留给多目标)。 export const VersionAudio = z.object({ codec: z.string().optional(), channels: z.string().optional(), language: z.string().optional(), }); export type VersionAudio = z.infer; export const AssetVersion = z.object({ assetId: Id, status: AssetStatus, resolution: z.string().nullable(), /** REMUX / BluRay(压制)/ BluRay-Disc(原盘)/ WEB-DL / WEBRip / HDTV … */ source: z.string().nullable(), isEncode: z.boolean(), videoCodec: z.string().nullable(), bitDepth: z.number().int().nullable(), hdr: z.array(z.string()), audio: z.array(VersionAudio), subtitleLanguages: z.array(z.string()), releaseGroup: z.string().nullable(), /** 剪辑版本(导演剪辑版等),无标记为 null */ edition: z.string().nullable(), sizeBytes: NonNegInt.nullable(), durationSec: NonNegInt.nullable(), fileName: z.string(), relativePath: z.string().nullable(), /** 库根本地路径 + 相对路径;库根未知时为 null */ absolutePath: z.string().nullable(), libraryRootId: Id.nullable(), locationStatus: LocationStatus.nullable(), /** 入库时间(资产登记时间) */ organizedAt: Timestamp, downloadTaskId: Id.nullable(), sourceReleaseId: Id.nullable(), /** 规格来源:name = 文件名,probe = ffprobe */ specFrom: z.array(z.enum(["name", "probe"])), /** delivery 已按当前口径写回;false = 本次现算,可跑回填脚本 */ specStored: z.boolean(), /** ffprobe 原始摘要(技术信息折叠里展示) */ probe: JsonObject.nullable(), /** 覆盖的内容单元(合集文件覆盖多集) */ unitIds: z.array(Id), }); export type AssetVersion = z.infer; export const VersionSpecGroup = z.object({ resolution: z.string().nullable(), source: z.string().nullable(), hdr: z.array(z.string()), count: NonNegInt, }); export const VersionSummary = z.object({ versionCount: NonNegInt, sizeBytes: NonNegInt, /** 按 分辨率 + 来源 + HDR 分组,多的在前 */ specs: z.array(VersionSpecGroup), }); export type VersionSummary = z.infer; export const UnitMatchState = z.enum(["satisfied", "missing", "upgradeable"]); export type UnitMatchState = z.infer; export const UnitMatch = z.object({ slotId: Id.nullable(), state: UnitMatchState, assetId: Id.nullable(), /** 比对说明原文(界面放进技术信息) */ reasons: z.array(z.string()), /** 差距原因码(`version_resolution:2160p` …),人话见 reasons.ts matchGapText */ codes: z.array(z.string()), }); export type UnitMatch = z.infer; export const MatchCounts = z.object({ satisfied: NonNegInt, missing: NonNegInt, upgradeable: NonNegInt }); export type MatchCounts = z.infer; export const EpisodeVersions = z.object({ unitId: Id, episodeNumber: z.number().int(), segment: z.string().nullable(), versions: z.array(AssetVersion), /** 与各版本目标的比对;没有版本目标时不出现 */ matches: z.array(UnitMatch).optional(), }); export type EpisodeVersions = z.infer; export const SeasonVersions = z.object({ seasonNumber: z.number().int(), unitId: Id.nullable(), /** 该季总集数(元数据);未知为 null */ episodeTotal: NonNegInt.nullable(), /** 至少有一个版本的集数 */ episodesWithVersion: NonNegInt, summary: VersionSummary, episodes: z.array(EpisodeVersions), /** 按第一个版本目标统计;没有版本目标时不出现 */ matchCounts: MatchCounts.optional(), }); export type SeasonVersions = z.infer; export const LibraryVersionsQuery = z.object({ /** 比对哪个版本目标;缺省取作品的主版本目标 */ slotId: z.uuid().optional(), }); export type LibraryVersionsQuery = z.infer; export const LibraryVersions = z.object({ entityId: Id, kind: MediaKind, /** 参与比对的版本目标(当前最多一个);空 = 未配置 */ targets: z.array(z.object({ slotId: Id, name: z.string() })), summary: VersionSummary, seasons: z.array(SeasonVersions), /** 电影:全部版本与比对 */ movie: z.object({ versions: z.array(AssetVersion), matches: z.array(UnitMatch).optional() }).nullable(), /** 剧集里没对上具体某一集的视频文件 */ unplaced: z.array(AssetVersion), }); export type LibraryVersions = z.infer;