- execute: actually call isInScheduleWindow/waitForWindow/sleepBetweenJobs in runSequential (they were dead code); emit queue_status SSE events (running/paused/sleeping/idle) so the pipeline's existing QueueStatus listener lights up - review: POST /:id/retry resets an errored plan to approved, wipes old done/error jobs, rebuilds command from current decisions, queues fresh job - scan: dev-mode DELETE now also wipes jobs + subtitle_files (previously orphaned after every dev reset) - biome: migrate config to 2.4 schema, autoformat 68 files (strings + indentation), relax opinionated a11y/hooks-deps/index-key rules that don't fit this codebase - routeTree.gen.ts regenerated after /nodes removal
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
// Codec sets and transcode target mapping for Apple device compatibility.
|
|
// Apple natively decodes: AAC, AC3, EAC3, ALAC, FLAC, MP3, PCM, Opus
|
|
// Everything else (DTS family, TrueHD family) needs transcoding.
|
|
|
|
const APPLE_COMPATIBLE_AUDIO = new Set([
|
|
"aac",
|
|
"ac3",
|
|
"eac3",
|
|
"alac",
|
|
"flac",
|
|
"mp3",
|
|
"pcm_s16le",
|
|
"pcm_s24le",
|
|
"pcm_s32le",
|
|
"pcm_f32le",
|
|
"pcm_s16be",
|
|
"pcm_s24be",
|
|
"pcm_s32be",
|
|
"pcm_f64le",
|
|
"opus",
|
|
]);
|
|
|
|
// Codec strings Jellyfin may report for DTS variants
|
|
const DTS_CODECS = new Set(["dts", "dca"]);
|
|
|
|
const TRUEHD_CODECS = new Set(["truehd"]);
|
|
|
|
export function isAppleCompatible(codec: string): boolean {
|
|
return APPLE_COMPATIBLE_AUDIO.has(codec.toLowerCase());
|
|
}
|
|
|
|
/** Maps (codec, profile, container) → target codec for transcoding. */
|
|
export function transcodeTarget(codec: string, profile: string | null, container: string | null): string | null {
|
|
const c = codec.toLowerCase();
|
|
const isMkv = !container || container.toLowerCase() === "mkv" || container.toLowerCase() === "matroska";
|
|
|
|
if (isAppleCompatible(c)) return null; // no transcode needed
|
|
|
|
// DTS-HD MA and DTS:X are lossless → FLAC in MKV, EAC3 in MP4
|
|
if (DTS_CODECS.has(c)) {
|
|
const p = (profile ?? "").toLowerCase();
|
|
const isLossless = p.includes("ma") || p.includes("hd ma") || p.includes("x");
|
|
if (isLossless) return isMkv ? "flac" : "eac3";
|
|
// Lossy DTS variants → EAC3
|
|
return "eac3";
|
|
}
|
|
|
|
// TrueHD (including Atmos) → FLAC in MKV, EAC3 in MP4
|
|
if (TRUEHD_CODECS.has(c)) {
|
|
return isMkv ? "flac" : "eac3";
|
|
}
|
|
|
|
// Any other incompatible codec → EAC3 as safe fallback
|
|
return "eac3";
|
|
}
|
|
|
|
/** Determine overall Apple compatibility for a set of kept audio streams. */
|
|
export function computeAppleCompat(
|
|
keptAudioCodecs: string[],
|
|
container: string | null,
|
|
): "direct_play" | "remux" | "audio_transcode" {
|
|
const hasIncompatible = keptAudioCodecs.some((c) => !isAppleCompatible(c));
|
|
if (hasIncompatible) return "audio_transcode";
|
|
|
|
const isMkv = !container || container.toLowerCase() === "mkv" || container.toLowerCase() === "matroska";
|
|
if (isMkv) return "remux";
|
|
|
|
return "direct_play";
|
|
}
|