All checks were successful
Build and Push Docker Image / build (push) Successful in 47s
two simplifications to how we pick and transcode the one-per-language audio track, motivated by seeing inconsistent DTS → FLAC vs DTS → EAC3 outputs in the wild: transcode target: - drop the FLAC path entirely. every incompatible source now targets EAC3 regardless of container or lossless/lossy status - FLAC for movie audio is bad value: ~2-3× the file size vs EAC3, no Atmos spatial metadata (TrueHD Atmos → FLAC silently loses Atmos), no AVR passthrough on Apple TV - one target = no more container-conditional surprises winner within a language group (betterAudio): - new priority: highest channels → Apple-compatible → default → index - old order put 'default' on top which forced a DTS-HD MA transcode even when an AC3 track at equal channels was right next to it. flipping means AC3 beats DTS-HD MA at the same channel count — pure copy instead of a lossless-then-re-encode round trip - channel count still dominates, so 7.1 TrueHD still beats 5.1 AC3 (and gets transcoded, which is the right call for real surround) tests: new case for DTS-HD MA default + AC3 non-default at 5.1 → AC3 wins, job_type=copy. new case for 7.1 TrueHD beats 5.1 AC3 default. every other existing test still holds.
61 lines
2.0 KiB
TypeScript
61 lines
2.0 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, etc.) transcodes to EAC3.
|
||
//
|
||
// Why EAC3 as the single target and not FLAC for lossless sources:
|
||
// - FLAC movie audio is ~2-3× the size of EAC3 at transparent quality
|
||
// - FLAC doesn't preserve Atmos spatial metadata (TrueHD Atmos → FLAC
|
||
// silently drops Atmos); EAC3 JOC can carry it
|
||
// - EAC3 passes through to AVRs over ARC/eARC; FLAC-to-AVR isn't a
|
||
// thing on Apple TV
|
||
// - One target per incompatible source = no container-conditional
|
||
// mapping surprises
|
||
|
||
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",
|
||
]);
|
||
|
||
export function isAppleCompatible(codec: string): boolean {
|
||
return APPLE_COMPATIBLE_AUDIO.has(codec.toLowerCase());
|
||
}
|
||
|
||
/**
|
||
* Maps (codec, profile, container) → target codec for transcoding.
|
||
* Returns null when the source is already Apple-compatible.
|
||
*
|
||
* profile and container are no longer consulted (kept in the signature
|
||
* for call-site stability and potential future per-source tuning).
|
||
*/
|
||
export function transcodeTarget(codec: string, _profile: string | null, _container: string | null): string | null {
|
||
if (isAppleCompatible(codec.toLowerCase())) return null;
|
||
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";
|
||
}
|