add hevc nvenc preset and batch compress scripts
- hevc_nvenc_1080p.json: 1080p HEVC NVENC preset (slow, 1850k VBR, 160k AAC) - hevc_nvenc_1080p_v2.json: same with decomb off for progressive sources - compress_hevc.py: batch folder compression with duration-based bitrate - compress_hevc_v2.py: adds NVDEC hw decode and optional -j 2 parallel mode - hevc_nvenc_1080p.md: preset usage doc with bitrate sizing table
This commit is contained in:
parent
d4d8278929
commit
4e63e41a0f
|
|
@ -0,0 +1,149 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Batch-compress every video in a folder to HEVC using the "1080P HEVC NVENC"
|
||||
HandBrake preset (hevc_nvenc_1080p.json, must sit next to this script).
|
||||
|
||||
Drop videos into the folder, run the script, collect the *_hevc.mp4 outputs:
|
||||
|
||||
python compress_hevc.py # uses E:\\Media\\Compress-Temp
|
||||
python compress_hevc.py D:\\some\\folder # any other folder
|
||||
python compress_hevc.py --target-gb 3.0 # change target size (default 2.5)
|
||||
python compress_hevc.py --dry-run # show what would run, encode nothing
|
||||
|
||||
For each file the script probes the duration with ffprobe and derives the
|
||||
average video bitrate needed to land near the target size, so a 1-hour clip
|
||||
and a 5-hour movie both come out roughly the same size. Files that are
|
||||
already at/below the target size are skipped, as are *_hevc.mp4 outputs and
|
||||
files whose output already exists. Encodes run sequentially (NVENC has a
|
||||
single hardware encoder, so parallelism buys nothing).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
PRESET_FILE = SCRIPT_DIR / "hevc_nvenc_1080p.json"
|
||||
PRESET_NAME = "1080P HEVC NVENC"
|
||||
|
||||
DEFAULT_FOLDER = r"E:\Media\Compress-Temp"
|
||||
VIDEO_EXTS = {".mp4", ".mkv", ".avi", ".mov", ".m4v", ".ts", ".wmv"}
|
||||
OUTPUT_SUFFIX = "_hevc"
|
||||
|
||||
AUDIO_KBPS = 160 # audio bitrate set by the preset
|
||||
OVERSHOOT = 1.09 # NVENC delivers ~9% above the requested avg bitrate
|
||||
MIN_VIDEO_KBPS = 800 # below this, 1080p HEVC falls apart visibly
|
||||
MAX_VIDEO_KBPS = 8000 # no point going higher for re-compression
|
||||
|
||||
|
||||
def find_tool(name, fallback):
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return path
|
||||
if Path(fallback).exists():
|
||||
return fallback
|
||||
sys.exit(f"error: '{name}' not found on PATH or at {fallback}")
|
||||
|
||||
|
||||
def probe_duration(ffprobe, path):
|
||||
out = subprocess.run(
|
||||
[ffprobe, "-v", "error", "-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1", str(path)],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if out.returncode != 0:
|
||||
raise RuntimeError(f"ffprobe failed for {path}: {out.stderr.strip()}")
|
||||
return float(out.stdout.strip())
|
||||
|
||||
|
||||
def calc_video_bitrate(duration_s, target_bytes):
|
||||
"""Requested (-b) video kbps to land near target_bytes, given NVENC overshoot."""
|
||||
total_kbps = target_bytes * 8 / 1000 / duration_s
|
||||
video_kbps = (total_kbps - AUDIO_KBPS) / OVERSHOOT
|
||||
return int(round(max(MIN_VIDEO_KBPS, min(MAX_VIDEO_KBPS, video_kbps))))
|
||||
|
||||
|
||||
def fmt_gib(nbytes):
|
||||
return f"{nbytes / 1024**3:.2f} GiB"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("folder", nargs="?", default=DEFAULT_FOLDER,
|
||||
help=f"folder to scan (default: {DEFAULT_FOLDER})")
|
||||
parser.add_argument("--target-gb", type=float, default=2.5,
|
||||
help="target size per video in GiB (default: 2.5)")
|
||||
parser.add_argument("--dry-run", action="store_true",
|
||||
help="print the planned encodes without running them")
|
||||
args = parser.parse_args()
|
||||
|
||||
folder = Path(args.folder)
|
||||
if not folder.is_dir():
|
||||
sys.exit(f"error: folder does not exist: {folder}")
|
||||
if not PRESET_FILE.is_file():
|
||||
sys.exit(f"error: preset file not found: {PRESET_FILE}")
|
||||
|
||||
handbrake = find_tool("HandBrakeCLI", r"C:\Program Files\HandBrake\HandBrakeCLI.exe")
|
||||
ffprobe = find_tool("ffprobe", r"C:\Program Files\ffmpeg\bin\ffprobe.exe")
|
||||
target_bytes = int(args.target_gb * 1024**3)
|
||||
|
||||
videos = sorted(
|
||||
p for p in folder.iterdir()
|
||||
if p.is_file()
|
||||
and p.suffix.lower() in VIDEO_EXTS
|
||||
and not p.stem.endswith(OUTPUT_SUFFIX)
|
||||
)
|
||||
if not videos:
|
||||
print(f"no videos found in {folder}")
|
||||
return
|
||||
|
||||
failures = 0
|
||||
for src in videos:
|
||||
out = src.with_name(src.stem + OUTPUT_SUFFIX + ".mp4")
|
||||
|
||||
if out.exists():
|
||||
print(f"SKIP {src.name} (output already exists)")
|
||||
continue
|
||||
if src.stat().st_size <= target_bytes:
|
||||
print(f"SKIP {src.name} (already {fmt_gib(src.stat().st_size)}, at/below target)")
|
||||
continue
|
||||
|
||||
try:
|
||||
duration = probe_duration(ffprobe, src)
|
||||
except RuntimeError as e:
|
||||
print(f"SKIP {src.name} ({e})")
|
||||
failures += 1
|
||||
continue
|
||||
|
||||
bitrate = calc_video_bitrate(duration, target_bytes)
|
||||
cmd = [
|
||||
handbrake,
|
||||
"--preset-import-file", str(PRESET_FILE),
|
||||
"-Z", PRESET_NAME,
|
||||
"-b", str(bitrate),
|
||||
"-i", str(src),
|
||||
"-o", str(out),
|
||||
]
|
||||
|
||||
print(f"ENCODE {src.name}: {fmt_gib(src.stat().st_size)}, "
|
||||
f"{duration / 3600:.2f} h, -b {bitrate} -> {out.name}")
|
||||
if args.dry_run:
|
||||
print(f" {' '.join(cmd)}")
|
||||
continue
|
||||
|
||||
result = subprocess.run(cmd)
|
||||
if result.returncode != 0 or not out.exists():
|
||||
print(f"FAIL {src.name} (HandBrakeCLI exit {result.returncode})")
|
||||
out.unlink(missing_ok=True)
|
||||
failures += 1
|
||||
continue
|
||||
|
||||
print(f"DONE {out.name}: {fmt_gib(out.stat().st_size)} "
|
||||
f"(was {fmt_gib(src.stat().st_size)})")
|
||||
|
||||
sys.exit(1 if failures else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Batch-compress every video in a folder to HEVC, v2.
|
||||
|
||||
Same as compress_hevc.py but built for throughput on NVIDIA GPUs:
|
||||
uses the "1080P HEVC NVENC v2" preset (hevc_nvenc_1080p_v2.json, must sit
|
||||
next to this script), decodes on the GPU via NVDEC (--enable-hw-decoding),
|
||||
and skips decomb entirely (preset has it off — progressive sources only!).
|
||||
|
||||
Measured on a GTX 1060: ~400 fps vs ~240 fps for the v1 pipeline (1.65x),
|
||||
which fully saturates the single NVENC engine — so running 2 jobs in
|
||||
parallel (-j 2) is supported but gains nothing; one job is already
|
||||
GPU-bound.
|
||||
|
||||
python compress_hevc_v2.py # E:\\Media\\Compress-Temp
|
||||
python compress_hevc_v2.py D:\\some\\folder # any other folder
|
||||
python compress_hevc_v2.py -j 2 # 2 parallel encodes
|
||||
python compress_hevc_v2.py --target-gb 3.0 # target size (default 2.5)
|
||||
python compress_hevc_v2.py --dry-run # show plan, encode nothing
|
||||
|
||||
WARNING: the v2 preset disables deinterlacing. Only feed it progressive
|
||||
sources (check with: ffprobe -show_entries stream=field_order ...).
|
||||
|
||||
Per-file video bitrate is derived from duration so every output lands near
|
||||
the target size. Files at/below the target, *_hevc.mp4 outputs, and files
|
||||
whose output already exists are skipped, so re-running resumes a batch.
|
||||
|
||||
Progress: single-job runs show HandBrake's live progress on the console;
|
||||
with -j 2 each job writes to <output>.log in the same folder instead
|
||||
(kept only on failure).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
PRESET_FILE = SCRIPT_DIR / "hevc_nvenc_1080p_v2.json"
|
||||
PRESET_NAME = "1080P HEVC NVENC v2"
|
||||
|
||||
DEFAULT_FOLDER = r"E:\Media\Compress-Temp"
|
||||
VIDEO_EXTS = {".mp4", ".mkv", ".avi", ".mov", ".m4v", ".ts", ".wmv"}
|
||||
OUTPUT_SUFFIX = "_hevc"
|
||||
|
||||
AUDIO_KBPS = 160 # audio bitrate set by the preset
|
||||
OVERSHOOT = 1.09 # NVENC delivers ~9% above the requested avg bitrate
|
||||
MIN_VIDEO_KBPS = 800 # below this, 1080p HEVC falls apart visibly
|
||||
MAX_VIDEO_KBPS = 8000 # no point going higher for re-compression
|
||||
MAX_JOBS = 2 # consumer Pascal NVENC allows 2 concurrent sessions
|
||||
|
||||
print_lock = threading.Lock()
|
||||
|
||||
|
||||
def log(msg):
|
||||
with print_lock:
|
||||
print(msg, flush=True)
|
||||
|
||||
|
||||
def find_tool(name, fallback):
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return path
|
||||
if Path(fallback).exists():
|
||||
return fallback
|
||||
sys.exit(f"error: '{name}' not found on PATH or at {fallback}")
|
||||
|
||||
|
||||
def probe_duration(ffprobe, path):
|
||||
out = subprocess.run(
|
||||
[ffprobe, "-v", "error", "-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1", str(path)],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if out.returncode != 0:
|
||||
raise RuntimeError(f"ffprobe failed for {path}: {out.stderr.strip()}")
|
||||
return float(out.stdout.strip())
|
||||
|
||||
|
||||
def calc_video_bitrate(duration_s, target_bytes):
|
||||
"""Requested (-b) video kbps to land near target_bytes, given NVENC overshoot."""
|
||||
total_kbps = target_bytes * 8 / 1000 / duration_s
|
||||
video_kbps = (total_kbps - AUDIO_KBPS) / OVERSHOOT
|
||||
return int(round(max(MIN_VIDEO_KBPS, min(MAX_VIDEO_KBPS, video_kbps))))
|
||||
|
||||
|
||||
def fmt_gib(nbytes):
|
||||
return f"{nbytes / 1024**3:.2f} GiB"
|
||||
|
||||
|
||||
def run_encode(handbrake, src, out, bitrate, dry_run, show_progress):
|
||||
cmd = [
|
||||
handbrake,
|
||||
"--enable-hw-decoding", "nvdec",
|
||||
"--preset-import-file", str(PRESET_FILE),
|
||||
"-Z", PRESET_NAME,
|
||||
"-b", str(bitrate),
|
||||
"-i", str(src),
|
||||
"-o", str(out),
|
||||
]
|
||||
if dry_run:
|
||||
log(f" {' '.join(cmd)}")
|
||||
return True
|
||||
|
||||
log_path = out.with_suffix(".log")
|
||||
if show_progress:
|
||||
# single job: let HandBrake draw its live progress on the console
|
||||
result = subprocess.run(cmd)
|
||||
else:
|
||||
# parallel: keep consoles from garbling each other, log per job instead
|
||||
log(f"START {src.name} (progress: {log_path})")
|
||||
with open(log_path, "w") as log_file:
|
||||
result = subprocess.run(cmd, stdout=log_file,
|
||||
stderr=subprocess.STDOUT)
|
||||
|
||||
if result.returncode != 0 or not out.exists():
|
||||
log(f"FAIL {src.name} (HandBrakeCLI exit {result.returncode})"
|
||||
+ ("" if show_progress else f", see {log_path}"))
|
||||
out.unlink(missing_ok=True)
|
||||
return False
|
||||
|
||||
log_path.unlink(missing_ok=True)
|
||||
log(f"DONE {out.name}: {fmt_gib(out.stat().st_size)} "
|
||||
f"(was {fmt_gib(src.stat().st_size)})")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("folder", nargs="?", default=DEFAULT_FOLDER,
|
||||
help=f"folder to scan (default: {DEFAULT_FOLDER})")
|
||||
parser.add_argument("-j", "--jobs", type=int, default=1, choices=[1, 2],
|
||||
help="parallel encodes (default: 1, max: 2 — NVENC session limit)")
|
||||
parser.add_argument("--target-gb", type=float, default=2.5,
|
||||
help="target size per video in GiB (default: 2.5)")
|
||||
parser.add_argument("--dry-run", action="store_true",
|
||||
help="print the planned encodes without running them")
|
||||
args = parser.parse_args()
|
||||
|
||||
folder = Path(args.folder)
|
||||
if not folder.is_dir():
|
||||
sys.exit(f"error: folder does not exist: {folder}")
|
||||
if not PRESET_FILE.is_file():
|
||||
sys.exit(f"error: preset file not found: {PRESET_FILE}")
|
||||
|
||||
handbrake = find_tool("HandBrakeCLI", r"C:\Program Files\HandBrake\HandBrakeCLI.exe")
|
||||
ffprobe = find_tool("ffprobe", r"C:\Program Files\ffmpeg\bin\ffprobe.exe")
|
||||
target_bytes = int(args.target_gb * 1024**3)
|
||||
|
||||
videos = sorted(
|
||||
p for p in folder.iterdir()
|
||||
if p.is_file()
|
||||
and p.suffix.lower() in VIDEO_EXTS
|
||||
and not p.stem.endswith(OUTPUT_SUFFIX)
|
||||
)
|
||||
if not videos:
|
||||
print(f"no videos found in {folder}")
|
||||
return
|
||||
|
||||
jobs = []
|
||||
failures = 0
|
||||
for src in videos:
|
||||
out = src.with_name(src.stem + OUTPUT_SUFFIX + ".mp4")
|
||||
|
||||
if out.exists():
|
||||
print(f"SKIP {src.name} (output already exists)")
|
||||
continue
|
||||
if src.stat().st_size <= target_bytes:
|
||||
print(f"SKIP {src.name} (already {fmt_gib(src.stat().st_size)}, at/below target)")
|
||||
continue
|
||||
|
||||
try:
|
||||
duration = probe_duration(ffprobe, src)
|
||||
except RuntimeError as e:
|
||||
print(f"SKIP {src.name} ({e})")
|
||||
failures += 1
|
||||
continue
|
||||
|
||||
bitrate = calc_video_bitrate(duration, target_bytes)
|
||||
print(f"QUEUE {src.name}: {fmt_gib(src.stat().st_size)}, "
|
||||
f"{duration / 3600:.2f} h, -b {bitrate} -> {out.name}")
|
||||
jobs.append((src, out, bitrate))
|
||||
|
||||
with ThreadPoolExecutor(max_workers=args.jobs) as pool:
|
||||
futures = [
|
||||
pool.submit(run_encode, handbrake, src, out, bitrate,
|
||||
args.dry_run, args.jobs == 1)
|
||||
for src, out, bitrate in jobs
|
||||
]
|
||||
for future in as_completed(futures):
|
||||
if not future.result():
|
||||
failures += 1
|
||||
|
||||
sys.exit(1 if failures else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"PresetList": [
|
||||
{
|
||||
"AlignAVStart": true,
|
||||
"AudioCopyMask": [],
|
||||
"AudioEncoderFallback": "av_aac",
|
||||
"AudioLanguageList": [
|
||||
"any"
|
||||
],
|
||||
"AudioList": [
|
||||
{
|
||||
"AudioBitrate": 160,
|
||||
"AudioCompressionLevel": 0,
|
||||
"AudioEncoder": "av_aac",
|
||||
"AudioMixdown": "stereo",
|
||||
"AudioNormalizeMixLevel": false,
|
||||
"AudioSamplerate": "auto",
|
||||
"AudioTrackQualityEnable": false,
|
||||
"AudioTrackQuality": -1,
|
||||
"AudioTrackGainSlider": 0,
|
||||
"AudioTrackDRCSlider": 0
|
||||
}
|
||||
],
|
||||
"AudioSecondaryEncoderMode": true,
|
||||
"AudioTrackSelectionBehavior": "all",
|
||||
"ChapterMarkers": true,
|
||||
"ChildrenArray": [],
|
||||
"Default": true,
|
||||
"FileFormat": "av_mp4",
|
||||
"Folder": false,
|
||||
"FolderOpen": false,
|
||||
"Optimize": true,
|
||||
"Mp4iPodCompatible": false,
|
||||
"PictureCropMode": 0,
|
||||
"PictureBottomCrop": 0,
|
||||
"PictureLeftCrop": 0,
|
||||
"PictureRightCrop": 0,
|
||||
"PictureTopCrop": 0,
|
||||
"PictureDARWidth": 1920,
|
||||
"PictureDeblockPreset": "off",
|
||||
"PictureDeblockTune": "medium",
|
||||
"PictureDeblockCustom": "strength=strong:thresh=20:blocksize=8",
|
||||
"PictureDeinterlaceFilter": "decomb",
|
||||
"PictureCombDetectPreset": "default",
|
||||
"PictureCombDetectCustom": "",
|
||||
"PictureDeinterlacePreset": "default",
|
||||
"PictureDeinterlaceCustom": "",
|
||||
"PictureDenoiseCustom": "",
|
||||
"PictureDenoiseFilter": "off",
|
||||
"PictureSharpenCustom": "",
|
||||
"PictureSharpenFilter": "off",
|
||||
"PictureSharpenPreset": "medium",
|
||||
"PictureSharpenTune": "none",
|
||||
"PictureDetelecine": "off",
|
||||
"PictureDetelecineCustom": "",
|
||||
"PictureColorspacePreset": "off",
|
||||
"PictureColorspaceCustom": "",
|
||||
"PictureChromaSmoothPreset": "off",
|
||||
"PictureChromaSmoothCustom": "",
|
||||
"PictureChromaSmoothTune": "none",
|
||||
"PictureItuPAR": false,
|
||||
"PictureKeepRatio": true,
|
||||
"PicturePAR": "auto",
|
||||
"PicturePARWidth": 1,
|
||||
"PicturePARHeight": 1,
|
||||
"PictureWidth": 1920,
|
||||
"PictureHeight": 1080,
|
||||
"PictureUseMaximumSize": true,
|
||||
"PictureAllowUpscaling": false,
|
||||
"PictureForceHeight": 0,
|
||||
"PictureForceWidth": 0,
|
||||
"PicturePadMode": "none",
|
||||
"PicturePadTop": 0,
|
||||
"PicturePadBottom": 0,
|
||||
"PicturePadLeft": 0,
|
||||
"PicturePadRight": 0,
|
||||
"PresetName": "1080P HEVC NVENC",
|
||||
"Type": 1,
|
||||
"SubtitleAddCC": false,
|
||||
"SubtitleAddForeignAudioSearch": false,
|
||||
"SubtitleAddForeignAudioSubtitle": false,
|
||||
"SubtitleBurnBehavior": "none",
|
||||
"SubtitleBurnBDSub": false,
|
||||
"SubtitleBurnDVDSub": false,
|
||||
"SubtitleLanguageList": [
|
||||
"any"
|
||||
],
|
||||
"SubtitleTrackSelectionBehavior": "none",
|
||||
"VideoAvgBitrate": 1850,
|
||||
"VideoColorMatrixCode": 0,
|
||||
"VideoEncoder": "nvenc_h265",
|
||||
"VideoFramerateMode": "vfr",
|
||||
"VideoGrayScale": false,
|
||||
"VideoScaler": "swscale",
|
||||
"VideoPreset": "slow",
|
||||
"VideoTune": "",
|
||||
"VideoProfile": "main",
|
||||
"VideoLevel": "auto",
|
||||
"VideoOptionExtra": "",
|
||||
"VideoQualityType": 1,
|
||||
"VideoQualitySlider": 24,
|
||||
"VideoMultiPass": true,
|
||||
"VideoTurboMultiPass": true,
|
||||
"x264UseAdvancedOptions": false,
|
||||
"PresetDisabled": false,
|
||||
"MetadataPassthrough": true
|
||||
}
|
||||
],
|
||||
"VersionMajor": 56,
|
||||
"VersionMicro": 0,
|
||||
"VersionMinor": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# Preset: 1080P HEVC NVENC
|
||||
|
||||
File: `hevc_nvenc_1080p.json` — HandBrake preset for GPU-accelerated HEVC (H.265)
|
||||
compression of 1080p video using NVIDIA NVENC.
|
||||
|
||||
## What it does
|
||||
|
||||
- **Encoder**: `nvenc_h265` (HEVC on the GPU, not CPU x265)
|
||||
- **Preset**: `slow` — best quality/speed balance on Pascal-class GPUs;
|
||||
`slowest` gains almost nothing but nearly halves the speed
|
||||
- **Rate control**: VBR, 1850 kbps video (multipass) + 160 kbps AAC stereo audio
|
||||
- **Resolution**: unchanged up to 1920×1080 (larger sources are downscaled to fit,
|
||||
aspect ratio preserved, no upscaling)
|
||||
- **Framerate**: same as source (VFR passthrough)
|
||||
- **Container**: MP4, web-optimized, chapters + metadata preserved
|
||||
- **Subtitles**: not included
|
||||
|
||||
Tuned so a ~3 hour 1080p source lands around **2.5–2.7 GiB**. Reference result:
|
||||
8.06 GB h264 1080p source (2h59m) → 2.68 GiB HEVC, ~26 min encode on a GTX 1060
|
||||
(~220 fps average).
|
||||
|
||||
## Requirements
|
||||
|
||||
- HandBrake 1.8+ (`HandBrakeCLI.exe` for command line use)
|
||||
- NVIDIA GPU with HEVC NVENC support (GTX 10-series or newer) and recent drivers
|
||||
|
||||
## Usage — command line
|
||||
|
||||
```bash
|
||||
HandBrakeCLI --preset-import-file hevc_nvenc_1080p.json \
|
||||
-Z "1080P HEVC NVENC" \
|
||||
-i input.mp4 -o output.mp4
|
||||
```
|
||||
|
||||
Windows example:
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files\HandBrake\HandBrakeCLI.exe" `
|
||||
--preset-import-file hevc_nvenc_1080p.json `
|
||||
-Z "1080P HEVC NVENC" `
|
||||
-i "F:\Media\input.mp4" -o "F:\Media\output.mp4"
|
||||
```
|
||||
|
||||
## Usage — HandBrake GUI
|
||||
|
||||
1. Open HandBrake → **Presets** panel → **Import** (bottom of the panel).
|
||||
2. Select `hevc_nvenc_1080p.json`.
|
||||
3. Open your source, choose the **1080P HEVC NVENC** preset, set the output path, Start.
|
||||
|
||||
## Adjusting the target size
|
||||
|
||||
The output size is driven by `VideoAvgBitrate` (kbps) and the video duration:
|
||||
|
||||
```
|
||||
VideoAvgBitrate ≈ (target_bytes × 8 / 1000 − audio_kbps × duration_s) / duration_s
|
||||
```
|
||||
|
||||
For a 2.5 GiB target with 160 kbps audio, that simplifies to roughly:
|
||||
|
||||
```
|
||||
VideoAvgBitrate ≈ 21500000 / duration_seconds − 160
|
||||
```
|
||||
|
||||
| Duration | VideoAvgBitrate | Approx. output |
|
||||
|----------|-----------------|----------------|
|
||||
| 1 h | ~5800 kbps | ~2.5 GiB |
|
||||
| 1.5 h | ~3800 kbps | ~2.5 GiB |
|
||||
| 2 h | ~2800 kbps | ~2.5 GiB |
|
||||
| 3 h | 1850 kbps (current) | ~2.6 GiB |
|
||||
| 4 h | ~1330 kbps | ~2.5 GiB |
|
||||
|
||||
Raise the value for better quality (larger file), lower it for a smaller file.
|
||||
For 1080p HEVC, going below ~1200 kbps starts to show visible artifacts.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"PresetList": [
|
||||
{
|
||||
"AlignAVStart": true,
|
||||
"AudioCopyMask": [],
|
||||
"AudioEncoderFallback": "av_aac",
|
||||
"AudioLanguageList": [
|
||||
"any"
|
||||
],
|
||||
"AudioList": [
|
||||
{
|
||||
"AudioBitrate": 160,
|
||||
"AudioCompressionLevel": 0,
|
||||
"AudioEncoder": "av_aac",
|
||||
"AudioMixdown": "stereo",
|
||||
"AudioNormalizeMixLevel": false,
|
||||
"AudioSamplerate": "auto",
|
||||
"AudioTrackQualityEnable": false,
|
||||
"AudioTrackQuality": -1,
|
||||
"AudioTrackGainSlider": 0,
|
||||
"AudioTrackDRCSlider": 0
|
||||
}
|
||||
],
|
||||
"AudioSecondaryEncoderMode": true,
|
||||
"AudioTrackSelectionBehavior": "all",
|
||||
"ChapterMarkers": true,
|
||||
"ChildrenArray": [],
|
||||
"Default": true,
|
||||
"FileFormat": "av_mp4",
|
||||
"Folder": false,
|
||||
"FolderOpen": false,
|
||||
"Optimize": true,
|
||||
"Mp4iPodCompatible": false,
|
||||
"PictureCropMode": 0,
|
||||
"PictureBottomCrop": 0,
|
||||
"PictureLeftCrop": 0,
|
||||
"PictureRightCrop": 0,
|
||||
"PictureTopCrop": 0,
|
||||
"PictureDARWidth": 1920,
|
||||
"PictureDeblockPreset": "off",
|
||||
"PictureDeblockTune": "medium",
|
||||
"PictureDeblockCustom": "strength=strong:thresh=20:blocksize=8",
|
||||
"PictureDeinterlaceFilter": "off",
|
||||
"PictureCombDetectPreset": "off",
|
||||
"PictureCombDetectCustom": "",
|
||||
"PictureDeinterlacePreset": "off",
|
||||
"PictureDeinterlaceCustom": "",
|
||||
"PictureDenoiseCustom": "",
|
||||
"PictureDenoiseFilter": "off",
|
||||
"PictureSharpenCustom": "",
|
||||
"PictureSharpenFilter": "off",
|
||||
"PictureSharpenPreset": "medium",
|
||||
"PictureSharpenTune": "none",
|
||||
"PictureDetelecine": "off",
|
||||
"PictureDetelecineCustom": "",
|
||||
"PictureColorspacePreset": "off",
|
||||
"PictureColorspaceCustom": "",
|
||||
"PictureChromaSmoothPreset": "off",
|
||||
"PictureChromaSmoothCustom": "",
|
||||
"PictureChromaSmoothTune": "none",
|
||||
"PictureItuPAR": false,
|
||||
"PictureKeepRatio": true,
|
||||
"PicturePAR": "auto",
|
||||
"PicturePARWidth": 1,
|
||||
"PicturePARHeight": 1,
|
||||
"PictureWidth": 1920,
|
||||
"PictureHeight": 1080,
|
||||
"PictureUseMaximumSize": true,
|
||||
"PictureAllowUpscaling": false,
|
||||
"PictureForceHeight": 0,
|
||||
"PictureForceWidth": 0,
|
||||
"PicturePadMode": "none",
|
||||
"PicturePadTop": 0,
|
||||
"PicturePadBottom": 0,
|
||||
"PicturePadLeft": 0,
|
||||
"PicturePadRight": 0,
|
||||
"PresetName": "1080P HEVC NVENC v2",
|
||||
"Type": 1,
|
||||
"SubtitleAddCC": false,
|
||||
"SubtitleAddForeignAudioSearch": false,
|
||||
"SubtitleAddForeignAudioSubtitle": false,
|
||||
"SubtitleBurnBehavior": "none",
|
||||
"SubtitleBurnBDSub": false,
|
||||
"SubtitleBurnDVDSub": false,
|
||||
"SubtitleLanguageList": [
|
||||
"any"
|
||||
],
|
||||
"SubtitleTrackSelectionBehavior": "none",
|
||||
"VideoAvgBitrate": 1850,
|
||||
"VideoColorMatrixCode": 0,
|
||||
"VideoEncoder": "nvenc_h265",
|
||||
"VideoFramerateMode": "vfr",
|
||||
"VideoGrayScale": false,
|
||||
"VideoScaler": "swscale",
|
||||
"VideoPreset": "slow",
|
||||
"VideoTune": "",
|
||||
"VideoProfile": "main",
|
||||
"VideoLevel": "auto",
|
||||
"VideoOptionExtra": "",
|
||||
"VideoQualityType": 1,
|
||||
"VideoQualitySlider": 24,
|
||||
"VideoMultiPass": true,
|
||||
"VideoTurboMultiPass": true,
|
||||
"x264UseAdvancedOptions": false,
|
||||
"PresetDisabled": false,
|
||||
"MetadataPassthrough": true
|
||||
}
|
||||
],
|
||||
"VersionMajor": 56,
|
||||
"VersionMicro": 0,
|
||||
"VersionMinor": 0
|
||||
}
|
||||
Loading…
Reference in New Issue