/** * @name 01_Detect_4K_Remux * @description Erkennt 4K-HEVC-Kandidaten fuer AV1: Remux-Dateien oder Quellen mit mehr als 35 Mbit/s. * @author Sascha (Port von Tdarr_Plugin_AV1_BestQuality_v100) * @revision 2 * @output Ja - 4K HEVC -> AV1 Encode * @output Nein - kein 4K AV1-Kandidat * @param {int} MinWidth Minimale Breite in Pixel, ab der eine Datei als 4K gilt * @param {int} MinSourceMbps Minimale Quellenbitrate fuer klassische Remux-Kandidaten */ function Script(MinWidth, MinSourceMbps) { MinWidth = MinWidth || 3000; MinSourceMbps = MinSourceMbps || 15; // Titel sind unzuverlaessig: WEB-DL/UHD-BluRay-Releases tragen oft kein "Remux". // Deshalb ist jede 4K-HEVC-Quelle oberhalb dieser VIDEO-Bitrate ebenfalls Kandidat. const HighBitrateMbps = 35; // ───────────────────────────────────────────────────────────────────────── // 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 klassische Remuxe weiterhin zulassen; Nicht-Remuxe // brauchen dagegen einen belastbaren Messwert oberhalb von 35 Mbit/s. let brKnown = bitrateMbps > 0; let highBR = brKnown ? (bitrateMbps >= MinSourceMbps) : true; let overHighBitrate = brKnown && bitrateMbps > HighBitrateMbps; let sourceCandidate = isRemux || overHighBitrate; 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 + ' >' + HighBitrateMbps + 'Mbit=' + overHighBitrate + (brKnown ? '' : ' (Bitrate unbekannt)')); if (isAV1) { Logger.ILog('Bereits AV1 - kein Re-Encode.'); return 2; } if (!is4K) { Logger.ILog('Nicht 4K (<' + MinWidth + 'px).'); return 2; } if (!isHEVC) { Logger.ILog('Codec ist nicht HEVC/H265.'); return 2; } if (!sourceCandidate) { Logger.ILog('Weder Remux noch Video-Bitrate > ' + HighBitrateMbps + ' Mbit/s.'); return 2; } if (isRemux && !highBR) { Logger.ILog('Remux-Bitrate < ' + MinSourceMbps + ' Mbit/s - lohnt nicht.'); return 2; } Logger.ILog('-> 4K HEVC Kandidat fuer AV1 Encode (' + (isRemux ? 'Remux' : 'hohe Bitrate ' + bitrateMbps.toFixed(1) + ' Mbit/s') + ')'); return 1; }