Tdarr_Plugins icon indicating copy to clipboard operation
Tdarr_Plugins copied to clipboard

Plugin to automatically crop black bars from video stream (Left/Right &| Top/Bottom)

Open argonan0 opened this issue 2 years ago • 1 comments

It would be great if there was a lossless crop plugin before another encode plugin runs.

Scenario is 1920x1080 video that is actually 4:3 with black bars.

argonan0 avatar Mar 10 '22 17:03 argonan0

Seconded. This should probably be broken up into two separate flow modules: Detect if black bars exist, and crop black bars. Also the ability to do this with GPU encoders would be a big plus!

This is a function I currently use in a custom plugin to detect black bars. It takes a bit of time and CPU to actually run, so there's probably a better implementation of the actual crop command. I'm just a monkey with a keyboard.

// Use ffmpeg cropdetect to detect black bars.
// If black bars are detected then set the crop variable to the detected values.
function generate_crop_values(file, otherArguments) {
    const fs = require('fs');
    const path = require('path');
    const { execSync } = require('child_process');

    const sourceFile = file.meta.SourceFile;

    // FFmpeg command to extract crop information and frames from the video
    const ffmpegCropCommand = `${otherArguments.ffmpegPath} -ss 120 -i \"${sourceFile}\" -t 9:00 -vf fps=1/2,cropdetect -f null - 2>&1`;
    const output = execSync(ffmpegCropCommand);

    // Extract crop values from output using regex
    const crops = output
        .toString()
        .match(/crop=\S+/g)
        .map((crop) => crop.substring(5));

    //Get the most commonly returned number and set that as the crop value
    //ffmpeg returns 4 values for cropdetect: width:height:x:y
    var crop_w_mode = [];
    var crop_h_mode = [];
    var crop_x_mode = [];
    var crop_y_mode = [];
    for (var c = 0; c < crops.length; c++) {
        crop = crops[c].split(':');
        crop_w_mode.push(parseInt(crop[0]));
        crop_h_mode.push(parseInt(crop[1]));
        crop_x_mode.push(parseInt(crop[2]));
        crop_y_mode.push(parseInt(crop[3]));
    }
    wMode = findMode(crop_w_mode);
    hMode = findMode(crop_h_mode);
    xMode = findMode(crop_x_mode);
    yMode = findMode(crop_y_mode);

    // Return a dict of the crop values
    return {w:wMode, h:hMode, x:xMode, y:yMode};
};

Here's an example of how I've implemented it with NVENC hardware acceleration. The response.preset value is a string that's returned to Tdarr at the end of the plugin for the ffmpeg command.

//Set hardware acceleration
if (file.ffProbeData.streams[0].codec_name === 'h264') {
    if (main10 === false) {
        response.preset += '-hwaccel cuvid -hwaccel_output_format cuda -c:v h264_cuvid '
    if (crop_values.x > 10 || crop_values.y > 10) {
        response.preset += `-crop ${crop_values.y}x${crop_values.y}x${crop_values.x}x${crop_values.x} <io> -map 0 `;
    } else {
        response.preset += `<io> -map 0 `;
    };
    } else {
    // If file is 10bit then disable hardware decode as it's unsupported
    if (crop_values.x > 10 || crop_values.y > 10) {
        response.preset += `<io> -map 0 -vf crop=${crop_values.w}:${crop_values.h}:${crop_values.x}:${crop_values.y},hwupload_cuda `
    } else {
        response.preset += `<io> -map 0 `
    }
    };
} else if (file.ffProbeData.streams[0].codec_name === 'hevc') {
    if (main10 === false) {
        response.preset += '-hwaccel cuvid -hwaccel_output_format cuda -c:v hevc_cuvid '
    if (crop_values.x > 10 || crop_values.y > 10) {
        response.preset += `-crop ${crop_values.y}x${crop_values.y}x${crop_values.x}x${crop_values.x} <io> -map 0 `;
    } else {s
        response.preset += `<io> -map 0 `;
    };
    } else {
    // If file is 10bit then disable hardware decode as it's unsupported
    if (crop_values.x > 10 || crop_values.y > 10) {
        response.preset += `<io> -map 0 -vf crop=${crop_values.w}:${crop_values.h}:${crop_values.x}:${crop_values.y},hwupload_cuda `
    } else {
        response.preset += `<io> -map 0 `
    }
    };
} else {
    // Unexpected file format, running without hardware decode
    if (crop_values.x > 10 || crop_values.y > 10) {
        response.preset += `<io> -map 0 -vf crop=${crop_values.w}:${crop_values.h}:${crop_values.x}:${crop_values.y},hwupload_cuda `;
    } else {
        response.preset += `<io> -map 0 `;
    };
};

himea-saito avatar Aug 30 '23 23:08 himea-saito