HaveAGitGat / Tdarr_Plugins

Tdarr Plugins
GNU General Public License v3.0
137 stars 157 forks source link

[feature request] AV1 variant #342

Open Tom-Neverwinter opened 1 year ago

Tom-Neverwinter commented 1 year ago

Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js and Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js

response.preset += `,${map} -dn -c:v libaom-av1 -b:v 0 -preset ${ffmpeg_preset} -crf ${inputs.fullhdCRF} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}

or something of the sort based on:

https://trac.ffmpeg.org/wiki/Encode/AV1

assuming bframe still works: https://scl.ece.ucsb.edu/sites/default/files/publications/08416609.pdf

look deeper: https://gitlab.com/AOMediaCodec/SVT-AV1/-/commits/master/

Tom-Neverwinter commented 1 year ago

This isnt quite right but is close [libaom-av1 has a constant quality (CQ) mode]

/* eslint-disable */
const details = () => {
  return {
    id: 'Tdarr_Plugin_vdka_Tiered_AV1_CQV_BASED_CONFIGURABLE',
    Stage: 'Pre-processing',
    Name: 'Tiered FFMPEG+AV1 CQ:V BASED CONFIGURABLE',
    Type: 'Video',
    Operation: 'Transcode',
    Description: `[Contains built-in filter] This plugin uses different CQ:V values (similar to crf but for AV1) depending on resolution, 
     the CQ:V value is configurable per resolution.
     FFmpeg Preset can be configured, uses slow by default. 
     ALL OPTIONS MUST BE CONFIGURED UNLESS MARKED OPTIONAL!
     If files are not in av1 they will be transcoded. 
     The output container is av1. \n\n`,
    Version: '1.00',
    Tags: 'pre-processing,ffmpeg,video only,av1,configurable',

    Inputs: [
      {
        name: 'sdCQV',
        type: 'string',
        defaultValue: '19',
        inputUI: {
          type: 'text',
        },
        tooltip: `Enter the CQ:V value you want for 480p and 576p content. 
       \\nExample:\\n 

      19`
      },
      {
        name: 'hdCQV',
        type: 'string',
        defaultValue: '19',
        inputUI: {
          type: 'text',
        },
        tooltip: `Enter the CQ:V value you want for 720p content.  

      \\nExample:\\n
      19`
      },
      {
        name: 'fullhdCQV',
        type: 'string',
        defaultValue: '19',
        inputUI: {
          type: 'text',
        },
        tooltip: `Enter the CQ:V value you want for 1080p content.  

      \\nExample:\\n
      19`
      },
      {
        name: 'uhdCQV',
        type: 'string',
        defaultValue: '19',
        inputUI: {
          type: 'text',
        },
        tooltip: `Enter the CQ:V value you want for 4K/UHD/2160p content.  

      \\nExample:\\n
      19`
      },
      {
        name: 'bframe',
        type: 'string',
        defaultValue: '0',
        inputUI: {
          type: 'text',
        },
        tooltip: `Specify amount of b-frames to use, 0-5. Use 0 to disable. (GPU must support this, turing and newer supports this, except for the 1650)  

      \\nExample:\\n
      3`
      },
      {
        name: 'ffmpeg_preset',
        type: 'string',
        defaultValue: 'slow',
        inputUI: {
          type: 'text',
        },
        tooltip: `OPTIONAL, DEFAULTS TO SLOW IF NOT SET 
      \\n Enter the ffmpeg preset you want, leave blank for default (slow) 
      \\n This only applies if video is transcoded, video already in av1 will not be transcoded with this setting

      \\nExample:\\n 
        slow  

      \\nExample:\\n 
        medium  

      \\nExample:\\n 
        fast  

      \\nExample:\\n 
        veryfast`
      }
    ]
  }
}

// eslint-disable-next-line no-unused-vars
const plugin = (file, librarySettings, inputs, otherArguments) => {

    const lib = require('../methods/lib')();
  // eslint-disable-next-line no-unused-vars,no-param-reassign
  inputs = lib.loadDefaultValues(inputs, details);
  var transcode = 0 //if this var changes to 1 the file will be transcoded
  var subcli = `-c:s copy`
  var maxmux = ''
  var map = '-map 0'
  var cqvinuse = ''
  //default values that will be returned
  var response = {
    processFile: false,
    preset: '',
    container: '.av1',
    handBrakeMode: false,
    FFmpegMode: false,
    reQueueAfter: true,
    infoLog: '',
  }

  //check if the file is a video, if not the function will be stopped immediately
  if (file.fileMedium !== 'video') {
    response.processFile = false
    response.infoLog += '☒File is not a video! \n'
    return response
  } else {
    // bitrateprobe = file.ffProbeData.streams[0].bit_rate;
    response.infoLog += '☑File is a video! \n'
  }

  //check if the file is already hevc, it will not be transcoded if true and the function will be stopped immediately
  if (file.ffProbeData.streams[0].codec_name == 'av1') {
    response.processFile = false
    response.infoLog += '☑File is already in av1! \n'
    return response
  }

  // Check if preset is configured, default to slow if not
  var ffmpeg_preset
  if (inputs.ffmpeg_preset === undefined) {
    ffmpeg_preset = `slow`
    response.infoLog += '☑Preset not set, defaulting to slow\n'
  } else {
    ffmpeg_preset = `${inputs.ffmpeg_preset}`
    response.infoLog += `☑Preset set as ${inputs.ffmpeg_preset}\n`
  }

  //codec will be checked so it can be transcoded correctly
  if (file.video_codec_name == 'h263') {
    response.preset = `-c:v h263_cuvid`
  } else if (file.video_codec_name == 'h264') {
    if (file.ffProbeData.streams[0].profile != 'High 10') {
      //Remove HW Decoding for High 10 Profile
      response.preset = `-c:v h264_cuvid`
    }
  } else if (file.video_codec_name == 'mjpeg') {
    response.preset = `c:v mjpeg_cuvid`
  } else if (file.video_codec_name == 'mpeg1') {
    response.preset = `-c:v mpeg1_cuvid`
  } else if (file.video_codec_name == 'mpeg2') {
    response.preset = `-c:v mpeg2_cuvid`
  }
  // skipping this one because it's empty
  //  else if (file.video_codec_name == 'mpeg4') {
  //    response.preset = ``
  //  }
  else if (file.video_codec_name == 'vc1') {
    response.preset = `-c:v vc1_cuvid`
  } else if (file.video_codec_name == 'vp8') {
    response.preset = `-c:v vp8_cuvid`
  } else if (file.video_codec_name == 'vp9') {
    response.preset = `-c:v vp9_cuvid`
  }

  //Set Subtitle Var before adding encode cli
  for (var i = 0; i < file.ffProbeData.streams.length; i++) {
    try {
      if (
        file.ffProbeData.streams[i].codec_name.toLowerCase() == 'mov_text' &&
        file.ffProbeData.streams[i].codec_type.toLowerCase() == 'subtitle'
      ) {
        subcli = `-c:s srt`
      }
    } catch (err) {}
    //mitigate TrueHD audio causing Too many packets error
    try {
      if (
        file.ffProbeData.streams[i].codec_name.toLowerCase() == 'truehd' ||
        (file.ffProbeData.streams[i].codec_name.toLowerCase() == 'dts' &&
          file.ffProbeData.streams[i].profile.toLowerCase() == 'dts-hd ma') ||
        (file.ffProbeData.streams[i].codec_name.toLowerCase() == 'aac' &&
          file.ffProbeData.streams[i].sample_rate.toLowerCase() == '44100' &&
          file.ffProbeData.streams[i].codec_type.toLowerCase() == 'audio')
      ) {
        maxmux = ` -max_muxing_queue_size 9999`
      }
    } catch (err) {}
    //mitigate errors due to embeded pictures
    try {
      if (
        (file.ffProbeData.streams[i].codec_name.toLowerCase() == 'png' ||
          file.ffProbeData.streams[i].codec_name.toLowerCase() == 'bmp' ||
          file.ffProbeData.streams[i].codec_name.toLowerCase() == 'mjpeg') &&
        file.ffProbeData.streams[i].codec_type.toLowerCase() == 'video'
      ) {
        map = `-map 0:v:0 -map 0:a -map 0:s?`
      }
    } catch (err) {}
  }
  //file will be encoded if the resolution is 480p or 576p
  //codec will be checked so it can be transcoded correctly
  if (file.video_resolution === '480p' || file.video_resolution === '576p') {
    cqvinuse = `${inputs.sdCQV}`
    response.preset += `,${map} -dn -c:v libaom-av1 -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.sdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
    transcode = 1
  }

  //file will be encoded if the resolution is 720p
  //codec will be checked so it can be transcoded correctly
  if (file.video_resolution === '720p') {
    cqvinuse = `${inputs.hdCQV}`
    response.preset += `,${map} -dn -c:v libaom-av1 -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.hdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
    transcode = 1
  }
  //file will be encoded if the resolution is 1080p
  //codec will be checked so it can be transcoded correctly
  if (file.video_resolution === '1080p') {
    cqvinuse = `${inputs.fullhdCQV}`
    response.preset += `,${map} -dn -c:v libaom-av1 -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.fullhdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
    transcode = 1
  }
  //file will be encoded if the resolution is 4K
  //codec will be checked so it can be transcoded correctly
  if (file.video_resolution === '4KUHD') {
    cqvinuse = `${inputs.uhdCQV}`
    response.preset += `,${map} -dn -c:v libaom-av1 -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.uhdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
    transcode = 1
  }
  //check if the file is eligible for transcoding
  //if true the neccessary response values will be changed
  if (transcode == 1) {
    response.processFile = true
    response.FFmpegMode = true
    response.reQueueAfter = true
    response.infoLog += `☑File is ${file.video_resolution}, using CQ:V value of ${cqvinuse}!\n`
    response.infoLog += `☒File is not hevc!\n`
    response.infoLog += `File is being transcoded!\n`
  }

  return response
}

module.exports.details = details;
module.exports.plugin = plugin;
uhthomas commented 1 year ago

container: '.av1',

This should be mkv? I don't believe av1 is a valid container.