Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC - Added AV1 encode support & HDR improvement #706

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Boosh1
Copy link
Contributor

@Boosh1 Boosh1 commented Oct 20, 2024

Fairly major revision. Been using this locally for months on HEVC & AV1 encodes and not had any issues currently. I can't guarantee my 4K HDR & DoVi setups are 'actually' working for real but they seem to look correct & I've not had errors for encodes or on Plex.

All testing was done on an Unraid setup, 6.12.13, using a Arc 380. Custom firmware so Arc can be used. Should be using default Tdarr docker setup with current inbuilt ffmpeg.

With AV1 support, admittedly this may be confusing since the plugin ID still specifies HEVC but I have updated the name. I didn't want to split this into a new plugin since the logic is pretty much exactly the same & I don't want to maintain two almost identical plugins)

  • Adding AV1 encode support, added as drop down selector (default is still HEVC)
  • Logic should pick valid video profile for either encoder setting
  • HDR detection & encode improvement (Appears to work on my HDR content & DoVi)
  • Added target bitrate modifier so it's possible to tune the bitrate (useful for AV1 to crunch it down further)
  • Better bitrate detection, should error if it can't be found at all
  • I've not verified any Mac setups but they can't use QSV anyway... I've left support in so it can use Videotoolbox, but if errors do show up it may be time to remove
  • Cleaned up descriptions & info logs where reasonable to try and clarify what's happening
  • Tests updated & added new test for a fully loaded AV1 setup

Fairly major revision. Been using this locally for months on HEVC & AV1 encodes and not had any issues currently.
I can't verify if my 4k HDR & DoVi setups are 'actually' working for real but they seem to look correct & I've not had errors for encodes or on Plex.

With AV1 support, admittedly this may be confusing since the plugin ID still specifies HEVC but I have updated the name. I didn't want to split this into a new plugin since the logic is pretty much exactly the same & I don't want to maintain two almost identical plugins)

- Adding AV1 encode support, added as drop down selector (default is still hevc)
- Logic should pick valid video profile for either encoder setting
- HDR detection & encode improvement (Appears to work on my HDR content & DoVi)
- Added target bitrate modifier so it's possible to tune the bitrate (useful for AV1 to crunch it down further)
- Better bitrate detection, should error if it can't be found at all
- Mac support is over. Doubt anyone was really using it on Mac but it's not worth touching further. Plugin should still use videotoolbox when on Mac but I won't be fixing up errors.
- Cleaned up descriptions & info logs where reasonable to try and clarify what's happening
- Tests updated & added new test for a fully loaded AV1 setup
@Boosh1
Copy link
Contributor Author

Boosh1 commented Oct 21, 2024

Will mention that the options "hevc_max_bitrate" & "reconvert_hevc" now are named a little poorly due to av1 support, but I don't think I can re-name without settings being lost which I'd like to avoid.
I have updated descriptions & this option previously has always included reconverting hevc, av1 & vp9 files anyway.

@tws101
Copy link
Contributor

tws101 commented Oct 21, 2024

What HDR is output ?

and can we use this method to make a flow generally preserver HDR in the general case...

I can do it in the general case with
CPU encode and NVIDIA encode but I was never able to get the FLOW general case to work without an encode error on QSV hardware...

I tried with this could net get QSV to work everything else HDR in came out HDR 10 out correctly

import {
  IpluginDetails,
  IpluginInputArgs,
  IpluginOutputArgs,
} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';

/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
const details = () :IpluginDetails => ({
  name: 'HDR all to HDR 10',
  description: 'Any HDR video stream already being encoded will be output to HDR 10',
  style: {
    borderColor: '#6efefc',
  },
  tags: 'video',
  isStartPlugin: false,
  pType: '',
  requiresVersion: '2.11.01',
  sidebarPosition: -1,
  icon: '',
  inputs: [],
  outputs: [
    {
      number: 1,
      tooltip: 'Continue to next plugin',
    },
  ],
});

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const plugin = (args:IpluginInputArgs):IpluginOutputArgs => {
  // @ts-expect-error require
  const lib = require('../../../../../methods/lib')();
  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
  args.inputs = lib.loadDefaultValues(args.inputs, details);

  for (let i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) {
    const stream = args.variables.ffmpegCommand.streams[i];
    if (stream.codec_type === 'video') {
      if (stream.color_space === 'bt2020nc') {
        args.jobLog(`HDR Detected, Using HDR 10 pixel format.`);
        stream.outputArgs.push('-profile:v:{outputTypeIndex}', 'main10');
        // @ts-expect-error includes string
        if (stream.outputArgs.some((row) => row.includes('qsv'))) {
          stream.outputArgs.push('-vf', 'scale_qsv=format=p010le');
        } else {
          stream.outputArgs.push('-pix_fmt:v:{outputTypeIndex}', 'p010le');
        }
        stream.outputArgs.push('-color_primaries','bt2020','-colorspace','bt2020nc','-color_trc','smpte2084'); 
      }
    }
  }

  return {
    outputFileObj: args.inputFileObj,
    outputNumber: 1,
    variables: args.variables,
  };
};
export {
  details,
  plugin,
};



