From 7aa14490b3a8750c088407c2eb94680a13b27c3e Mon Sep 17 00:00:00 2001 From: sascha Date: Fri, 17 Jul 2026 19:34:08 +0200 Subject: [PATCH] chore: add 01_Detect_4K_Remux.js from live FileFlows export --- fileflows/scripts/01_Detect_4K_Remux.js | 109 ++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 fileflows/scripts/01_Detect_4K_Remux.js diff --git a/fileflows/scripts/01_Detect_4K_Remux.js b/fileflows/scripts/01_Detect_4K_Remux.js new file mode 100644 index 0000000..2325449 --- /dev/null +++ b/fileflows/scripts/01_Detect_4K_Remux.js @@ -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; +}