chore: add 01_Detect_4K_Remux.js from live FileFlows export

This commit is contained in:
sascha 2026-07-17 19:34:08 +02:00
parent 12cfca26d1
commit 7aa14490b3

View file

@ -0,0 +1,109 @@
function Script(MinWidth, MinSourceMbps)
{
MinWidth = MinWidth || 3000;
MinSourceMbps = MinSourceMbps || 15;
// ─────────────────────────────────────────────────────────────────────────
// WICHTIG (verifiziert 2026-07-06): Variables.vi.VideoInfo wird in dieser
// FileFlows-Version im ScriptNode NICHT befuellt (0/7 Laeufe hatten vi-Daten,
// die "loadVi"-Warteschleife war wirkungslos). Der native VideoInfo-Zugriff
// laeuft nur ueber den FFmpeg-Builder-Node, nicht im Script.
// -> Verlaesslicher Weg: ffprobe direkt aufrufen und JSON parsen.
// Liefert echte Kanalzahlen (6/8) statt Layout-Strings ("5.1").
// ─────────────────────────────────────────────────────────────────────────
function probeInfo() {
var probe = null;
try { probe = Flow.GetToolPath('ffprobe'); } catch (e) {}
if (!probe) probe = Variables['ffprobe'] || 'ffprobe';
var input = Flow.WorkingFile;
var res = Flow.Execute({
command: probe,
argumentList: ['-v', 'quiet', '-print_format', 'json',
'-show_format', '-show_streams', input]
});
if (!res || res.exitCode !== 0) { Logger.WLog('ffprobe fehlgeschlagen (exit=' + (res ? res.exitCode : '?') + ')'); return null; }
var raw = res.standardOutput || res.output || '';
var j;
try { j = JSON.parse(raw); } catch (e) { Logger.WLog('ffprobe JSON parse-Fehler: ' + e); return null; }
var streams = j.streams || [];
var fmtBr = parseInt((j.format && j.format.bit_rate) || 0, 10) || 0;
function tagBps(s) { var t = s.tags || {}; return parseInt(t.BPS || t['BPS-eng'] || t.bps || 0, 10) || 0; }
var vids = [], auds = [];
for (var i = 0; i < streams.length; i++) {
var s = streams[i], disp = s.disposition || {};
if (s.codec_type === 'video') {
// cover/poster (attached_pic, mjpeg, png) ignorieren
if (disp.attached_pic === 1 || s.codec_name === 'mjpeg' || s.codec_name === 'png') continue;
var dovi = (s.side_data_list || []).some(function (x) {
var t = ('' + (x.side_data_type || '')).toLowerCase();
return t.indexOf('dovi') !== -1 || t.indexOf('dolby vision') !== -1;
});
var trc = ('' + (s.color_transfer || ''));
vids.push({
Width: s.width || 0, Height: s.height || 0,
Codec: (s.codec_name || ''),
Bitrate: tagBps(s) || 0,
ColorTransfer: trc, ColorPrimaries: s.color_primaries || '', ColorSpace: s.color_space || '',
PixelFormat: s.pix_fmt || '',
DolbyVision: dovi,
// P7 hat HDR10-Base-Layer (smpte2084), P5 ist ICtCp (kein smpte2084)
DolbyVisionProfile: dovi ? (trc.indexOf('smpte2084') !== -1 ? 7 : 5) : null
});
} else if (s.codec_type === 'audio') {
var t = s.tags || {};
auds.push({
Language: t.language || '', Codec: (s.codec_name || ''),
Channels: s.channels || 0, ChannelLayout: s.channel_layout || '',
Title: t.title || '', Profile: s.profile || '',
Default: (disp.default === 1), Bitrate: tagBps(s) || 0
});
}
}
return { Bitrate: fmtBr, VideoStreams: vids, AudioStreams: auds };
}
let vi = probeInfo();
let v = (vi && vi.VideoStreams && vi.VideoStreams.length > 0) ? vi.VideoStreams[0] : null;
if (!v) {
Logger.ELog('Kein Video-Stream ermittelbar (ffprobe) - kann 4K-Remux nicht bewerten');
return 2;
}
let width = v.Width || 0;
let codec = (v.Codec || '').toLowerCase();
let bitrateBps = v.Bitrate || 0;
// Video-Bitrate fehlt (kein BPS-Tag) -> aus Container-Gesamtbitrate schaetzen (~92%)
if (bitrateBps <= 0 && vi.Bitrate) bitrateBps = Math.round(vi.Bitrate * 0.92);
let bitrateMbps = bitrateBps > 0 ? (bitrateBps / 1000000) : 0;
// Dateiname aus den flachen Punkt-Keys (Variables.file als Objekt existiert NICHT in dieser Version)
let fileName = Variables['file.Orig.FullName'] || Variables['file.FullName']
|| Variables['file.Name'] || '';
if (!fileName) { try { fileName = Flow.WorkingFile || ''; } catch (e) {} }
let is4K = width >= MinWidth;
let isRemux = /remux/i.test(fileName);
let isHEVC = (codec === 'hevc' || codec === 'h265');
let isAV1 = (codec === 'av1');
// Bei unbekannter Bitrate NICHT ablehnen: ein 4K-HEVC-Remux ist per Definition hochbitratig.
let brKnown = bitrateMbps > 0;
let highBR = brKnown ? (bitrateMbps >= MinSourceMbps) : true;
Logger.ILog('Datei: ' + fileName);
Logger.ILog('Video: codec=' + codec + ' width=' + width + ' bitrate=' +
(brKnown ? bitrateMbps.toFixed(1) + ' Mbit/s' : 'unbekannt'));
Logger.ILog('Checks: 4K=' + is4K + ' Remux=' + isRemux + ' HEVC=' + isHEVC +
' >=' + MinSourceMbps + 'Mbit=' + highBR + (brKnown ? '' : ' (Bitrate unbekannt->Remux gilt als HB)'));
if (isAV1) { Logger.ILog('Bereits AV1 - kein Re-Encode.'); return 2; }
if (!is4K) { Logger.ILog('Nicht 4K (<' + MinWidth + 'px).'); return 2; }
if (!isRemux) { Logger.ILog('Kein "Remux" im Dateinamen.'); return 2; }
if (!isHEVC) { Logger.ILog('Codec ist nicht HEVC/H265.'); return 2; }
if (!highBR) { Logger.ILog('Bitrate < ' + MinSourceMbps + ' Mbit/s - lohnt nicht.'); return 2; }
Logger.ILog('-> 4K Remux HEVC Kandidat fuer AV1 Encode');
return 1;
}