Skip to content

Commit

Permalink
Merge pull request #12 from patrickenfuego/dynamic-metadata-updates
Browse files Browse the repository at this point in the history
RPU Editing & Dynamic Metadata Updates
  • Loading branch information
patrickenfuego authored Mar 25, 2023
2 parents 305e247 + aabe202 commit 1bb7a61
Show file tree
Hide file tree
Showing 21 changed files with 433 additions and 169 deletions.
90 changes: 50 additions & 40 deletions FFEncoder.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@
Skip Dolby Vision encoding, even if metadata is present
.PARAMETER SkipHDR10Plus
Skip HDR10+ encoding, even if metadata is present
.PARAMETER HDR10PlusSkipReorder
Fix for HDR10+ decoding order. Whether this parameter should be used must be validated manually
.PARAMETER ExitOnError
Converts certain non-terminating errors to terminating ones, such as input validation prompts. This can prevent blocking on automation when one
running instance encounters an error
Expand Down Expand Up @@ -657,6 +659,12 @@ param (
[alias('No10P', 'STP')]
[switch]$SkipHDR10Plus,

[Parameter(Mandatory = $false, ParameterSetName = 'CRF')]
[Parameter(Mandatory = $false, ParameterSetName = 'PASS')]
[Parameter(Mandatory = $false, ParameterSetName = 'QP')]
[alias('SkipReorder')]
[switch]$HDR10PlusSkipReorder,

[Parameter(Mandatory = $false, ParameterSetName = 'CRF')]
[Parameter(Mandatory = $false, ParameterSetName = 'PASS')]
[Parameter(Mandatory = $false, ParameterSetName = 'QP')]
Expand Down Expand Up @@ -1013,7 +1021,7 @@ if (!$PSBoundParameters['VapoursynthScript']) {
if ($Unsharp -notin $unsharpSet -and $Unsharp -notlike 'custom=*') {
$unsharpOptions = ($unsharpSet + 'custom=<filter_string>') |
Join-String -Separator "`r`n`t`u{2022} " `
-OutputPrefix "$($boldOn) Valid options for Unsharp$($boldOff):`n`t`u{2022} "
-OutputPrefix "$($boldOn) Valid options for Unsharp$($boldOff):`n`t`u{2022} "
Write-Host "Invalid option entered for Unsharp:`n$unsharpOptions"
$params = @{
Prompt = 'Enter a valid option: '
Expand Down Expand Up @@ -1149,51 +1157,53 @@ $audioArray = @($audioHash1, $audioHash2)
#>

$ffmpegParams = @{
Encoder = $Encoder
CropDimensions = $cropDim
AudioInput = $audioArray
Subtitles = $Subtitles
Preset = $Preset
RateControl = $rateControl
Deblock = $Deblock
Deinterlace = $Deinterlace
AqMode = $AqMode
AqStrength = $AqStrength
PsyRd = $PsyRd
PsyRdoq = $PsyRdoq
NoiseReduction = $NoiseReduction
NLMeans = $NLMeans
Unsharp = $unsharpHash
TuDepth = $TuDepth
LimitTu = $LimitTu
Tree = $Tree
Merange = $Merange
Ref = $Ref
Qcomp = $QComp
BFrames = $BFrames
BIntra = $BIntra
Subme = $Subme
IntraSmoothing = $StrongIntraSmoothing
Threads = $Threads
RCLookahead = $RCLookahead
Level = $Level
VBV = $VBV
FFMpegExtra = $FFMpegExtra
EncoderExtra = $EncoderExtra
Scale = $scaleHash
Paths = $paths
Verbose = $setVerbose
TestFrames = $TestFrames
TestStart = $TestStart
SkipDolbyVision = $SkipDolbyVision
SkipHDR10Plus = $SkipHDR10Plus
DisableProgress = $DisableProgress
Encoder = $Encoder
CropDimensions = $cropDim
AudioInput = $audioArray
Subtitles = $Subtitles
Preset = $Preset
RateControl = $rateControl
Deblock = $Deblock
Deinterlace = $Deinterlace
AqMode = $AqMode
AqStrength = $AqStrength
PsyRd = $PsyRd
PsyRdoq = $PsyRdoq
NoiseReduction = $NoiseReduction
NLMeans = $NLMeans
Unsharp = $unsharpHash
TuDepth = $TuDepth
LimitTu = $LimitTu
Tree = $Tree
Merange = $Merange
Ref = $Ref
Qcomp = $QComp
BFrames = $BFrames
BIntra = $BIntra
Subme = $Subme
IntraSmoothing = $StrongIntraSmoothing
Threads = $Threads
RCLookahead = $RCLookahead
Level = $Level
VBV = $VBV
FFMpegExtra = $FFMpegExtra
EncoderExtra = $EncoderExtra
Scale = $scaleHash
Paths = $paths
Verbose = $setVerbose
TestFrames = $TestFrames
TestStart = $TestStart
SkipDolbyVision = $SkipDolbyVision
SkipHDR10Plus = $SkipHDR10Plus
HDR10PlusSkipReorder = $HDR10PlusSkipReorder
DisableProgress = $DisableProgress
}

try {
Invoke-FFMpeg @ffmpegParams
}
catch {
$_.Exception | Select-Object *
$params = @{
Message = "An error occurred during ffmpeg invocation. Exception:`n$($_.Exception)"
RecommendedAction = 'Correct the Error Message'
Expand Down
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

# FFEncoder

FFEncoder is a cross-platform PowerShell script and module that is meant to make high definition video encoding workflows easier. FFEncoder uses [ffmpeg](https://ffmpeg.org/), [VapourSynth](https://www.vapoursynth.com/doc/), [Mkvtoolnix](https://mkvtoolnix.download/), [ffprobe](https://ffmpeg.org/ffprobe.html), the [x264 H.264 encoder](https://x264.org/en/), and the [x265 HEVC encoder](https://x265.readthedocs.io/en/master/index.html) to compress, filter, and multiplex multimedia files for streaming or archiving.
FFEncoder is a cross-platform PowerShell script and module that is meant to make high definition video encoding workflows easier. FFEncoder uses [ffmpeg](https://ffmpeg.org/), [VapourSynth](https://www.vapoursynth.com/doc/), [Mkvtoolnix](https://mkvtoolnix.download/), [ffprobe](https://ffmpeg.org/ffprobe.html), the [x264 H.264 encoder](https://x264.org/en/), and the [x265 H.265 HEVC encoder](https://x265.readthedocs.io/en/master/index.html) to compress, filter, and multiplex multimedia files for streaming or archiving.

Dynamic Metadata such as Dolby Vision and/or HDR10+ is fully supported.

---

- [FFEncoder](#ffencoder)
- [About](#about)
Expand Down Expand Up @@ -125,7 +129,10 @@ FFEncoder will automatically fetch and fill HDR metadata before encoding begins.
- Maximum Content Light Level
- Maximum Frame Average Light Level
- HDR10+ Metadata
- **WARNING**: Depending on the source, the metadata ordering may be incorrect after extraction. Evaluate the generated JSON file manually and use the `-HDR10PlusSkipReorder` parameter if necessary to correct this
- Read [the author's documentation](https://github.com/quietvoid/hdr10plus_tool) to learn why this parameter might be required and when to use it
- Dolby Vision Metadata
- Automatically edits the generated RPU file to ensure the metadata is accurate
- Requires `x265` (mods are fine) to be available via PATH because ffmpeg still doesn't handle RPU files correctly, even in version 5. If there is more than one `x265*` option in PATH, the first option returned is selected
- Currently, only profile 8.1 is supported due it it's backwards compatibility with HDR10
- It is recommended to have `mkvmerge`/`mkvextract` available. The script will multiplex tracks back together after encoding
Expand Down Expand Up @@ -232,15 +239,16 @@ FFEncoder can accept the following parameters from the command line:

### Encoder Config

| Parameter Name | Default | Mandatory | Alias | Description |
| --------------------- | ------------ | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Encoder** | x265 | False | **Enc** | Specifies which encoder to use - x264 or x265 |
| **FirstPassType** | Default | False | **PassType**, **FTP** | Tuning option for two pass encoding. See [Two Pass Encoding Options](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#two-pass-encoding-options) for more info |
| **SkipDolbyVision** | False | False | **NoDV**, **SDV** | Switch to disable Dolby Vision encoding, even if metadata is present |
| **SkipHDR10Plus** | False | False | **No10P**, **NTP** | Switch to disable HDR10+ encoding, even if metadata is present |
| **TestFrames** | 0 (Disabled) | False | **T**, **Test** | Integer value representing the number of test frames to encode. When `-TestStart` is not set, encoding starts at 00:01:30 so that title screens are skipped |
| **TestStart** | Disabled | False | **Start**, **TS** | Starting point for test encodes. Accepts formats `00:01:30` (sexagesimal time), `200f` (frame start), `200t` (decimal time in seconds) |
| **VapourSynthScript** | Disabled | False | **VSScript**, **VPY** | Path to VapourSynth script. Video filtering parameters are ignored when enabled, and must be done in the vpy script |
| Parameter Name | Default | Mandatory | Alias | Description |
| ------------------------ | ------------ | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Encoder** | x265 | False | **Enc** | Specifies which encoder to use - x264 or x265 |
| **FirstPassType** | Default | False | **PassType**, **FTP** | Tuning option for two pass encoding. See [Two Pass Encoding Options](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#two-pass-encoding-options) for more info |
| **SkipDolbyVision** | False | False | **NoDV**, **SDV** | Switch to disable Dolby Vision encoding, even if metadata is present |
| **SkipHDR10Plus** | False | False | **No10P**, **NTP** | Switch to disable HDR10+ encoding, even if metadata is present |
| **HDR10PlusSkipReorder** | False | False | **SkipReorder** | Switch to correct improper HDR10+ metadata ordering on some sources. **You must verify yourself if this is required or not** |
| **TestFrames** | 0 (Disabled) | False | **T**, **Test** | Integer value representing the number of test frames to encode. When `-TestStart` is not set, encoding starts at 00:01:30 so that title screens are skipped |
| **TestStart** | Disabled | False | **Start**, **TS** | Starting point for test encodes. Accepts formats `00:01:30` (sexagesimal time), `200f` (frame start), `200t` (decimal time in seconds) |
| **VapourSynthScript** | Disabled | False | **VSScript**, **VPY** | Path to VapourSynth script. Video filtering parameters are ignored when enabled, and must be done in the vpy script |

### Universal Encoder Settings

Expand Down
Binary file modified bin/linux/dovi_tool
Binary file not shown.
Binary file modified bin/linux/hdr10plus_tool
Binary file not shown.
Binary file modified bin/mac/dovi_tool
Binary file not shown.
Binary file modified bin/mac/hdr10plus_tool
Binary file not shown.
Binary file modified bin/windows/dovi_tool.exe
Binary file not shown.
Binary file modified bin/windows/hdr10plus_tool.exe
Binary file not shown.
4 changes: 2 additions & 2 deletions modules/FFTools/FFTools.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'FFTools.psm1'

# Version number of this module.
ModuleVersion = '2.0.0'
ModuleVersion = '2.4.0'

# Supported PSEditions
CompatiblePSEditions = 'Core'
Expand Down Expand Up @@ -91,7 +91,7 @@ AliasesToExport = 'iffmpeg', 'cropfile', 'cropdim'
FileList = 'FFTools.psd1', 'FFTools.psm1', 'Private\Set-AudioPreference.ps1', 'Private\Get-SubtitleStream', 'Private\Set-SubtitlePreference',
'Private\Get-HDRMetadata.ps1', 'Public\Invoke-FFMpeg.ps1', 'Public\Invoke-TwoPassFFMpeg.ps1', 'Public\New-CropFile.ps1', 'Public\Measure-CropDimensions.ps1', 'Public\Invoke-VMAF.ps1',
'Private\ConvertTo-Stereo.ps1', 'Private\Set-PresetParameters.ps1', 'Private\Set-FFMPegArgs.ps1', 'Private\Set-VideoFilter.ps1', 'Private\Set-TestParameters.ps1',
'Private\Watch-ScriptTerminated.ps1', 'Private\Confirm-Parameters.ps1', 'Private\Set-DVArgs.ps1',
'Private\Watch-ScriptTerminated.ps1', 'Private\Confirm-Parameters.ps1', 'Private\Set-DVArgs.ps1', 'Private\Edit-RPU.ps1', 'Private\Import-Config.ps1',
'Utils\Invoke-DeeEncoder.ps1','Utils\Confirm-ScaleFilter.ps1','Utils\Write-Report.ps1',
'Utils\Invoke-MkvMerge.ps1', 'Utils\Confirm-HDR10Plus.ps1', 'Utils\Confirm-DolbyVision.ps1', 'Utils\Remove-FilePrompt.ps1', 'Utils\Read-Config.ps1'

Expand Down
3 changes: 2 additions & 1 deletion modules/FFTools/FFTools.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ $Script:dee = @{
# Keep track of frame count for 2-pass encodes
$Script:frame = @{
FrameCount = 0
TestStart = 0
}

# Detect operating system info
Expand Down Expand Up @@ -137,7 +138,7 @@ ___________ .__ __ .__ ______________________
'@

# Current script release version
[version]$release = '2.3.0'
[version]$release = '2.4.0'


#### End module variables ####
Expand Down
119 changes: 119 additions & 0 deletions modules/FFTools/Private/Edit-RPU.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using namespace System.IO

<#
.SYNOPSIS
Edit Dolby Vision RPU metadata
.DESCRIPTION
Edits the extracted RPU file to ensure metadata properties are set correctly.
.PARAMETER Paths
<hashtable> Paths to files used throughout the script
.PARAMETER CropDimensions
<int[]> Cropping dimensions used to generate black bar offsets
.PARAMETER HDRMetadata
<hashtable> HDR metadata extracted from source file
#>
function Edit-RPU {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[hashtable]$Paths,

[Parameter(Mandatory = $true)]
[array]$CropDimensions,

[Parameter(Mandatory = $true)]
[hashtable]$HDRMetadata
)

$edited = $Paths.DvPath.replace('.bin', '_edited.bin')
if ([File]::Exists($edited)) {
Write-Verbose "Existing edited RPU file found"
return
}

Write-Host "Editing RPU file..." @emphasisColors -NoNewline

$outJson = Join-Path (Split-Path $Paths.InputFile -Parent) -ChildPath 'rpu_edit.json'

$srcWidth, $srcHeight = switch ($CropDimensions[0]) {
{ $_ -gt 1920 } { 3840, 2160 }
{ $_ -gt 1280 -and $_ -lt 3000 } { 1920, 1080 }
}

$canvasTop = $canvasBottom = ($srcHeight - $CropDimensions[1]) / 2
$canvasLeft = $CanvasRight = ($srcWidth - $CropDimensions[0]) / 2

# Lookup table for common pq values
$pqLookup = @{
1 = 7
50 = 62
10000000 = 3079
40000000 = 3696
}

$presets = @(
[ordered]@{
'id' = 0
'left' = $canvasLeft
'right' = $CanvasRight
'top' = $canvasTop
'bottom' = $canvasBottom
}
)


$minPq = $pqLookup[$HDRMetadata['MinLuma']]
$maxPq = $pqLookup[$HDRMetadata['MaxLuma']]

$level6 = [Ordered]@{
'max_display_mastering_luminance' = $HDRMetadata['MaxLuma'] / 10000
'min_display_mastering_luminance' = $HDRMetadata['MinLuma']
'max_content_light_level' = $HDRMetadata['MaxCLL']
'max_frame_average_light_level' = $HDRMetadata['MaxFAL']
}

$metadata = [pscustomobject]@{
'mode' = 2
'min_pq' = $minPq
'max_pq' = $maxPq
'active_area' = [Ordered]@{
'crop' = $true
'presets' = $presets
}
'level6' = $level6
}

ConvertTo-Json -InputObject $metadata -Depth 4 |
Out-File $outJson

if ($IsLinux -or $IsMacOS) {
$parserPath = $IsLinux ?
([Path]::Join((Get-Item $PSScriptRoot).Parent.Parent.Parent, "bin/linux/dovi_tool")) :
([Path]::Join((Get-Item $PSScriptRoot).Parent.Parent.Parent, "bin/mac/dovi_tool"))

$parserPath, $edited, $outJson, $dv = ($parserPath, $edited, $outJson, $Paths.DvPath).ForEach({ [regex]::Escape($_) })
$outEdit = bash -c "$parserPath editor -i $dv -j $outJson -o $edited"
}
else {
$parserPath = [Path]::Join((Get-Item $PSScriptRoot).Parent.Parent.Parent, "bin\windows\dovi_tool.exe")
$outEdit = cmd.exe /c "`"$parserPath`" editor -i `"$($Paths.DvPath)`" -j `"$outJson`" -o `"$edited`""
}

Write-Verbose "`n$($outEdit -join "`n")"

if ((Test-Path $edited)) {
$editLen = (Get-Item $edited).Length
$srcLen = (Get-Item $Paths.DvPath).Length
if ((($srcLen - $editLen) / 1MB ) -lt 2) {
Write-Host "Great Success!`n" @progressColors
Remove-Item $Paths.DvPath
$Paths.DvPath = $edited
}
else {
Write-Host "Edited RPU file was generated, but is smaller than expected. Investigate manually`n" @warnColors
}
}
else {
Write-Host "Edited RPU file was not found and won't be used`n" @warnColors
}
}
Loading

0 comments on commit 1bb7a61

Please sign in to comment.