0.13.0 DVD Series
This commit is contained in:
+152
-9
@@ -838,6 +838,18 @@ const SETTINGS_SCHEMA_METADATA_UPDATES = [
|
||||
required: 0,
|
||||
description: 'Preset Name für -Z (DVD). Leer = kein Preset, nur CLI-Parameter werden verwendet.',
|
||||
validation_json: '{}'
|
||||
},
|
||||
{
|
||||
key: 'output_template_dvd_series_episode',
|
||||
required: 1,
|
||||
description: 'Template für einzelne DVD-Serienepisoden ohne Dateiendung. Platzhalter: {seriesTitle}, {seasonNr}, {episodeNr}, {episodeTitle}, {discNr}, {year}, {language}. Optional mit führender Null: {0:seasonNr}, {0:episodeNr}. Alias: {seasonNo}/{episodeNo} entspricht {seasonNr}/{episodeNr}.',
|
||||
validation_json: '{"minLength":1}'
|
||||
},
|
||||
{
|
||||
key: 'output_template_dvd_series_multi_episode',
|
||||
required: 1,
|
||||
description: 'Template für zusammengefasste DVD-Serienepisoden ohne Dateiendung. Platzhalter: {seriesTitle}, {seasonNr}, {episodeNoStart}, {episodeNoEnd}, {episodeRange}, {discNr}, {year}, {language}. Optional mit führender Null: {0:seasonNr}, {0:episodeNoStart}, {0:episodeNoEnd}. Alias: {seasonNo} entspricht {seasonNr}.',
|
||||
validation_json: '{"minLength":1}'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -917,6 +929,16 @@ async function migrateSettingsSchemaMetadata(db) {
|
||||
WHERE key = 'raw_dir_cd_owner' AND label != 'Eigentümer CD RAW-Ordner'`
|
||||
);
|
||||
|
||||
// depends_on-Spalte zu settings_schema hinzufügen, bevor neue abhängige
|
||||
// Settings per INSERT OR IGNORE angelegt werden.
|
||||
{
|
||||
const cols = await db.all(`PRAGMA table_info(settings_schema)`);
|
||||
if (!cols.some((c) => c.name === 'depends_on')) {
|
||||
await db.run(`ALTER TABLE settings_schema ADD COLUMN depends_on TEXT DEFAULT NULL`);
|
||||
logger.info('migrate:settings-schema-add-depends_on');
|
||||
}
|
||||
}
|
||||
|
||||
// Add movie_dir_cd if not already present
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
@@ -986,6 +1008,87 @@ async function migrateSettingsSchemaMetadata(db) {
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('movie_dir_audiobook_owner', NULL)`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('raw_dir_dvd_series', 'Pfade', 'RAW-Ordner (DVD Serie)', 'path', 0, 'RAW-Zielpfad für DVD-Serien. Leer = gleicher Pfad wie RAW-Ordner (DVD).', NULL, '[]', '{}', 1021)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('raw_dir_dvd_series', NULL)`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('raw_dir_dvd_series_owner', 'Pfade', 'Eigentümer RAW-Ordner (DVD Serie)', 'string', 0, 'Eigentümer der Dateien im Format user:gruppe für DVD-Serien-RAW. Leer = Eigentümer Raw-Ordner (DVD).', NULL, '[]', '{}', 1022)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('raw_dir_dvd_series_owner', NULL)`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('series_dir_dvd', 'Pfade', 'Serien-Ordner (DVD)', 'path', 0, 'Zielordner für DVD-Serienfolgen. Leer = Standardpfad (data/output/series).', NULL, '[]', '{}', 113)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('series_dir_dvd', NULL)`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('series_dir_dvd_owner', 'Pfade', 'Eigentümer Serien-Ordner (DVD)', 'string', 0, 'Eigentümer der DVD-Serienausgaben im Format user:gruppe. Leer = Standardbenutzer des Dienstes.', NULL, '[]', '{}', 1135)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('series_dir_dvd_owner', NULL)`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('dvd_series_plugin_enabled', 'Features', 'DVD Serien-Plugin aktiviert', 'boolean', 1, 'Aktiviert die additive Serienerkennung für DVDs. Wenn deaktiviert, bleibt der bestehende DVD-Film-Flow unverändert.', 'false', '[]', '{}', 145)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('dvd_series_plugin_enabled', 'false')`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on)
|
||||
VALUES ('dvd_series_prescan_enabled', 'Features', 'DVD HandBrake-Prescan aktiviert', 'boolean', 1, 'Führt nach der DVD-Erkennung einen zusätzlichen HandBrake-Prescan für die Serienheuristik aus.', 'true', '[]', '{}', 146, 'dvd_series_plugin_enabled')`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('dvd_series_prescan_enabled', 'true')`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('output_template_dvd_series_episode', 'Pfade', 'Output Template (DVD Serie, Episode)', 'string', 1, 'Template für einzelne DVD-Serienepisoden ohne Dateiendung. Platzhalter: {seriesTitle}, {seasonNr}, {episodeNr}, {episodeTitle}, {discNr}, {year}, {language}. Optional mit führender Null: {0:seasonNr}, {0:episodeNr}. Alias: {seasonNo}/{episodeNo} entspricht {seasonNr}/{episodeNr}.', '{seriesTitle}/Staffel {seasonNr}/S{seasonNr}E{episodeNr} - {episodeTitle}', '[]', '{"minLength":1}', 536)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('output_template_dvd_series_episode', '{seriesTitle}/Staffel {seasonNr}/S{seasonNr}E{episodeNr} - {episodeTitle}')`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('output_template_dvd_series_multi_episode', 'Pfade', 'Output Template (DVD Serie, Multi-Episode)', 'string', 1, 'Template für zusammengefasste DVD-Serienepisoden ohne Dateiendung. Platzhalter: {seriesTitle}, {seasonNr}, {episodeNoStart}, {episodeNoEnd}, {episodeRange}, {discNr}, {year}, {language}. Optional mit führender Null: {0:seasonNr}, {0:episodeNoStart}, {0:episodeNoEnd}. Alias: {seasonNo} entspricht {seasonNr}.', '{seriesTitle}/Staffel {seasonNr}/S{seasonNr}E{episodeRange}', '[]', '{"minLength":1}', 537)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('output_template_dvd_series_multi_episode', '{seriesTitle}/Staffel {seasonNr}/S{seasonNr}E{episodeRange}')`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('tmdb_api_read_access_token', 'Metadaten', 'TMDb Read Access Token', 'string', 0, 'Read Access Token für Serien-Metadaten über TheMovieDB (TMDb).', NULL, '[]', '{}', 401)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('tmdb_api_read_access_token', NULL)`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('dvd_series_language', 'Metadaten', 'DVD Serien-Sprache', 'string', 1, 'Bevorzugte Sprache für Serien-Metadaten im TMDb-Format, z.B. de-DE oder en-US.', 'de-DE', '[]', '{"minLength":2}', 403)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('dvd_series_language', 'de-DE')`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('dvd_series_fallback_languages', 'Metadaten', 'DVD Serien-Fallback-Sprachen', 'string', 1, 'Kommagetrennte TMDb-Sprachen für Fallback-Titel, z.B. de-DE,en-US,fr-FR.', 'de-DE,en-US', '[]', '{"minLength":2}', 404)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('dvd_series_fallback_languages', 'de-DE,en-US')`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('dvd_series_order_preference', 'Metadaten', 'DVD Serien-Ordnung', 'select', 1, 'Bevorzugte Episodenordnung für DVD-Serien. Episode Groups nutzen TMDb Alternate Orders, falls vorhanden.', 'episode_group', '[{"label":"Episode Group","value":"episode_group"},{"label":"Aired / Season","value":"aired"}]', '{}', 405)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('dvd_series_order_preference', 'episode_group')`);
|
||||
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
VALUES ('dvd_series_reuse_disc_profiles', 'Metadaten', 'DVD Disc-Profile wiederverwenden', 'boolean', 1, 'Verwendet zuvor bestätigte Disc-Profile erneut, um Episoden automatischer zuzuordnen.', 'true', '[]', '{}', 406)`
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('dvd_series_reuse_disc_profiles', 'true')`);
|
||||
|
||||
await db.run(`DELETE FROM settings_values WHERE key IN ('tvdb_api_key', 'tvdb_subscriber_pin')`);
|
||||
await db.run(`DELETE FROM settings_schema WHERE key IN ('tvdb_api_key', 'tvdb_subscriber_pin')`);
|
||||
|
||||
// Converter-Pfade und Eigentümer
|
||||
await db.run(
|
||||
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||
@@ -1077,15 +1180,6 @@ async function migrateSettingsSchemaMetadata(db) {
|
||||
);
|
||||
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('coverart_recovery_interval_hours', '6')`);
|
||||
|
||||
// depends_on-Spalte zu settings_schema hinzufügen (falls noch nicht vorhanden)
|
||||
{
|
||||
const cols = await db.all(`PRAGMA table_info(settings_schema)`);
|
||||
if (!cols.some((c) => c.name === 'depends_on')) {
|
||||
await db.run(`ALTER TABLE settings_schema ADD COLUMN depends_on TEXT DEFAULT NULL`);
|
||||
logger.info('migrate:settings-schema-add-depends_on');
|
||||
}
|
||||
}
|
||||
|
||||
await db.run(`
|
||||
CREATE TABLE IF NOT EXISTS user_prefs (
|
||||
key TEXT PRIMARY KEY,
|
||||
@@ -1093,6 +1187,55 @@ async function migrateSettingsSchemaMetadata(db) {
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
await db.run(`
|
||||
CREATE TABLE IF NOT EXISTS dvd_series_disc_profiles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
provider TEXT NOT NULL DEFAULT 'tmdb',
|
||||
provider_series_id TEXT,
|
||||
provider_series_name TEXT,
|
||||
season_number INTEGER,
|
||||
season_type TEXT NOT NULL DEFAULT 'dvd',
|
||||
disc_label TEXT,
|
||||
disc_serial TEXT,
|
||||
disc_number INTEGER,
|
||||
language TEXT,
|
||||
disc_signature TEXT NOT NULL,
|
||||
fingerprint_json TEXT,
|
||||
episode_map_json TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
await db.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_dvd_series_disc_profiles_signature ON dvd_series_disc_profiles(disc_signature)`);
|
||||
await db.run(`CREATE INDEX IF NOT EXISTS idx_dvd_series_disc_profiles_series ON dvd_series_disc_profiles(provider, provider_series_id, season_number)`);
|
||||
await db.run(`UPDATE dvd_series_disc_profiles SET provider = 'tmdb' WHERE provider = 'tvdb'`);
|
||||
|
||||
await db.run(`
|
||||
CREATE TABLE IF NOT EXISTS dvd_series_title_mappings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
parent_job_id INTEGER NOT NULL,
|
||||
disc_profile_id INTEGER,
|
||||
title_index INTEGER NOT NULL,
|
||||
title_kind TEXT NOT NULL,
|
||||
confidence REAL NOT NULL DEFAULT 0,
|
||||
selected INTEGER NOT NULL DEFAULT 1,
|
||||
provider TEXT NOT NULL DEFAULT 'tmdb',
|
||||
provider_series_id TEXT,
|
||||
season_number INTEGER,
|
||||
episode_start INTEGER,
|
||||
episode_end INTEGER,
|
||||
episode_ids_json TEXT,
|
||||
mapping_json TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (parent_job_id) REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (disc_profile_id) REFERENCES dvd_series_disc_profiles(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
await db.run(`CREATE INDEX IF NOT EXISTS idx_dvd_series_title_mappings_parent_job ON dvd_series_title_mappings(parent_job_id, title_index)`);
|
||||
await db.run(`CREATE INDEX IF NOT EXISTS idx_dvd_series_title_mappings_series ON dvd_series_title_mappings(provider, provider_series_id, season_number)`);
|
||||
await db.run(`UPDATE dvd_series_title_mappings SET provider = 'tmdb' WHERE provider = 'tvdb'`);
|
||||
}
|
||||
|
||||
async function backfillJobOutputFolders(db) {
|
||||
|
||||
Reference in New Issue
Block a user