e34598aab0
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 51s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 55s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m31s
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
## GPU mezzanine relay直传(核心优化)
- 新增 relay /mezzanine/{key} 接口(PUT/GET/HEAD/DELETE),复用token鉴权
- gpu_encoder 默认走 relay PUT 上传 mezzanine(Tailscale/Docker内网,~1s完成)
- relay上传失败自动fallback到公网OSS,兼容未配Tailscale的环境
- P4000 从 relay_base_url(:8092) GET mezzanine,跳过OSS下载
- 节省公网OSS上传+下载18-20s固定延迟,短视频(1min)GPU路径终于比CPU快
- 新增 GPU_ENCODE_MEZZANINE_TRANSPORT 配置(relay|oss),默认relay
## CPU编码preset
- FFMPEG_ENCODE_PRESET 默认 fast→veryfast,60s720p CPU编码32s→20s(~38%提速)
## CI staging部署修复
- STAGING_SSH_HOST 默认值恢复为 116.62.226.203(真实staging机,DNS指向的机器)
- STAGING_SSH_PORT 默认值 22222→22
- scripts/ci_staging_healthcheck.sh 同步更新
- 注:47.98.113.167 是prod机,之前误设为staging目标导致部署打偏
88 lines
3.7 KiB
Python
Executable File
88 lines
3.7 KiB
Python
Executable File
"""FFmpeg 共享工具 — packages/shared 层.
|
||
|
||
仅包含与业务无关的底层原语:FFmpeg/FFprobe 二进制路径、run_ffmpeg 执行器。
|
||
业务相关的滤镜构建、视频探测等留在 apps/worker/video_processing/ffmpeg_utils.py。
|
||
|
||
application 层和 worker 层都可以引用本模块,避免跨层依赖。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import os
|
||
import shutil
|
||
import subprocess # nosec B404
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# ── 常量 ──────────────────────────────────────────────────────────────────────
|
||
|
||
FFMPEG_BIN: str = shutil.which("ffmpeg") or "ffmpeg"
|
||
FFPROBE_BIN: str = shutil.which("ffprobe") or "ffprobe"
|
||
|
||
# FFmpeg 执行默认超时(秒),防止 FFmpeg hang 住导致进程永久阻塞
|
||
# 默认 30 分钟,足够处理大部分短视频渲染;超长视频可单独传参覆盖
|
||
DEFAULT_FFMPEG_TIMEOUT = 1800
|
||
|
||
# ── 编码参数(集中配置,支持环境变量覆盖)────────────────────────────────────
|
||
# preset 默认 veryfast,相比 fast 再提速 ~40%(4 核 Xeon 60s 720p: 32s→20s),CRF=23 画质可接受
|
||
# 可通过环境变量 FFMPEG_ENCODE_PRESET 覆盖(如 fast/medium 追求质量,ultrafast 追求极致速度)
|
||
FFMPEG_ENCODE_PRESET: str = os.environ.get("FFMPEG_ENCODE_PRESET", "veryfast")
|
||
# CRF 保持 23(libx264 默认质量),可通过 FFMPEG_ENCODE_CRF 覆盖
|
||
FFMPEG_ENCODE_CRF: str = os.environ.get("FFMPEG_ENCODE_CRF", "23")
|
||
# 编码线程数:0 = 自动检测 CPU 核心数,充分利用多核
|
||
FFMPEG_ENCODE_THREADS: str = os.environ.get("FFMPEG_ENCODE_THREADS", "0")
|
||
|
||
|
||
# ── FFmpeg 执行 ───────────────────────────────────────────────────────────────
|
||
|
||
|
||
def run_ffmpeg(
|
||
command: list[str],
|
||
*,
|
||
capture_output: bool = True,
|
||
timeout: int | None = DEFAULT_FFMPEG_TIMEOUT,
|
||
) -> tuple[str, str]:
|
||
"""执行 FFmpeg 命令(统一入口)。
|
||
|
||
Args:
|
||
command: 完整的 ffmpeg 命令列表(含 "ffmpeg" 本身)
|
||
capture_output: 是否捕获 stdout/stderr
|
||
timeout: 超时时间(秒),默认 1800s(30分钟);None 表示不设超时(不推荐)
|
||
|
||
Returns:
|
||
(stdout, stderr) 元组
|
||
|
||
Raises:
|
||
subprocess.CalledProcessError: 命令执行失败时抛出,
|
||
异常信息包含完整 stderr 以便排查。
|
||
subprocess.TimeoutExpired: 超时未完成时抛出,FFmpeg 进程会被 kill。
|
||
"""
|
||
try:
|
||
result = subprocess.run( # nosec B603
|
||
command,
|
||
check=True,
|
||
stdout=subprocess.PIPE if capture_output else None,
|
||
stderr=subprocess.PIPE if capture_output else None,
|
||
text=True,
|
||
timeout=timeout,
|
||
)
|
||
return (result.stdout or "", result.stderr or "")
|
||
except subprocess.TimeoutExpired:
|
||
logger.error(
|
||
"FFmpeg 命令超时 (%ds): command=%s",
|
||
timeout or -1,
|
||
" ".join(str(c) for c in command[:20]),
|
||
)
|
||
raise
|
||
except subprocess.CalledProcessError as e:
|
||
# 把完整 stderr 打到日志,方便排查 exit code 183 等问题
|
||
stderr_text = (e.stderr or "").strip()
|
||
logger.error(
|
||
"FFmpeg 命令失败: exit_code=%d command=%s\nstderr:\n%s",
|
||
e.returncode,
|
||
" ".join(str(c) for c in command[:20]), # 截断过长的命令
|
||
stderr_text[:5000], # 截断过长的 stderr
|
||
)
|
||
raise
|