@Boosh1
Copy link
Contributor Author

Boosh1 commented Oct 21, 2024

I've currently ensured the color data is pulled over correctly by just copying what the file reports. i.e just setting these on the encode cmd.

`-color_primaries ${file.ffProbeData.streams[i].color_primaries} `
 `-color_trc ${file.ffProbeData.streams[i].color_transfer} `
 `-colorspace ${file.ffProbeData.streams[i].color_space} `;

However on properly reviewing my HDR files I am noticing actual HDR metadata is missing. This method is likely 'ok' for bog standard HDR but Dolby vision is probably wrong. I'm actually reviewing this now, it might be ok.

Something to note for your code, the video profiles on AV1 & HEVC are different. I'm assuming you're trying av1 due to being on this thread so;
stream.outputArgs.push('-profile:v:{outputTypeIndex}', 'main10');
may instead need to be
stream.outputArgs.push('-profile:v:{outputTypeIndex}', 'main'); OR stream.outputArgs.push('-profile:v:{outputTypeIndex}', 'high');
high possibly being overkill unless you actually have 4:4:4 chroma subsampled content.

@Boosh1
Copy link
Contributor Author

Boosh1 commented Oct 22, 2024

Ok so I have reviewed my test cases again & it appears metadata is in tact for the most part. The output HDR format seems to match what comes in (be that HDR, HDR10, dolby vision).
The issue I may have is that some files possibly lose data if the source doesn't specify them properly (especially if things like mastering display info was never included), but viewing the file displays no evident problem. This is part of the reason why I'm not claiming this is a proper implementation as I just can't verify for sure that everything is intact after.
On good source files though the HDR after conversion looks good based on the file info & even the files that seem to be missing bits of meta data display well on a HDR screen (but I'm no HDR expert). In this end, this works well enough for me

i.e this is a converted file, dolby vision & HDR10 data appears to be intact & correct.
ffprobe

index:0,
codec_name:"av1",
codec_long_name:"Alliance for Open Media AV1",
profile:"Main",
codec_type:"video",
codec_tag_string:"[0][0][0][0]",
codec_tag:"0x0000",
width:3840,
height:2160,
coded_width:3840,
coded_height:2160,
closed_captions:0,
film_grain:0,
has_b_frames:0,
sample_aspect_ratio:"1:1",
display_aspect_ratio:"16:9",
pix_fmt:"yuv420p10le",
level:12,
color_range:"tv",
color_space:"bt2020nc",
color_transfer:"smpte2084",
color_primaries:"bt2020",
field_order:"progressive",
refs:1,
r_frame_rate:"24000/1001",
avg_frame_rate:"24000/1001",
time_base:"1/1000",
start_pts:42,
start_time:"0.042000",
extradata_size:22,
disposition:{
default:1,
dub:0,
original:0,
comment:0,
lyrics:0,
karaoke:0,
forced:0,
hearing_impaired:0,
visual_impaired:0,
clean_effects:0,
attached_pic:0,
timed_thumbnails:0,
captions:0,
descriptions:0,
metadata:0,
dependent:0,
still_image:0
},
tags:{
ENCODER:"Lavc60.3.100 av1_qsv",
BPS:"7156765",
DURATION:"02:58:28.947708333",
NUMBER_OF_FRAMES:"256758",
NUMBER_OF_BYTES:"9580178346",
_STATISTICS_WRITING_APP:"mkvpropedit v85.0 ('Shame For You') 64-bit",
_STATISTICS_WRITING_DATE_UTC:"2024-10-15 15:32:53",
_STATISTICS_TAGS:"BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
},
side_data_list:[
{
side_data_type:"Mastering display metadata",
red_x:"11878269/16777216",
red_y:"4898947/16777216",
green_x:"11408507/67108864",
green_y:"13371441/16777216",
blue_x:"8791261/67108864",
blue_y:"12348031/268435456",
white_point_x:"10492471/33554432",
white_point_y:"689963/2097152",
min_luminance:"201883/2018830051",
max_luminance:"1000/1"
},
{
side_data_type:"DOVI configuration record",
dv_version_major:1,
dv_version_minor:0,
dv_profile:10,
dv_level:8,
rpu_present_flag:1,
el_present_flag:0,
bl_present_flag:1,
dv_bl_signal_compatibility_id:1
}
]

Mediainfo

@type:"Video",
StreamOrder:"0",
ID:"1",
UniqueID:"7777815772280829429",
Format:"AV1",
Format_Profile:"Main",
Format_Level:"5.0",
HDR_Format:"Dolby Vision / SMPTE ST 2086",
HDR_Format_Version:"1.0",
HDR_Format_Profile:"dav1.10",
HDR_Format_Level:"08",
HDR_Format_Settings:"BL+RPU",
HDR_Format_Compatibility:"HDR10 / HDR10",
CodecID:"V_AV1",
Duration:"10708.947708333",
BitRate:"7156765",
Width:"3840",
Height:"2160",
Sampled_Width:"3840",
Sampled_Height:"2160",
PixelAspectRatio:"1.000",
DisplayAspectRatio:"1.778",
FrameRate_Mode:"CFR",
FrameRate:"23.976",
FrameRate_Num:"24000",
FrameRate_Den:"1001",
FrameCount:"256758",
ColorSpace:"YUV",
ChromaSubsampling:"4:2:0",
BitDepth:"10",
Delay:"0.042",
Delay_Source:"Container",
StreamSize:"9580178346",
Encoded_Library:"Lavc60.3.100 av1_qsv",
Default:"Yes",
Forced:"No",
colour_description_present:"Yes",
colour_description_present_Source:"Container / Stream",
colour_range:"Limited",
colour_range_Source:"Container / Stream",
colour_primaries:"BT.2020",
colour_primaries_Source:"Container / Stream",
transfer_characteristics:"PQ",
transfer_characteristics_Source:"Container / Stream",
matrix_coefficients:"BT.2020 non-constant",
matrix_coefficients_Source:"Container / Stream",
MasteringDisplay_ColorPrimaries:"BT.2020",
MasteringDisplay_ColorPrimaries_Source:"Container",
MasteringDisplay_Luminance:"min: 0.0001 cd/m2, max: 1000 cd/m2",
MasteringDisplay_Luminance_Source:"Container"

Will be happy for the HDR logic to be reviewed if needs be, (lines 616-650).

    if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') {
      // Check if codec of stream is mjpeg/png, if so then remove this "video" stream.
      // mjpeg/png are usually embedded pictures that can cause havoc with plugins.
      if (file.ffProbeData.streams[i].codec_name === 'mjpeg' || file.ffProbeData.streams[i].codec_name === 'png') {
        extraArguments += `-map -0:v:${videoIdx} `;
      } else { // Ensure to only do further checks if video stream is valid for use
        // Check for HDR in files. Attempt to use same color
        if ((file.ffProbeData.streams[i].color_space === 'bt2020nc'
          || file.ffProbeData.streams[i].color_space === 'bt2020n')
          && (file.ffProbeData.streams[i].color_transfer === 'smpte2084'
            || file.ffProbeData.streams[i].color_transfer === 'arib-std-b67')
          && file.ffProbeData.streams[i].color_primaries === 'bt2020') {
          response.infoLog += '==WARNING== This looks to be a HDR file. HDR is supported but '
            + 'correct encoding is not guaranteed.\n';
          extraArguments += `-color_primaries ${file.ffProbeData.streams[i].color_primaries} `
            + `-color_trc ${file.ffProbeData.streams[i].color_transfer} `
            + `-colorspace ${file.ffProbeData.streams[i].color_space} `;
          hdrEnabled = true;
        }

        // Had at least one case where a file contained no evident HDR data but was marked as HDR content
        // meaning transcode OR plex would butcher the file
        if (hdrEnabled !== true) {
          try {
            if (typeof file.mediaInfo.track[i + 1].HDR_Format !== 'undefined') {
              response.infoLog += '==ERROR== This file has Media data implying it is HDR '
                + `(${file.mediaInfo.track[i + 1].HDR_Format}), `
                + 'but no details about color space or primaries... '
                + 'Unable to convert and safely keep HDR data. Aborting!\n';
              return response;
            }
          } catch (err) {
            // Catch error - Ignore & carry on - If check can bomb out if tags don't exist...
          }
        }

Done more testing & confirmed, dolby vision files are normally not converted properly so we'll filter these out now.
The output usually doesn't have evident issues, it's only when tested on a Dolby vision TV that issues present & it's highly dependant on the source material.
i.e I think if the file has the correct HDR10 fallback info then it will play fine on a Dolby vision TV (just can't play Dovi proper), but a lot of files I've had don't have this info & after conversion the Dovi TV will display a pink blown out version.
It'll be better to just prevent Dovi conversions & I guess there's the question of why compress a Dovi file? I don't think it fits the work flow of a bitrate based re-encode.
@Boosh1
Copy link
Contributor Author

Boosh1 commented Oct 26, 2024

Good thing HDR came up as I've done more testing & confirmed, so actually Dolby vision files are an issue so we'll filter these out now. Standard HDR is certainly correct, but still need some more testing on HDR10.

So with Dolby vision, ffmpeg doesn't error but I don't think QSV supports native dolby vision encoding so dealing with those files will be tricky. The output will only show issue when tested on a Dolby vision TV.

I think if the source file has the correct HDR fallback info then after encode it will play fine on a Dolby vision TV (just can't play Dovi proper), but I realised a bunch of files I have don't have this info & after conversion the Dovi TV will display a pink blown out version.
The output file also still claims to be Dolby Vision & have the right data to be one, but isn't correct. Anyway it'll be better to just prevent Dovi conversions & I guess there's the question of why compress a Dovi file? I don't think it fits the work flow of a bitrate based re-encode.

Realised haven't accounted for HDR10+ which would also likely be affected by ffmpeg so we skip those too.
Also some tidy up on comments
Removed a map cmd that shouldn't be needed
@Boosh1
Copy link
Contributor Author

Boosh1 commented Dec 17, 2024

Also updated now to account for HDR10+ which I had missed. It should now skip those too.
My general advice for DV & HDR10+ is don't use those files with this plugin but they should be safely skipped now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants