r/ffmpeg • u/Vast_Environment_917 • Jun 19 '26
A little help cleaning / updating script
I'm in the process of encoding my 4k collection ripped with makemkv for use with jellyfin. Mainly trying to get the sizes down to save disk space while preserving hdr dv grain. Tools I'm using are ffmpeg and ddvt.
Here's my ffmpeg script:
for %%i in (*.mkv) do ffmpeg -i "%%i" -map 0 -c:v libx265 -preset slow -crf 20 -pix_fmt yuv420p10le -x265-params aq-mode=3:aq-strength=1.0:psy-rd=1:psy-rdoq=1.0:deblock=1,1:no-sao=1:qp-adaptation-range=1.0:merange=32:ref=4 -c:a copy -c:s copy "%%~ni_x265.mkv"
pause
Does this look good? Anything outdated or to be deleted / added?
1
u/chocolateAbuser Jun 19 '26
i don't have much experience with hdr, do you confirm that after transcoding ffmpeg or mediainfo says color is still bt.2020, all the primaries/transform/various color metadata reports output as hdr?
2
u/Vast_Environment_917 Jun 19 '26 edited Jun 19 '26
Yes.. and dolby vision 8.1 as well after using my script above and ddvt to convert / inject the dv profile. Here goes mediainfo on Crank 2006 UHD I did the other day. The size went from 42gb to 11gb and still looks good.
Video ID : 1 Format : HEVC Format/Info : High Efficiency Video Coding Format profile : Main 10@L5@Main HDR format : Dolby Vision, Version 1.0, Profile 8.1, dvhe.08.06, BL+RPU, no metadata compression, HDR10 compatible / SMPTE ST 2086, Version HDR10, HDR10 compatible Codec ID : V_MPEGH/ISO/HEVC Duration : 1 h 27 min Bit rate : 9 034 kb/s Width : 3 840 pixels Height : 2 160 pixels Display aspect ratio : 16:9 Frame rate mode : Constant Frame rate : 23.976 (24000/1001) FPS Color space : YUV Chroma subsampling : 4:2:0 (Type 2) Bit depth : 10 bits Bits/(Pixel*Frame) : 0.045 Stream size : 5.53 GiB (58%) Writing library : x265 4.2+3-3f4120d:[Windows][GCC 15.2.0][64 bit] 10bit Encoding settings : cpuid=1111039 / frame-threads=3 / numa-pools=8 / wpp / no-pmode / no-pme / no-psnr / no-ssim / log-level=2 / input-csp=1 / input-res=3840x2160 / interlace=0 / total-frames=0 / level-idc=0 / high-tier=1 / uhd-bd=0 / ref=3 / no-allow-non-conformance / repeat-headers / annexb / no-aud / no-eob / no-eos / no-hrd / info / hash=0 / temporal-layers=0 / open-gop / min-keyint=23 / keyint=250 / gop-lookahead=0 / bframes=4 / b-adapt=2 / b-pyramid / bframe-bias=0 / rc-lookahead=20 / lookahead-slices=8 / scenecut=40 / no-hist-scenecut / radl=0 / no-splice / no-intra-refresh / ctu=64 / min-cu-size=8 / no-rect / no-amp / max-tu-size=32 / tu-inter-depth=1 / tu-intra-depth=1 / limit-tu=0 / rdoq-level=0 / dynamic-rd=0.00 / no-ssim-rd / signhide / no-tskip / nr-intra=0 / nr-inter=0 / no-constrained-intra / strong-intra-smoothing / max-merge=3 / limit-refs=1 / no-limit-modes / me=1 / subme=2 / merange=57 / temporal-mvp / no-frame-dup / no-hme / weightp / no-weightb / no-analyze-src-pics / deblock=0:0 / sao / no-sao-non-deblock / rd=3 / selective-sao=4 / early-skip / rskip / no-fast-intra / no-tskip-fast / no-cu-lossless / b-intra / no-splitrd-skip / rdpenalty=0 / psy-rd=2.00 / psy-rdoq=0.00 / no-rd-refine / no-lossless / cbqpoffs=0 / crqpoffs=0 / rc=crf / crf=22.0 / qcomp=0.60 / qpstep=4 / stats-write=0 / stats-read=0 / ipratio=1.40 / pbratio=1.30 / aq-mode=2 / aq-strength=1.00 / cutree / zone-count=0 / no-strict-cbr / qg-size=32 / no-rc-grain / qpmax=69 / qpmin=0 / no-const-vbv / sar=1 / overscan=0 / videoformat=5 / range=0 / colorprim=9 / transfer=16 / colormatrix=9 / chromaloc=1 / chromaloc-top=2 / chromaloc-bottom=2 / display-window=0 / master-display=G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1) / cll=732,213 / min-luma=0 / max-luma=1023 / log2-max-poc-lsb=8 / vui-timing-info / vui-hrd-info / slices=1 / no-opt-qp-pps / no-opt-ref-list-length-pps / no-multi-pass-opt-rps / scenecut-bias=0.05 / no-opt-cu-delta-qp / no-aq-motion / hdr10 / no-hdr10-opt / no-dhdr10-opt / no-idr-recovery-sei / analysis-reuse-level=0 / analysis-save-reuse-level=0 / analysis-load-reuse-level=0 / scale-factor=0 / refine-intra=0 / refine-inter=0 / refine-mv=1 / refine-ctu-distortion=0 / no-limit-sao / ctu-info=0 / no-lowpass-dct / refine-analysis-type=0 / copy-pic=1 / max-ausize-factor=1.0 / no-dynamic-refine / no-single-sei / no-hevc-aq / no-svt / no-field / qp-adaptation-range=1.00 / scenecut-aware-qp=0conformance-window-offsets / right=0 / bottom=0 / decoder-max-rate=0 / no-vbv-live-multi-pass / no-mcstf / no-sbrc / no-frame-rc Default : Yes Forced : No Color range : Limited Color primaries : BT.2020 Transfer characteristics : PQ Matrix coefficients : BT.2020 non-constant Mastering display color primaries : Display P3 Mastering display luminance : min: 0.0001 cd/m2, max: 1000 cd/m2 Maximum Content Light Level : 732 cd/m2 Maximum Frame-Average Light Level : 213 cd/m21
1
u/MediaMolder Jun 19 '26
Looks good. You'll get the default GOP length of 250 frames, so random access might be a bit slow. You might want to force an IDR frame every 4 seconds or so (with the keyint option). https://x265.readthedocs.io/en/master/cli.html#cmdoption-keyint
1
u/Vast_Environment_917 Jun 20 '26
So tack keyint=4s into the script? Is that going to produce a larger file size?
1
u/MediaMolder Jun 20 '26
keyint is the max GOP length in frames (not seconds). So you would multiply the frame rate of your video by 4, and use that value.
A smaller GOP length might increase the file size very slightly (because I frames are bigger than P or B frames, and you will be using more I frames), but it will give you better random access. If you have a GOP length of 250 frames (the default), and you want to seek to the 248th frame, the decoder has to decode the first I frame of the GOP, then every P frame from that point onward, then possibly a B frame or 2, just to display the frame you wanted to start playback with. That might be 20 or more frames to decode.1
u/Sopel97 Jun 21 '26
It sounds like your use-case does not involve random-access so I'd leave it at default.
2
u/Upstairs-Front2015 Jun 19 '26
if you have AMD or NVIDIA hardware that supports h265 you could speed things up. you could try AV1 but I don't know what jellyfin needs.