header={ "recipe_version": "1.15", "title": "Trim Video Black Detect", "description": "detect if the video has black frames at the beginning and remove them", "tags": "video", "chef": "BeatRig", "spice": "BQ==:G+Hy36kJcnQQYYavb/JV2Il6eCYr6h4hRJHCF9rRbhX5BcazL6cuTgUMdz0xA50VWA5PmmV0Q7aWkiUtNFtbk991l+t0e8IBXMReMIT1GNjnZZDv7n2YRAo0xcSQbY0ixjG6LLH4wGKnspKAoA/v69iLn6N2IH0XzIZwTGdHSo4=", "palette": "Clean Slate", "flavour": "nvb1kiqzIZRJ2c6uMIB+7p4av9LxKnGzLgvb3dKKrCEjLKN+HYTVQZgiWby+Qoxcg+XG2jVUCTAVkUv8ufjCbNfY2k9A30GADR+t4/Ri9JAU5T6eCojF8sw7876hvPgPqukiPfN9mY7Hi6ugVXWizLJ+P+L30xT78dKqRIYMOjo=", "time": 1736352941, "core_version": "0.7.2", "magnetron_version": "1.0.333", "dependencies": "ffprobe,ffmpeg", "type": "default", "os": "windows,macOS", "functions": "main", "uuid": "afc871158af44535830b64de1bc88dac", "instructions": "Drop file(s) here" }; // ============================================================================= // GLOBALS var duration = 0; // ============================================================================= function main() { // SET PROGRESS BAR setProgress(101); // GET ALL FILES var files = getFiles(); // CHECK VALID FILES for ( const [k, v] of Object.entries(files) ) { setMainMessage("preparing file "+k+" of "+files.length); // RUN FFPROBE TO GET STREAM TYPE AND DURATION IN ONE CALL let ffprobeOutput = cmd("ffprobe", [ "-loglevel", "error", '-select_streams', 'v:0', "-show_entries", "stream=codec_type,format=duration", "-of", "json", v.path, ]); // PARSE FFPROBE OUTPUT const ffprobeData = JSON.parse(ffprobeOutput); const isVideo = ffprobeData.streams.some(stream => stream.codec_type === "video"); duration = ffprobeData.format && ffprobeData.format.duration ? Math.round(parseFloat(ffprobeData.format.duration) * 1000) : null; // IF IS NOT A VIDEO FILE if(!isVideo) { getFileIcon(v.path, v.index, 'Exclamation'); getFileIconColor(v.path, v.index, 'FFff0000'); setFileStatus(v.path, v.index, 'Not a valid video file'); continue; } // // UPDATE FILE LIST getFileIcon(v.path, v.index, 'CaretSquareORight'); getFileIconColor(v.path, v.index, 'FFdddddd'); setFileStatus(v.path, v.index, 'processing'); // OUTFILE let path = getPathInfo(v.path); let vidout = path.folder+"/"+path.basename+"-[trimmed]."+path.ext; // FFMPEG CALL setMainMessage("analyzing frames"); var blackDetect = cmd("ffprobe", [ '-f','lavfi', '-i','movie='+v.path+',blackdetect[out0]', '-show_entries','tags=lavfi.black_start,lavfi.black_end', '-of','default=nw=1', // "-loglevel", "quiet", // "-progress", "pipe:1", // "-nostats" ]); const blackStart = parseFloat(blackDetect.match(/TAG:lavfi\.black_start=(.*?)(?=[\n\r])/)?.[1] || null); const blackEnd = parseFloat(blackDetect.match(/TAG:lavfi\.black_end=(.*?)(?=[\n\r])/)?.[1] || null); if(blackStart == 0. && blackEnd > 0.) { setMainMessage("trimming file"); cmd("ffmpeg", [ '-ss',blackEnd, '-i',v.path, '-vcodec','copy', '-acodec','copy', vidout, '-y', "-loglevel", "quiet", "-progress", "pipe:1", "-nostats" ]); } // --------------------------------------------------------------------- // THIS FILE IS DONE setProgress(100); // SET STATUS if(!fileExists(vidout) || getFileBytes(vidout) == 0) { if(fileExists(vidout)) deleteFile(vidout); getFileIcon(v.path, v.index, 'Exclamation'); getFileIconColor(v.path, v.index, 'FFff0000'); setFileStatus(v.path, v.index, 'ERROR'); } else { getFileIcon(v.path, v.index, 'CheckSquare'); getFileIconColor(v.path, v.index, 'FF00aa00'); setFileStatus(v.path, v.index, 'DONE'); } // --------------------------------------------------------------------- }; // ALL IS DONE setMainMessage("all done"); } // ============================================================================= // FFMPEG PROCESS STATUS function cmd_callback(cmd_name, cmd_output) { if (cmd_name === "ffmpeg" && cmd_output.length > 0 && duration > 0) { const progress = Math.round((parseInt(cmd_output.match(/out_time_ms=(\d+)/)?.pop() || 0, 10) / 1000) / duration * 100); setProgress(progress); } }