chore: add 05_Finalize_Move.js from live FileFlows export
This commit is contained in:
parent
b29cc860bb
commit
2eb34a4527
1 changed files with 164 additions and 0 deletions
164
fileflows/scripts/05_Finalize_Move.js
Normal file
164
fileflows/scripts/05_Finalize_Move.js
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
function Script(SourceRoot, DestRoot)
|
||||||
|
{
|
||||||
|
SourceRoot = SourceRoot || '/tdarr/complete/';
|
||||||
|
DestRoot = DestRoot || '/tdarr/converted/';
|
||||||
|
|
||||||
|
let origPath = Variables['file.Orig.FullName'];
|
||||||
|
let workingFile = Variables['file.FullName'];
|
||||||
|
|
||||||
|
if (!origPath || !workingFile) {
|
||||||
|
Logger.ELog('Pfad-Variablen fehlen (orig=' + origPath + ' work=' + workingFile + ')');
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (origPath.indexOf(SourceRoot) !== 0) {
|
||||||
|
Logger.ELog('Original "' + origPath + '" liegt nicht unter ' + SourceRoot + ' - abort');
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let relPath = origPath.substring(SourceRoot.length);
|
||||||
|
let destAbs = DestRoot + relPath;
|
||||||
|
let destDir = destAbs.substring(0, destAbs.lastIndexOf('/'));
|
||||||
|
|
||||||
|
Logger.ILog('orig: ' + origPath);
|
||||||
|
Logger.ILog('working: ' + workingFile);
|
||||||
|
Logger.ILog('dest: ' + destAbs);
|
||||||
|
|
||||||
|
Flow.Execute({ command: '/bin/mkdir', argumentList: ['-p', destDir] });
|
||||||
|
|
||||||
|
// Wenn Ziel schon existiert: explizit loggen und ueberschreiben (typisch bei Re-Encode
|
||||||
|
// gleichen Films aus altem Run). Kein -n, sonst geht der frische Encode beim Cleanup verloren.
|
||||||
|
let testExist = Flow.Execute({ command: '/usr/bin/test', argumentList: ['-f', destAbs] });
|
||||||
|
if (testExist && testExist.exitCode === 0) {
|
||||||
|
let oldSize = Flow.Execute({ command: '/usr/bin/stat', argumentList: ['-c', '%s', destAbs] });
|
||||||
|
let oldSizeStr = (oldSize && oldSize.standardOutput) ? oldSize.standardOutput.trim() : '?';
|
||||||
|
Logger.WLog('Ziel existiert bereits (size=' + oldSizeStr + ' bytes) - wird ueberschrieben: ' + destAbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mv = Flow.Execute({ command: '/bin/mv', argumentList: ['-f', workingFile, destAbs] });
|
||||||
|
if (!mv || mv.exitCode !== 0) {
|
||||||
|
Logger.ELog('mv failed (exit=' + (mv ? mv.exitCode : '?') + ')');
|
||||||
|
if (mv && mv.standardError) Logger.ELog(mv.standardError);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.SetWorkingFile(destAbs);
|
||||||
|
Logger.ILog('Moved -> ' + destAbs);
|
||||||
|
|
||||||
|
if (origPath !== destAbs) {
|
||||||
|
Flow.Execute({ command: '/bin/rm', argumentList: ['-f', origPath] });
|
||||||
|
Logger.ILog('Deleted original: ' + origPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
// UNTERTITEL-HANDLING (greift fuer ALLE Zweige: AV1, HEVC, reiner Copy)
|
||||||
|
// Ziel: Emby-Auto-Burn-Transcode wegen Untertiteln verhindern.
|
||||||
|
// - Text-Subs (srt/ass): als externe .srt-Sidecar rausziehen UND aus MKV entfernen.
|
||||||
|
// Radarr (importExtraFiles=.srt) zieht sie beim Import mit und benennt synchron um.
|
||||||
|
// - Bild-Subs (PGS/VobSub): im MKV behalten, aber default/forced-Flags loeschen
|
||||||
|
// -> Emby brennt nie automatisch ein (kein Transcode). Manuell waehlbar bleiben sie.
|
||||||
|
// - Forced-Textspur -> eigene .forced.srt.
|
||||||
|
// Remux (Container-only, -c copy) laeuft NUR wenn noetig (Text-Sub vorhanden oder
|
||||||
|
// ein Bild-Sub traegt default/forced). Sonst wird das Video nicht angefasst.
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════
|
||||||
|
try {
|
||||||
|
handleSubtitles(destAbs);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.WLog('Untertitel-Handling uebersprungen (Fehler, Film bleibt unveraendert): ' + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// ── Helfer ───────────────────────────────────────────────────────────────
|
||||||
|
function handleSubtitles(file) {
|
||||||
|
var ffprobe = Flow.GetToolPath('ffprobe') || Variables['ffprobe'] || 'ffprobe';
|
||||||
|
var ffmpeg = Flow.GetToolPath('ffmpeg') || Variables['ffmpeg'] || 'ffmpeg';
|
||||||
|
|
||||||
|
var res = Flow.Execute({
|
||||||
|
command: ffprobe,
|
||||||
|
argumentList: ['-v', 'quiet', '-print_format', 'json', '-show_streams', file]
|
||||||
|
});
|
||||||
|
if (!res || res.exitCode !== 0) { Logger.WLog('Sub: ffprobe fehlgeschlagen - ueberspringe'); return; }
|
||||||
|
var j;
|
||||||
|
try { j = JSON.parse(res.standardOutput || res.output || ''); } catch (e) { Logger.WLog('Sub: JSON-Fehler - ueberspringe'); return; }
|
||||||
|
|
||||||
|
// relative Subtitle-Streams sammeln (0:s:0, 0:s:1, ...)
|
||||||
|
var subs = [], sidx = 0;
|
||||||
|
var textCodecs = ['subrip', 'srt', 'ass', 'ssa', 'mov_text', 'webvtt', 'text', 'subviewer', 'eia_608'];
|
||||||
|
(j.streams || []).forEach(function (s) {
|
||||||
|
if (s.codec_type !== 'subtitle') return;
|
||||||
|
var codec = ('' + (s.codec_name || '')).toLowerCase();
|
||||||
|
var disp = s.disposition || {};
|
||||||
|
var tags = s.tags || {};
|
||||||
|
subs.push({
|
||||||
|
sIdx: sidx, // relativer -map 0:s:N Index
|
||||||
|
codec: codec,
|
||||||
|
isText: textCodecs.indexOf(codec) !== -1,
|
||||||
|
lang: ('' + (tags.language || 'und')).toLowerCase(),
|
||||||
|
forced: (disp.forced === 1) || /forced/i.test('' + (tags.title || '')),
|
||||||
|
isDefault: (disp.default === 1)
|
||||||
|
});
|
||||||
|
sidx++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subs.length === 0) { Logger.ILog('Sub: keine Untertitel-Spuren.'); return; }
|
||||||
|
|
||||||
|
var textSubs = subs.filter(function (s) { return s.isText; });
|
||||||
|
var imgSubs = subs.filter(function (s) { return !s.isText; });
|
||||||
|
|
||||||
|
// 1) Text-Subs als .srt-Sidecar extrahieren (jeweils eigener ffmpeg-Call, Main unangetastet)
|
||||||
|
var base = file.replace(/\.[^./]+$/, ''); // Pfad ohne Extension
|
||||||
|
var seen = {}; // lang+forced -> Zaehler (Mehrfachspuren)
|
||||||
|
textSubs.forEach(function (s) {
|
||||||
|
var key = s.lang + (s.forced ? '.forced' : '');
|
||||||
|
var n = seen[key] || 0; seen[key] = n + 1;
|
||||||
|
var suffix = '.' + s.lang + (s.forced ? '.forced' : '') + (n > 0 ? '.' + n : '');
|
||||||
|
var sidecar = base + suffix + '.srt';
|
||||||
|
var ex = Flow.Execute({
|
||||||
|
command: ffmpeg,
|
||||||
|
argumentList: ['-hide_banner', '-y', '-i', file,
|
||||||
|
'-map', '0:s:' + s.sIdx, '-c:s', 'srt', sidecar]
|
||||||
|
});
|
||||||
|
if (ex && ex.exitCode === 0) Logger.ILog('Sub: Sidecar extrahiert -> ' + sidecar);
|
||||||
|
else Logger.WLog('Sub: Sidecar-Extraktion fehlgeschlagen (0:s:' + s.sIdx + ', ' + s.codec + ')');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2) Remux nur wenn noetig: Text-Subs raus ODER ein Bild-Sub traegt default/forced.
|
||||||
|
var badImgDisp = imgSubs.some(function (s) { return s.isDefault || s.forced; });
|
||||||
|
if (textSubs.length === 0 && !badImgDisp) {
|
||||||
|
Logger.ILog('Sub: nur Bild-Subs ohne default/forced -> kein Remux noetig.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmp = Flow.TempPath + '/' + Flow.NewGuid() + '.mkv';
|
||||||
|
// Video + Audio + Attachments 1:1; Bild-Subs behalten (mit neutralisierter Disposition),
|
||||||
|
// Text-Subs weglassen.
|
||||||
|
var args = ['-hide_banner', '-y', '-i', file, '-map', '0:v?', '-map', '0:a?', '-map', '0:t?'];
|
||||||
|
var outSubPos = 0;
|
||||||
|
imgSubs.forEach(function (s) { args.push('-map', '0:s:' + s.sIdx); });
|
||||||
|
args.push('-c', 'copy');
|
||||||
|
// alle behaltenen Bild-Subs: default + forced weg (Emby brennt sonst automatisch ein)
|
||||||
|
imgSubs.forEach(function () {
|
||||||
|
args.push('-disposition:s:' + outSubPos, '0');
|
||||||
|
outSubPos++;
|
||||||
|
});
|
||||||
|
args.push('-dn', '-max_muxing_queue_size', '9999', tmp);
|
||||||
|
|
||||||
|
Logger.ILog('Sub: Remux (Text raus: ' + textSubs.length + ', Bild behalten: ' + imgSubs.length +
|
||||||
|
', Flags neutralisiert). FFmpeg: ' + args.join(' '));
|
||||||
|
var rm = Flow.Execute({ command: ffmpeg, argumentList: args });
|
||||||
|
if (!rm || rm.exitCode !== 0) {
|
||||||
|
Logger.WLog('Sub: Remux fehlgeschlagen (exit=' + (rm ? rm.exitCode : '?') + ') - Film bleibt unveraendert.');
|
||||||
|
if (rm && rm.standardError) Logger.WLog(rm.standardError);
|
||||||
|
Flow.Execute({ command: '/bin/rm', argumentList: ['-f', tmp] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var mv2 = Flow.Execute({ command: '/bin/mv', argumentList: ['-f', tmp, file] });
|
||||||
|
if (!mv2 || mv2.exitCode !== 0) {
|
||||||
|
Logger.WLog('Sub: Ersetzen der Datei fehlgeschlagen - Original bleibt.');
|
||||||
|
Flow.Execute({ command: '/bin/rm', argumentList: ['-f', tmp] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.ILog('Sub: MKV neu gemuxt -> ' + file);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue