Skip to content

Commit 1bb7a61

Browse files
Merge pull request #12 from patrickenfuego/dynamic-metadata-updates
RPU Editing & Dynamic Metadata Updates
2 parents 305e247 + aabe202 commit 1bb7a61

21 files changed

+433
-169
lines changed

FFEncoder.ps1

+50-40
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@
218218
Skip Dolby Vision encoding, even if metadata is present
219219
.PARAMETER SkipHDR10Plus
220220
Skip HDR10+ encoding, even if metadata is present
221+
.PARAMETER HDR10PlusSkipReorder
222+
Fix for HDR10+ decoding order. Whether this parameter should be used must be validated manually
221223
.PARAMETER ExitOnError
222224
Converts certain non-terminating errors to terminating ones, such as input validation prompts. This can prevent blocking on automation when one
223225
running instance encounters an error
@@ -657,6 +659,12 @@ param (
657659
[alias('No10P', 'STP')]
658660
[switch]$SkipHDR10Plus,
659661

662+
[Parameter(Mandatory = $false, ParameterSetName = 'CRF')]
663+
[Parameter(Mandatory = $false, ParameterSetName = 'PASS')]
664+
[Parameter(Mandatory = $false, ParameterSetName = 'QP')]
665+
[alias('SkipReorder')]
666+
[switch]$HDR10PlusSkipReorder,
667+
660668
[Parameter(Mandatory = $false, ParameterSetName = 'CRF')]
661669
[Parameter(Mandatory = $false, ParameterSetName = 'PASS')]
662670
[Parameter(Mandatory = $false, ParameterSetName = 'QP')]
@@ -1013,7 +1021,7 @@ if (!$PSBoundParameters['VapoursynthScript']) {
10131021
if ($Unsharp -notin $unsharpSet -and $Unsharp -notlike 'custom=*') {
10141022
$unsharpOptions = ($unsharpSet + 'custom=<filter_string>') |
10151023
Join-String -Separator "`r`n`t`u{2022} " `
1016-
-OutputPrefix "$($boldOn) Valid options for Unsharp$($boldOff):`n`t`u{2022} "
1024+
-OutputPrefix "$($boldOn) Valid options for Unsharp$($boldOff):`n`t`u{2022} "
10171025
Write-Host "Invalid option entered for Unsharp:`n$unsharpOptions"
10181026
$params = @{
10191027
Prompt = 'Enter a valid option: '
@@ -1149,51 +1157,53 @@ $audioArray = @($audioHash1, $audioHash2)
11491157
#>
11501158

11511159
$ffmpegParams = @{
1152-
Encoder = $Encoder
1153-
CropDimensions = $cropDim
1154-
AudioInput = $audioArray
1155-
Subtitles = $Subtitles
1156-
Preset = $Preset
1157-
RateControl = $rateControl
1158-
Deblock = $Deblock
1159-
Deinterlace = $Deinterlace
1160-
AqMode = $AqMode
1161-
AqStrength = $AqStrength
1162-
PsyRd = $PsyRd
1163-
PsyRdoq = $PsyRdoq
1164-
NoiseReduction = $NoiseReduction
1165-
NLMeans = $NLMeans
1166-
Unsharp = $unsharpHash
1167-
TuDepth = $TuDepth
1168-
LimitTu = $LimitTu
1169-
Tree = $Tree
1170-
Merange = $Merange
1171-
Ref = $Ref
1172-
Qcomp = $QComp
1173-
BFrames = $BFrames
1174-
BIntra = $BIntra
1175-
Subme = $Subme
1176-
IntraSmoothing = $StrongIntraSmoothing
1177-
Threads = $Threads
1178-
RCLookahead = $RCLookahead
1179-
Level = $Level
1180-
VBV = $VBV
1181-
FFMpegExtra = $FFMpegExtra
1182-
EncoderExtra = $EncoderExtra
1183-
Scale = $scaleHash
1184-
Paths = $paths
1185-
Verbose = $setVerbose
1186-
TestFrames = $TestFrames
1187-
TestStart = $TestStart
1188-
SkipDolbyVision = $SkipDolbyVision
1189-
SkipHDR10Plus = $SkipHDR10Plus
1190-
DisableProgress = $DisableProgress
1160+
Encoder = $Encoder
1161+
CropDimensions = $cropDim
1162+
AudioInput = $audioArray
1163+
Subtitles = $Subtitles
1164+
Preset = $Preset
1165+
RateControl = $rateControl
1166+
Deblock = $Deblock
1167+
Deinterlace = $Deinterlace
1168+
AqMode = $AqMode
1169+
AqStrength = $AqStrength
1170+
PsyRd = $PsyRd
1171+
PsyRdoq = $PsyRdoq
1172+
NoiseReduction = $NoiseReduction
1173+
NLMeans = $NLMeans
1174+
Unsharp = $unsharpHash
1175+
TuDepth = $TuDepth
1176+
LimitTu = $LimitTu
1177+
Tree = $Tree
1178+
Merange = $Merange
1179+
Ref = $Ref
1180+
Qcomp = $QComp
1181+
BFrames = $BFrames
1182+
BIntra = $BIntra
1183+
Subme = $Subme
1184+
IntraSmoothing = $StrongIntraSmoothing
1185+
Threads = $Threads
1186+
RCLookahead = $RCLookahead
1187+
Level = $Level
1188+
VBV = $VBV
1189+
FFMpegExtra = $FFMpegExtra
1190+
EncoderExtra = $EncoderExtra
1191+
Scale = $scaleHash
1192+
Paths = $paths
1193+
Verbose = $setVerbose
1194+
TestFrames = $TestFrames
1195+
TestStart = $TestStart
1196+
SkipDolbyVision = $SkipDolbyVision
1197+
SkipHDR10Plus = $SkipHDR10Plus
1198+
HDR10PlusSkipReorder = $HDR10PlusSkipReorder
1199+
DisableProgress = $DisableProgress
11911200
}
11921201

11931202
try {
11941203
Invoke-FFMpeg @ffmpegParams
11951204
}
11961205
catch {
1206+
$_.Exception | Select-Object *
11971207
$params = @{
11981208
Message = "An error occurred during ffmpeg invocation. Exception:`n$($_.Exception)"
11991209
RecommendedAction = 'Correct the Error Message'

README.md

+18-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
# FFEncoder
1010

11-
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.
11+
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.
12+
13+
Dynamic Metadata such as Dolby Vision and/or HDR10+ is fully supported.
14+
15+
---
1216

1317
- [FFEncoder](#ffencoder)
1418
- [About](#about)
@@ -125,7 +129,10 @@ FFEncoder will automatically fetch and fill HDR metadata before encoding begins.
125129
- Maximum Content Light Level
126130
- Maximum Frame Average Light Level
127131
- HDR10+ Metadata
132+
- **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
133+
- Read [the author's documentation](https://github.com/quietvoid/hdr10plus_tool) to learn why this parameter might be required and when to use it
128134
- Dolby Vision Metadata
135+
- Automatically edits the generated RPU file to ensure the metadata is accurate
129136
- 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
130137
- Currently, only profile 8.1 is supported due it it's backwards compatibility with HDR10
131138
- It is recommended to have `mkvmerge`/`mkvextract` available. The script will multiplex tracks back together after encoding
@@ -232,15 +239,16 @@ FFEncoder can accept the following parameters from the command line:
232239

233240
### Encoder Config
234241

235-
| Parameter Name | Default | Mandatory | Alias | Description |
236-
| --------------------- | ------------ | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
237-
| **Encoder** | x265 | False | **Enc** | Specifies which encoder to use - x264 or x265 |
238-
| **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 |
239-
| **SkipDolbyVision** | False | False | **NoDV**, **SDV** | Switch to disable Dolby Vision encoding, even if metadata is present |
240-
| **SkipHDR10Plus** | False | False | **No10P**, **NTP** | Switch to disable HDR10+ encoding, even if metadata is present |
241-
| **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 |
242-
| **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) |
243-
| **VapourSynthScript** | Disabled | False | **VSScript**, **VPY** | Path to VapourSynth script. Video filtering parameters are ignored when enabled, and must be done in the vpy script |
242+
| Parameter Name | Default | Mandatory | Alias | Description |
243+
| ------------------------ | ------------ | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
244+
| **Encoder** | x265 | False | **Enc** | Specifies which encoder to use - x264 or x265 |
245+
| **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 |
246+
| **SkipDolbyVision** | False | False | **NoDV**, **SDV** | Switch to disable Dolby Vision encoding, even if metadata is present |
247+
| **SkipHDR10Plus** | False | False | **No10P**, **NTP** | Switch to disable HDR10+ encoding, even if metadata is present |
248+
| **HDR10PlusSkipReorder** | False | False | **SkipReorder** | Switch to correct improper HDR10+ metadata ordering on some sources. **You must verify yourself if this is required or not** |
249+
| **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 |
250+
| **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) |
251+
| **VapourSynthScript** | Disabled | False | **VSScript**, **VPY** | Path to VapourSynth script. Video filtering parameters are ignored when enabled, and must be done in the vpy script |
244252

245253
### Universal Encoder Settings
246254

bin/linux/dovi_tool

808 KB
Binary file not shown.

bin/linux/hdr10plus_tool

916 KB
Binary file not shown.

bin/mac/dovi_tool

3.56 MB
Binary file not shown.

bin/mac/hdr10plus_tool

2.87 MB
Binary file not shown.

bin/windows/dovi_tool.exe

278 KB
Binary file not shown.

bin/windows/hdr10plus_tool.exe

404 KB
Binary file not shown.

modules/FFTools/FFTools.psd1

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'FFTools.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '2.0.0'
15+
ModuleVersion = '2.4.0'
1616

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

modules/FFTools/FFTools.psm1

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ $Script:dee = @{
6969
# Keep track of frame count for 2-pass encodes
7070
$Script:frame = @{
7171
FrameCount = 0
72+
TestStart = 0
7273
}
7374

7475
# Detect operating system info
@@ -137,7 +138,7 @@ ___________ .__ __ .__ ______________________
137138
'@
138139

139140
# Current script release version
140-
[version]$release = '2.3.0'
141+
[version]$release = '2.4.0'
141142

142143

143144
#### End module variables ####

modules/FFTools/Private/Edit-RPU.ps1

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
using namespace System.IO
2+
3+
<#
4+
.SYNOPSIS
5+
Edit Dolby Vision RPU metadata
6+
.DESCRIPTION
7+
Edits the extracted RPU file to ensure metadata properties are set correctly.
8+
.PARAMETER Paths
9+
<hashtable> Paths to files used throughout the script
10+
.PARAMETER CropDimensions
11+
<int[]> Cropping dimensions used to generate black bar offsets
12+
.PARAMETER HDRMetadata
13+
<hashtable> HDR metadata extracted from source file
14+
#>
15+
function Edit-RPU {
16+
[CmdletBinding()]
17+
param (
18+
[Parameter(Mandatory = $true)]
19+
[hashtable]$Paths,
20+
21+
[Parameter(Mandatory = $true)]
22+
[array]$CropDimensions,
23+
24+
[Parameter(Mandatory = $true)]
25+
[hashtable]$HDRMetadata
26+
)
27+
28+
$edited = $Paths.DvPath.replace('.bin', '_edited.bin')
29+
if ([File]::Exists($edited)) {
30+
Write-Verbose "Existing edited RPU file found"
31+
return
32+
}
33+
34+
Write-Host "Editing RPU file..." @emphasisColors -NoNewline
35+
36+
$outJson = Join-Path (Split-Path $Paths.InputFile -Parent) -ChildPath 'rpu_edit.json'
37+
38+
$srcWidth, $srcHeight = switch ($CropDimensions[0]) {
39+
{ $_ -gt 1920 } { 3840, 2160 }
40+
{ $_ -gt 1280 -and $_ -lt 3000 } { 1920, 1080 }
41+
}
42+
43+
$canvasTop = $canvasBottom = ($srcHeight - $CropDimensions[1]) / 2
44+
$canvasLeft = $CanvasRight = ($srcWidth - $CropDimensions[0]) / 2
45+
46+
# Lookup table for common pq values
47+
$pqLookup = @{
48+
1 = 7
49+
50 = 62
50+
10000000 = 3079
51+
40000000 = 3696
52+
}
53+
54+
$presets = @(
55+
[ordered]@{
56+
'id' = 0
57+
'left' = $canvasLeft
58+
'right' = $CanvasRight
59+
'top' = $canvasTop
60+
'bottom' = $canvasBottom
61+
}
62+
)
63+
64+
65+
$minPq = $pqLookup[$HDRMetadata['MinLuma']]
66+
$maxPq = $pqLookup[$HDRMetadata['MaxLuma']]
67+
68+
$level6 = [Ordered]@{
69+
'max_display_mastering_luminance' = $HDRMetadata['MaxLuma'] / 10000
70+
'min_display_mastering_luminance' = $HDRMetadata['MinLuma']
71+
'max_content_light_level' = $HDRMetadata['MaxCLL']
72+
'max_frame_average_light_level' = $HDRMetadata['MaxFAL']
73+
}
74+
75+
$metadata = [pscustomobject]@{
76+
'mode' = 2
77+
'min_pq' = $minPq
78+
'max_pq' = $maxPq
79+
'active_area' = [Ordered]@{
80+
'crop' = $true
81+
'presets' = $presets
82+
}
83+
'level6' = $level6
84+
}
85+
86+
ConvertTo-Json -InputObject $metadata -Depth 4 |
87+
Out-File $outJson
88+
89+
if ($IsLinux -or $IsMacOS) {
90+
$parserPath = $IsLinux ?
91+
([Path]::Join((Get-Item $PSScriptRoot).Parent.Parent.Parent, "bin/linux/dovi_tool")) :
92+
([Path]::Join((Get-Item $PSScriptRoot).Parent.Parent.Parent, "bin/mac/dovi_tool"))
93+
94+
$parserPath, $edited, $outJson, $dv = ($parserPath, $edited, $outJson, $Paths.DvPath).ForEach({ [regex]::Escape($_) })
95+
$outEdit = bash -c "$parserPath editor -i $dv -j $outJson -o $edited"
96+
}
97+
else {
98+
$parserPath = [Path]::Join((Get-Item $PSScriptRoot).Parent.Parent.Parent, "bin\windows\dovi_tool.exe")
99+
$outEdit = cmd.exe /c "`"$parserPath`" editor -i `"$($Paths.DvPath)`" -j `"$outJson`" -o `"$edited`""
100+
}
101+
102+
Write-Verbose "`n$($outEdit -join "`n")"
103+
104+
if ((Test-Path $edited)) {
105+
$editLen = (Get-Item $edited).Length
106+
$srcLen = (Get-Item $Paths.DvPath).Length
107+
if ((($srcLen - $editLen) / 1MB ) -lt 2) {
108+
Write-Host "Great Success!`n" @progressColors
109+
Remove-Item $Paths.DvPath
110+
$Paths.DvPath = $edited
111+
}
112+
else {
113+
Write-Host "Edited RPU file was generated, but is smaller than expected. Investigate manually`n" @warnColors
114+
}
115+
}
116+
else {
117+
Write-Host "Edited RPU file was not found and won't be used`n" @warnColors
118+
}
119+
}

0 commit comments

Comments
 (0)