67 lines
2.1 KiB
TypeScript
67 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';
|
|
}
|