write canonical iso3 language metadata, tighten is_noop, store full jellyfin data
Build and Push Docker Image / build (push) Failing after 16s
Build and Push Docker Image / build (push) Failing after 16s
ffmpeg now writes -metadata:s:a:i language=<iso3> on every kept audio track so
files end up with canonical 3-letter tags (en → eng, ger → deu, null → und).
analyzer passes stream.profile (not title) to transcodeTarget so lossless
dts-hd ma in mkv correctly targets flac. is_noop also checks og-is-default and
canonical-language so pipeline-would-change-it cases stop showing as done.
normalizeLanguage gains 2→3 mapping, and mapStream no longer normalizes at
ingest so the raw jellyfin tag survives for the canonical check.
per-item scan work runs in a single db.transaction for large sqlite speedups,
extracted into server/services/rescan.ts so execute.ts can reuse it.
on successful job, execute calls jellyfin /Items/{id}/Refresh, waits for
DateLastRefreshed to change, refetches the item, and upserts it through the
same pipeline; plan flips to done iff the fresh streams satisfy is_noop.
schema wiped + rewritten to carry jellyfin_raw, external_raw, profile,
bit_depth, date_last_refreshed, runtime_ticks, original_title, last_executed_at
— so future scans aren't required to stay correct. user must drop data/*.db.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ function stream(o: Partial<MediaStream> & Pick<MediaStream, "id" | "type" | "str
|
||||
return {
|
||||
item_id: 1,
|
||||
codec: null,
|
||||
profile: null,
|
||||
language: null,
|
||||
language_display: null,
|
||||
title: null,
|
||||
@@ -16,6 +17,7 @@ function stream(o: Partial<MediaStream> & Pick<MediaStream, "id" | "type" | "str
|
||||
channel_layout: null,
|
||||
bit_rate: null,
|
||||
sample_rate: null,
|
||||
bit_depth: null,
|
||||
...o,
|
||||
};
|
||||
}
|
||||
@@ -36,6 +38,7 @@ const ITEM: MediaItem = {
|
||||
jellyfin_id: "x",
|
||||
type: "Movie",
|
||||
name: "Test",
|
||||
original_title: null,
|
||||
series_name: null,
|
||||
series_jellyfin_id: null,
|
||||
season_number: null,
|
||||
@@ -44,15 +47,20 @@ const ITEM: MediaItem = {
|
||||
file_path: "/movies/Test.mkv",
|
||||
file_size: null,
|
||||
container: "mkv",
|
||||
runtime_ticks: null,
|
||||
date_last_refreshed: null,
|
||||
original_language: "eng",
|
||||
orig_lang_source: "jellyfin",
|
||||
needs_review: 0,
|
||||
imdb_id: null,
|
||||
tmdb_id: null,
|
||||
tvdb_id: null,
|
||||
jellyfin_raw: null,
|
||||
external_raw: null,
|
||||
scan_status: "scanned",
|
||||
scan_error: null,
|
||||
last_scanned_at: null,
|
||||
last_executed_at: null,
|
||||
created_at: "",
|
||||
};
|
||||
|
||||
@@ -155,6 +163,25 @@ describe("buildCommand", () => {
|
||||
expect(cmd).toContain("'/movies/Test.de.srt'");
|
||||
});
|
||||
|
||||
test("writes canonical iso3 language metadata on every kept audio stream", () => {
|
||||
const streams = [
|
||||
stream({ id: 1, type: "Video", stream_index: 0 }),
|
||||
stream({ id: 2, type: "Audio", stream_index: 1, language: "en" }), // 2-letter → eng
|
||||
stream({ id: 3, type: "Audio", stream_index: 2, language: "ger" }), // alias → deu
|
||||
stream({ id: 4, type: "Audio", stream_index: 3, language: null }), // unknown → und
|
||||
];
|
||||
const decisions = [
|
||||
decision({ stream_id: 1, action: "keep", target_index: 0 }),
|
||||
decision({ stream_id: 2, action: "keep", target_index: 0 }),
|
||||
decision({ stream_id: 3, action: "keep", target_index: 1 }),
|
||||
decision({ stream_id: 4, action: "keep", target_index: 2 }),
|
||||
];
|
||||
const cmd = buildCommand(ITEM, streams, decisions);
|
||||
expect(cmd).toContain("-metadata:s:a:0 language=eng");
|
||||
expect(cmd).toContain("-metadata:s:a:1 language=deu");
|
||||
expect(cmd).toContain("-metadata:s:a:2 language=und");
|
||||
});
|
||||
|
||||
test("sets first kept audio as default, clears others", () => {
|
||||
const streams = [
|
||||
stream({ id: 1, type: "Video", stream_index: 0 }),
|
||||
|
||||
Reference in New Issue
Block a user