Update to better hand h265 / x265 encoded files.
This commit is contained in:
parent
0e10143b5c
commit
cdd27043a2
3 changed files with 101 additions and 12 deletions
67
app.py
67
app.py
|
|
@ -62,10 +62,20 @@ VIDEO_EXTENSIONS = {
|
|||
|
||||
|
||||
def get_video_info(filepath: str) -> dict | None:
|
||||
"""
|
||||
Use ffprobe to get duration, total bitrate, codec, and dimensions.
|
||||
|
||||
Bitrate resolution strategy (handles HEVC/MKV where stream-level
|
||||
bit_rate is absent):
|
||||
1. Stream-level bit_rate — present for H.264/MP4, often missing for HEVC
|
||||
2. Format-level bit_rate — reliable for all containers
|
||||
3. Derived from size/duration — final fallback
|
||||
"""
|
||||
cmd = [
|
||||
'ffprobe', '-v', 'error',
|
||||
'-select_streams', 'v:0',
|
||||
'-show_entries', 'format=duration,bit_rate,size:stream=codec_name,width,height',
|
||||
'-show_entries',
|
||||
'format=duration,bit_rate,size:stream=codec_name,width,height,bit_rate',
|
||||
'-of', 'json',
|
||||
filepath,
|
||||
]
|
||||
|
|
@ -78,15 +88,26 @@ def get_video_info(filepath: str) -> dict | None:
|
|||
stream = (data.get('streams') or [{}])[0]
|
||||
|
||||
duration = float(fmt.get('duration', 0))
|
||||
bit_rate = int(fmt.get('bit_rate', 0))
|
||||
size_bytes = int(fmt.get('size', 0))
|
||||
codec = stream.get('codec_name', 'unknown')
|
||||
width = stream.get('width', 0)
|
||||
height = stream.get('height', 0)
|
||||
|
||||
if bit_rate == 0 and duration > 0:
|
||||
# Prefer stream-level bitrate, fall back to format-level, then derive
|
||||
stream_br = int(stream.get('bit_rate') or 0)
|
||||
format_br = int(fmt.get('bit_rate') or 0)
|
||||
if stream_br > 0:
|
||||
bit_rate = stream_br
|
||||
elif format_br > 0:
|
||||
bit_rate = format_br
|
||||
elif duration > 0:
|
||||
bit_rate = int((size_bytes * 8) / duration)
|
||||
else:
|
||||
bit_rate = 0
|
||||
|
||||
# Target ≈ 1/3 of the total bitrate, reserving 128 kbps for audio.
|
||||
# For HEVC sources the format bitrate already includes audio, so the
|
||||
# same formula applies regardless of codec.
|
||||
audio_bps = 128_000
|
||||
video_bps = bit_rate - audio_bps if bit_rate > audio_bps else bit_rate
|
||||
target_video_bps = max(int(video_bps / 3), 200_000)
|
||||
|
|
@ -345,13 +366,21 @@ def run_compression_job(job_id: str) -> None:
|
|||
|
||||
src_path = file_info['path']
|
||||
target_bitrate = file_info.get('target_bit_rate_bps', 1_000_000)
|
||||
src_codec = file_info.get('codec', 'unknown').lower()
|
||||
p = Path(src_path)
|
||||
out_path = str(p.parent / (p.stem + suffix + p.suffix))
|
||||
|
||||
# Choose encoder to match the source codec.
|
||||
# hevc / h265 / x265 → libx265
|
||||
# everything else → libx264 (safe, universally supported)
|
||||
is_hevc = src_codec in ('hevc', 'h265', 'x265')
|
||||
encoder = 'libx265' if is_hevc else 'libx264'
|
||||
|
||||
push_event(job, {
|
||||
'type': 'file_start', 'index': idx, 'total': total,
|
||||
'filename': p.name, 'output': out_path,
|
||||
'message': f'Compressing ({idx + 1}/{total}): {p.name}',
|
||||
'encoder': encoder,
|
||||
'message': f'Compressing ({idx + 1}/{total}): {p.name} [{encoder}]',
|
||||
})
|
||||
|
||||
try:
|
||||
|
|
@ -365,12 +394,34 @@ def run_compression_job(job_id: str) -> None:
|
|||
duration_secs = 0
|
||||
|
||||
video_k = max(int(target_bitrate / 1000), 200)
|
||||
|
||||
# Build the encoder-specific part of the ffmpeg command.
|
||||
#
|
||||
# libx264 uses -maxrate / -bufsize for VBV (Video Buffering Verifier).
|
||||
# libx265 passes those same constraints via -x265-params because its
|
||||
# CLI option names differ from the generic ffmpeg flags.
|
||||
# Both use AAC audio at 128 kbps.
|
||||
# -movflags +faststart is only meaningful for MP4 containers; it is
|
||||
# harmless (silently ignored) for MKV/MOV/etc.
|
||||
if is_hevc:
|
||||
vbv_maxrate = int(video_k * 1.5)
|
||||
vbv_bufsize = video_k * 2
|
||||
encoder_opts = [
|
||||
'-c:v', 'libx265',
|
||||
'-b:v', f'{video_k}k',
|
||||
'-x265-params', f'vbv-maxrate={vbv_maxrate}:vbv-bufsize={vbv_bufsize}',
|
||||
]
|
||||
else:
|
||||
encoder_opts = [
|
||||
'-c:v', 'libx264',
|
||||
'-b:v', f'{video_k}k',
|
||||
'-maxrate', f'{int(video_k * 1.5)}k',
|
||||
'-bufsize', f'{video_k * 2}k',
|
||||
]
|
||||
|
||||
cmd = [
|
||||
'ffmpeg', '-y', '-i', src_path,
|
||||
'-c:v', 'libx264',
|
||||
'-b:v', f'{video_k}k',
|
||||
'-maxrate', f'{int(video_k * 1.5)}k',
|
||||
'-bufsize', f'{video_k * 2}k',
|
||||
*encoder_opts,
|
||||
'-c:a', 'aac', '-b:a', '128k',
|
||||
'-movflags', '+faststart',
|
||||
'-progress', 'pipe:1', '-nostats',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue