Автоматична генерація скріншотів (+HDR). Скрипт для PowerShell

Нові коментарі

Нова тема   Відповісти
Автор Повідомлення
4br4h4m 
VIP


З нами з: 29.04.22
Востаннє: 28.06.25
Повідомлень: 479

2023-09-11 02:04  
Вітаю.
Ще раніше помічав, що у релізах з HDR люди часто роблять не зовсім коректні скріншоти через особливості "глибини кольору".

https://toloka.to/t672390
https://toloka.to/t670763
https://toloka.to/t670199

В мене були аналогічні проблеми з PotPlayer, тому шукав альтернативу, бажано взагалі напівавтоматизовану.

В результаті знайшовся такий скрипт:
https://gist.github.com/lambdan/70a36e53b90ebbff97bc9e73b2fa4414

Трішки його підправив і вирішив викласти.
Шляхи до Info.exe (CLI) і ffmpeg.exe вкажіть свої.
Скачати останні офіційні версії можна тут
зберегти як hdr.ps1 і запускати так:
powershell -ExecutionPolicy Bypass -File d:\hdr.ps1 "The.Hurt.Locker.2008..4K.2160p.x265.10bit.HDR10+.Ukr.Eng.Sub.Eng."

Можна задати параметр how_many фіксованим значенням.
PowerShell скрипт

# For batch:
# foreach($f in Get-ChildItem .) { .\hdr.ps1 $f }

$infile = $Args[0] # Input video

# Get some video length info using Info
$Info = "d:\Portable\FFMpeg\MediaInfo_CLI_23.07_Windows_x64\Info.exe"
$ffmpeg = "d:\Portable\FFMpeg\ffmpeg-2023-09-07-git-9c9f48e7f2-full_build\bin\ffmpeg.exe"
#$length_in_ms = $Info --Output='General;%Duration%' "$infile" | Out-String
$length_in_ms = & $Info --Language=raw --Full --Inform='Video;%Duration%' "$infile" | Out-String
#Info";%Duration/String4%"
$length_in_secs = $length_in_ms/1000

# Create output folder
$outfolder = "./screenshots/"
If(!(test-path $outfolder)) {
md $outfolder
}

# Create this many screenshots, or base it on video length
#$how_many = 20 # maybe make this the 2nd arg in the future?
$how_many = [math]::Round($length_in_secs/100)
Write-Host "Will create $how_many screenshots"

$used_seconds = @()
$i = 1
while($i -le $how_many) {
# Get a random second of the video that hasnt been already used
$random_sec = get-random -maximum $length_in_secs
$rounded = [math]::Round($random_sec)
if ($used_seconds.Contains($rounded) -eq $True) {
while ($used_seconds.Contains($rounded) -eq $True) {
#Write-Host $rounded "already used"
#Write-Host $used_seconds
$random_sec = get-random -maximum $length_in_secs
$rounded = [math]::Round($random_sec)
}
#Write-Host "Found unused" $rounded
}
$used_seconds += $rounded

# Outfile that screenshot will be saved as
$outfile = $outfolder + $infile + "_" + $rounded + ".png"

Write-Host $i/$how_many $outfile
#$result = & $ffmpeg -loglevel error -ss $rounded -i "$infile" -frames:v 1 $outfile | Out-String
& $ffmpeg -loglevel error -ss $rounded -i "$infile" -vf zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p -frames:v 1 $outfile

$i++
}
Результат використання скрипта можна побачити тут:
https://toloka.to/t672395

Для не HDR прибрати оце:
Цитата:
-vf zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p


Використана інфа:
https://stackoverflow.com/questions/70544342/ffmpeg-why-is-output-video-contrast-brightness-too-bright
https://stackoverflow.com/questions/19091771/how-to-find-duration-of-a-video-file-using-Info-in-seconds-or-other-formats
4br4h4m 
VIP


З нами з: 29.04.22
Востаннє: 28.06.25
Повідомлень: 479

2025-04-26 13:54  
HDR для нової версії FFMPEG (7+):
PowerShell

# Requires 'ffmpeg' and 'Info' in path

# For batch:
# foreach($f in Get-ChildItem .) { .\random_screenshots.ps1 $f }

$infile = $Args[0] # Input video

# Get some video length info using Info
$Info = "d:\FFMpeg\MediaInfo_CLI_23.07_Windows_x64\Info.exe"
$ffmpeg = "d:\FFMpeg\ffmpeg-7.1.1-full_build\bin\ffmpeg.exe"
#$length_in_ms = $Info --Output='General;%Duration%' "$infile" | Out-String
$length_in_ms = & $Info --Language=raw --Full --Inform='Video;%Duration%' "$infile" | Out-String
#Info";%Duration/String4%"
$length_in_secs = $length_in_ms/1000

