update minio port

This commit is contained in:
Kevin
2026-05-22 09:35:41 +08:00
parent 153c91f8ff
commit 62b14d7386
22 changed files with 1256 additions and 1170 deletions

View File

@@ -0,0 +1,276 @@
"""FFmpeg/ffprobe helpers for batch uploads and browser-ready MP4 (infrastructure only)."""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
from loguru import logger
VISUALIZATION_MAX_WIDTH = 1920
def visualization_ffmpeg_scale_filter() -> str:
return f"scale='min({VISUALIZATION_MAX_WIDTH},iw)':-2"
def browser_transcode_tmp_path(output_path: Path) -> Path:
return output_path.with_name(f"{output_path.stem}.part{output_path.suffix}")
def is_readable_mp4(path: Path) -> bool:
ffprobe = shutil.which("ffprobe")
if ffprobe is None or not path.is_file() or path.stat().st_size < 4096:
return False
proc = subprocess.run(
[
ffprobe,
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=codec_name",
"-of",
"csv=p=0",
str(path),
],
check=False,
text=True,
capture_output=True,
)
return proc.returncode == 0 and bool((proc.stdout or "").strip())
def ffprobe_fields(path: Path, entries: str) -> dict[str, str]:
ffprobe = shutil.which("ffprobe")
if ffprobe is None or not path.is_file():
return {}
proc = subprocess.run(
[
ffprobe,
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
entries,
"-of",
"default=noprint_wrappers=1",
str(path),
],
check=False,
text=True,
capture_output=True,
)
if proc.returncode != 0:
return {}
fields: dict[str, str] = {}
for line in proc.stdout.splitlines():
if "=" not in line:
continue
key, value = line.split("=", 1)
fields[key.strip().lower()] = value.strip().lower()
return fields
def ffprobe_container_format(path: Path) -> str:
ffprobe = shutil.which("ffprobe")
if ffprobe is None or not path.is_file():
return ""
proc = subprocess.run(
[
ffprobe,
"-v",
"error",
"-show_entries",
"format=format_name",
"-of",
"default=noprint_wrappers=1:nokey=1",
str(path),
],
check=False,
text=True,
capture_output=True,
)
if proc.returncode != 0:
return ""
return (proc.stdout or "").strip().lower()
def is_browser_compatible_mp4(path: Path) -> bool:
fields = ffprobe_fields(path, "stream=codec_name,pix_fmt")
return fields.get("codec_name") == "h264" and fields.get("pix_fmt") in {"yuv420p", "yuvj420p"}
def batch_input_needs_normalize(path: Path) -> bool:
if not is_readable_mp4(path):
return True
if not is_browser_compatible_mp4(path):
return True
container = ffprobe_container_format(path)
if container and "mpeg" in container:
return True
fields = ffprobe_fields(path, "stream=codec_name,width,height")
try:
width = int(fields.get("width") or "0")
except ValueError:
width = 0
return width > 1920
def normalize_batch_input_video(source_path: Path, output_path: Path) -> bool:
ffmpeg = shutil.which("ffmpeg")
if ffmpeg is None or not source_path.is_file():
return False
if not is_readable_mp4(source_path):
logger.warning("skip batch input normalize: unreadable source {}", source_path)
return False
output_path.parent.mkdir(parents=True, exist_ok=True)
tmp_path = browser_transcode_tmp_path(output_path)
if tmp_path.exists():
tmp_path.unlink()
logger.info("ffmpeg batch input normalize starting: {} -> {}", source_path, output_path)
proc = subprocess.run(
[
ffmpeg,
"-y",
"-hide_banner",
"-loglevel",
"error",
"-fflags",
"+genpts",
"-i",
str(source_path),
"-map",
"0:v:0",
"-an",
"-f",
"mp4",
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-preset",
"veryfast",
"-crf",
"23",
"-vf",
"scale='min(1920,iw)':-2",
"-movflags",
"+faststart",
str(tmp_path),
],
check=False,
text=True,
capture_output=True,
)
if proc.returncode != 0:
stderr = (proc.stderr or "").strip()
logger.warning("ffmpeg batch input normalize failed: {}", stderr[-3000:])
if tmp_path.exists():
tmp_path.unlink()
return False
if not tmp_path.is_file() or tmp_path.stat().st_size <= 0:
logger.warning("ffmpeg batch input normalize produced empty file: {}", tmp_path)
if tmp_path.exists():
tmp_path.unlink()
return False
tmp_path.replace(output_path)
if not is_browser_compatible_mp4(output_path):
logger.warning("ffmpeg batch input normalize output not h264/yuv420p: {}", output_path)
output_path.unlink(missing_ok=True)
return False
logger.info(
"ffmpeg batch input normalize complete: {} ({} bytes)",
output_path,
output_path.stat().st_size,
)
return True
def ensure_batch_pipeline_input_video(*, source_path: Path, dest_path: Path) -> None:
import shutil as sh
dest_path.parent.mkdir(parents=True, exist_ok=True)
if dest_path.is_file() and dest_path.stat().st_size > 0 and not batch_input_needs_normalize(dest_path):
return
if batch_input_needs_normalize(source_path):
if normalize_batch_input_video(source_path, dest_path):
return
logger.warning(
"batch input normalize failed, falling back to raw copy: {} -> {}",
source_path,
dest_path,
)
if not dest_path.is_file():
sh.copy2(source_path, dest_path)
def transcode_visualization_for_browser(source_path: Path, output_path: Path) -> bool:
ffmpeg = shutil.which("ffmpeg")
if ffmpeg is None or not source_path.is_file():
return False
if not is_readable_mp4(source_path):
logger.warning("skip visualization transcode: unreadable source {}", source_path)
return False
output_path.parent.mkdir(parents=True, exist_ok=True)
tmp_path = browser_transcode_tmp_path(output_path)
if tmp_path.exists():
tmp_path.unlink()
logger.info("ffmpeg visualization transcode starting: {} -> {}", source_path, output_path)
proc = subprocess.run(
[
ffmpeg,
"-y",
"-hide_banner",
"-loglevel",
"error",
"-i",
str(source_path),
"-map",
"0:v:0",
"-an",
"-f",
"mp4",
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-preset",
"ultrafast",
"-crf",
"23",
"-vf",
visualization_ffmpeg_scale_filter(),
"-movflags",
"+faststart",
str(tmp_path),
],
check=False,
text=True,
capture_output=True,
)
if proc.returncode != 0:
stderr = (proc.stderr or "").strip()
logger.warning("ffmpeg visualization transcode failed: {}", stderr[-3000:])
if tmp_path.exists():
tmp_path.unlink()
return False
if not tmp_path.is_file() or tmp_path.stat().st_size <= 0:
logger.warning("ffmpeg visualization transcode produced empty file: {}", tmp_path)
if tmp_path.exists():
tmp_path.unlink()
return False
tmp_path.replace(output_path)
if not is_browser_compatible_mp4(output_path):
logger.warning("ffmpeg output is not browser-compatible h264/yuv420p: {}", output_path)
output_path.unlink(missing_ok=True)
return False
logger.info(
"ffmpeg visualization transcode complete: {} ({} bytes)",
output_path,
output_path.stat().st_size,
)
return True