# Create output folder
$outfolder = "./screenshots/"
If(!(test-path $outfolder)) {
md $outfolder
}

# Create this many screenshots, or base it on video length
$how_many = 10 # maybe make this the 2nd arg in the future?
#$how_many = [math]::Round($length_in_secs/100)
Write-Host "Will create $how_many screenshots"

$used_seconds = @()
$i = 1
while($i -le $how_many) {
# Get a random second of the video that hasnt been already used
$random_sec = get-random -maximum $length_in_secs
$rounded = [math]::Round($random_sec)
if ($used_seconds.Contains($rounded) -eq $True) {
while ($used_seconds.Contains($rounded) -eq $True) {
#Write-Host $rounded "already used"
#Write-Host $used_seconds
$random_sec = get-random -maximum $length_in_secs
$rounded = [math]::Round($random_sec)
}
#Write-Host "Found unused" $rounded
}
$used_seconds += $rounded

# Outfile that screenshot will be saved as
$outfile = $outfolder + $infile + "_" + $rounded + ".png"

Write-Host $i/$how_many $outfile
#$result = & $ffmpeg -loglevel error -ss $rounded -i "$infile" -frames:v 1 $outfile | Out-String
#& $ffmpeg -loglevel error -ss $rounded -i "$infile" -vf zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p -frames:v 1 $outfile

& $ffmpeg -loglevel error -ss $rounded -i "$infile" `
-vf "zscale=transfer=linear:npl=100,format=gbrpf32le,zscale=primaries=bt709,tonemap=tonemap=hable:desat=0,zscale=transfer=bt709:matrix=bt709:range=tv,format=yuv420p" `
-frames:v 1 $outfile

$i++
}
gorvic 
VIP


З нами з: 27.11.21
Востаннє: 30.06.25
Повідомлень: 894

2025-06-15 13:50  
4br4h4m написано:
HDR для...
Код:
tonemap=tonemap=hable:desat=0


Як я вже писав в одному каналі (із порівнянням результьатів), ось таке рекодування у SDR приводить до лайнових результатів. Потрібно використовувати інший метод, і тоді результат буде ідентичний оригіналу.
Код:
tonemap=tonemap=reinhard:desat=2


Додано через 19 хвилин 30 секунд:

І ще одне, використовувати такий метод для створення скріншотів - це звичайно збочення якесь, але робити по одному скріншоту в циклі - це взагалі треба бути ще тим нубом:

ось приблизно як це можна оптимізувати

Код:
# ігноруємо якийсь проміжок, щоб не скрінило заставку та початкові титри
-ss 00:03:00
# кажемо, що будемо робити скрін приблизно через кожну хвилину (або можна поставити своє)
-vf fps=0.01
# кількість скрінів, які треба зробити
-frames:v 8
# шлях та назва картинки. Де %03d буде замінено на порядковий номер
-y "screenshot_name_%03d.png"
gorvic 
VIP


З нами з: 27.11.21
Востаннє: 30.06.25
Повідомлень: 894

2025-06-28 01:26  
Я тут трішки почитав код для FFmpeg pngenc і побачив деякі приховані параметри, які можна використати

1. "-compression_level 0...9" (0-без стискання, 9 максимальна компрессія, по замовчування використовується 1)
2. попередній параметр має сенс тільки при використанні "-pred mixed"
3. бітність png файлу: rgb24=8біт, rgba=8біт+альфа канал, rgb48be=16біт, rgba64be=16біт+альфа канал (по замовчуванні)

все це напряму залежить від розміру вихідного скріншоту, то ж я б запропонував зробити десь такі батніки
make_sdr_screenshots.bat
Код:
@ECHO OFF

set scr_count=8
set compr_level=7

ffmpeg  -hide_banner -loglevel error -stats -i "%~1" -ss 00:03:00 -vf "fps=0.01,format=yuv420p" -pix_fmt:v rgb24 -pred mixed -compression_level %compr_level% -frames:v %scr_count% -y "%~dpn1_%%03d.png"
make_hdr_screenshots.bat
Код:
@ECHO OFF

set scr_count=8
set compr_level=7

ffmpeg  -hide_banner -loglevel error -stats -i "%~1" -ss 00:03:00 -vf "fps=0.01,zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard:desat=2,zscale=t=bt709:m=bt709:r=tv,format=yuv420p" -pix_fmt:v rgb24 -pred mixed -compression_level %compr_level% -frames:v %scr_count% -y "%~dpn1_%%03d.png"
і або кидати на них відразу файл фільму, або викликати з параметром

Код:
make_sdr_screenshots.bat "sdr_movie."
make_hdr_screenshots.bat "hdr_movie."


ps Можна зробити виклик всередені ще Info й самому вирішувати, HDR то, чи ні
Info
Код:

.......
set media_info=PathToMediaInfo
call :CHECK_HDR "%~1"

if %video_hdr%==false (
   ...
) else (
   ...
)
EXIT /b 0

:CHECK_HDR
set video_hdr=false
for /f "delims=" %%i in ('CALL %media_info% --Output^=Video^;^"%%HDR_Format/String%%\n^" "%~1"') do (
    for /f "tokens=1 delims=" %%k in ("%%i") do (
        if not "%%k"=="" (
            echo "%%k" | findstr "HDR" > nul 2>&1 && set video_hdr=true || set video_hdr=false
        )
    )
    EXIT /b 0
)
EXIT /b 0
Але то зайве, як для такого простого прикладу
gorvic 
VIP


З нами з: 27.11.21
Востаннє: 30.06.25
Повідомлень: 894

2025-06-29 00:41  
Вся ця ідея перетворилась у повноцінний скрипт, який використовує (якщо збірка ffmpeg дозволяє) метод трансформації, що задіяний у mpv player

https://gist.github.com/vikthedev/40417c75426670000c5e4bc5af2783e4
maksymmandzyk 
Частий відвідувач


З нами з: 11.08.18
Востаннє: 30.06.25
Повідомлень: 47

2025-06-29 20:05  
gorvic, Можливо я чогось не розумію, але навіщо взагалі самому тонпамити HDR скріншоти в SDR?
gorvic написано:
приховані параметри

і не зрозумів в якому місці вони настільки приховані, що потрібно лізти в код https://ffmpeg.org/ffmpeg-all.html#Options-48
gorvic 
VIP


З нами з: 27.11.21
Востаннє: 30.06.25
Повідомлень: 894

2025-06-30 00:56  
maksymmandzyk написано:
і не зрозумів в якому місці вони настільки приховані, що потрібно лізти в код

може в мані десь розписані формати, в яких можна зберігати пнг і що коден із них означає? чи вказано, при яких налаштуваннях запрацює compression_level і що цей левел означає? Чи може ви навіть на ману будете дивитись і повірите, що 9ка у compression_level - це по замовчуванню, а зовсім не 1?

В код завжди потрібно лізти, особливо, коли мана й на десяту частину не покриває можливостей і у ній стільки розбіжностей з кодом Wink (1)

ps А з приводу того, що і навіщо - ну так не я це почав, просто не спалось, ось і порозбирався з різними тонмапами, бо те, що тут пропонувалось зовсім далеке від реальності.
maksymmandzyk 
Частий відвідувач


З нами з: 11.08.18
Востаннє: 30.06.25
Повідомлень: 47

2025-06-30 13:05  
gorvic написано:
може в мані десь розписані формати, в яких можна зберігати пнг і що коден із них означає?

Ні, дійсно, хіба дивитись в код 🤷‍♂️

gorvic написано:
чи вказано, при яких налаштуваннях запрацює compression_level і що цей левел означає? Чи може ви навіть на ману будете дивитись і повірите, що 9ка у compression_level - це по замовчуванню, а зовсім не 1?

А тут вже брешете) compression_level за замовчуванням все таки 6, а не 1, документація дійсно невірна, так само з -pred, за замовчуванням стоїть none, а не paeth.

gorvic написано:
rgba64be=16біт+альфа канал (по замовчуванні)

І тут у вас якісь дивні налаштування за замовчуванням, за замовчуванням 8 біт відео зберігаються в 8 біт пнг без альфа каналу, і 10/12 біт відео в 16 біт пнг без альфа каналу.

Загалом стискати скріншоти прямо через ffmpeg неефективно, для цього краще окремо oxipng використовувати.
Час на один 4к 16 біт HDR скріншот на не дуже сучасній системі:
-pred mixed -compression_level 7: 9s,34.6MiB(те що ви використовуєте в скрипті)
-pred none -compression_level 0: 3s,48.7MiB + oxipng -o 2: 8s, 27.5MiB(те що я трошки потикав, і це найкраще співвідношення часу/розміру для 16 біт HDR, для 8 біт SDR достатньо і oxipng -o 1)

gorvic написано:
І ще одне, використовувати такий метод для створення скріншотів - це звичайно збочення якесь, але робити по одному скріншоту в циклі - це взагалі треба бути ще тим нубом:
ось приблизно як це можна оптимізувати

Воно то може і виглядає "по нубськи", але якщо в циклі використовується саме Input Seeking, то це буде набагато швидше, ніж ставити щось типу -vf fps=0.01.

gorvic написано:
ps А з приводу того, що і навіщо - ну так не я це почав, просто не спалось, ось і порозбирався з різними тонмапами, бо те, що тут пропонувалось зовсім далеке від реальності.

Зрозумів, ну загалом мабуть варто просто уникати будь-якого тонмапу, щоб не було таких проблем, і були справжні HDR скріншоти
Ваш часовий пояс: GMT + 2 Години

Нова тема   Відповісти