VideoCompress V1.1
This commit is contained in:
286
VideoCompress/config.py
Normal file
286
VideoCompress/config.py
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, messagebox, filedialog
|
||||||
|
import main as main_program
|
||||||
|
|
||||||
|
CONFIG_NAME = "config.json"
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"crf": 18,
|
||||||
|
"codec": "h264", # could be h264, h264_qsv, h264_nvenc … etc.
|
||||||
|
"ffmpeg": "ffmpeg",
|
||||||
|
"video_ext": [".mp4", ".mkv"],
|
||||||
|
"extra": [],
|
||||||
|
"manual": None,
|
||||||
|
"train": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
HW_SUFFIXES = ["amf", "qsv", "nvenc"]
|
||||||
|
CODECS_BASE = ["h264", "hevc"]
|
||||||
|
preset_options = {
|
||||||
|
"不使用":["","ultrafast","superfast","veryfast","faster","fast","medium","slow","slower","veryslow",],
|
||||||
|
"AMD": ["","speed","balanced","quality",],
|
||||||
|
"Intel": ["","veryfast","faster","fast","medium","slow",],
|
||||||
|
"NVIDIA": ["","default","slow","medium","fast","hp","hq",]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def config_path() -> str:
|
||||||
|
"""Return path of config file next to the executable / script."""
|
||||||
|
if getattr(sys, "frozen", False): # PyInstaller executable
|
||||||
|
base = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
base = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
return os.path.join(base, CONFIG_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
def load_config() -> dict:
|
||||||
|
try:
|
||||||
|
with open(config_path(), "r", encoding="utf-8") as fp:
|
||||||
|
data = json.load(fp)
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError("config.json root must be an object")
|
||||||
|
return {**DEFAULT_CONFIG, **data}
|
||||||
|
except FileNotFoundError:
|
||||||
|
return DEFAULT_CONFIG.copy()
|
||||||
|
except Exception as exc:
|
||||||
|
messagebox.showwarning("FFmpeg Config", f"Invalid config.json – using defaults.\n{exc}")
|
||||||
|
return DEFAULT_CONFIG.copy()
|
||||||
|
|
||||||
|
|
||||||
|
def save_config(cfg: dict):
|
||||||
|
try:
|
||||||
|
with open(config_path(), "w", encoding="utf-8") as fp:
|
||||||
|
json.dump(cfg, fp, ensure_ascii=False, indent=4)
|
||||||
|
except Exception as exc:
|
||||||
|
messagebox.showerror("配置中心", f"保存失败:\n{exc}")
|
||||||
|
else:
|
||||||
|
messagebox.showinfo("配置中心", "保存成功。")
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigApp(tk.Tk):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
# 设置现代化主题和统一内边距
|
||||||
|
style = ttk.Style(self)
|
||||||
|
style.theme_use('clam')
|
||||||
|
self.title("配置中心")
|
||||||
|
self.resizable(False, False)
|
||||||
|
self.cfg = load_config()
|
||||||
|
|
||||||
|
if "-preset" in self.cfg["extra"]:
|
||||||
|
idx = self.cfg["extra"].index("-preset")
|
||||||
|
self.preset = self.cfg["extra"][idx+1]
|
||||||
|
self.cfg["extra"].pop(idx)
|
||||||
|
self.cfg["extra"].pop(idx)
|
||||||
|
else:
|
||||||
|
self.preset = ""
|
||||||
|
|
||||||
|
self._build_ui()
|
||||||
|
# ── helper --------------------------------------------------------------
|
||||||
|
def _grid_label(self, row: int, text: str):
|
||||||
|
tk.Label(self, text=text, anchor="w").grid(row=row, column=0, sticky="w", pady=2, padx=4)
|
||||||
|
|
||||||
|
def _str_var(self, key: str):
|
||||||
|
var = tk.StringVar(value=str(self.cfg.get(key, "")))
|
||||||
|
var.trace_add("write", lambda *_: self.cfg.__setitem__(key, var.get()))
|
||||||
|
return var
|
||||||
|
|
||||||
|
def _bool_var(self, key: str):
|
||||||
|
var = tk.BooleanVar(value=bool(self.cfg.get(key)))
|
||||||
|
var.trace_add("write", lambda *_: self.cfg.__setitem__(key, var.get()))
|
||||||
|
return var
|
||||||
|
|
||||||
|
def _list_var_entry(self, key: str, width: int = 28):
|
||||||
|
"""Comma‑separated list entry bound to config[key]."""
|
||||||
|
var = tk.StringVar(value=",".join(self.cfg.get(key, [])))
|
||||||
|
|
||||||
|
def _update(*_):
|
||||||
|
self.cfg[key] = [s.strip() for s in var.get().split(",") if s.strip()]
|
||||||
|
|
||||||
|
var.trace_add("write", _update)
|
||||||
|
ent = tk.Entry(self, textvariable=var, width=width)
|
||||||
|
return ent
|
||||||
|
|
||||||
|
# ── UI ------------------------------------------------------------------
|
||||||
|
def _build_ui(self):
|
||||||
|
row = 0
|
||||||
|
padx_val = 6
|
||||||
|
pady_val = 4
|
||||||
|
# 编解码器
|
||||||
|
self._grid_label(row, "编解码器 (h264 / hevc)")
|
||||||
|
codec_base = tk.StringVar()
|
||||||
|
codec_base.set(next((c for c in CODECS_BASE if self.cfg["codec"].startswith(c)), "h264"))
|
||||||
|
codec_menu = ttk.Combobox(self, textvariable=codec_base, values=CODECS_BASE, state="readonly", width=10)
|
||||||
|
codec_menu.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# 显卡品牌(硬件加速)
|
||||||
|
self._grid_label(row, "GPU加速")
|
||||||
|
accel = tk.StringVar()
|
||||||
|
brand_vals = ["不使用", "NVIDIA", "AMD", "Intel"]
|
||||||
|
def get_brand():
|
||||||
|
codec = self.cfg["codec"]
|
||||||
|
if codec.endswith("_nvenc"):
|
||||||
|
return "NVIDIA"
|
||||||
|
elif codec.endswith("_amf"):
|
||||||
|
return "AMD"
|
||||||
|
elif codec.endswith("_qsv"):
|
||||||
|
return "Intel"
|
||||||
|
return "不使用"
|
||||||
|
accel.set(get_brand())
|
||||||
|
accel_menu = ttk.Combobox(self, textvariable=accel, values=brand_vals, state="readonly", width=10)
|
||||||
|
accel_menu.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# CRF或码率选择
|
||||||
|
mode = tk.StringVar(value="crf")
|
||||||
|
if "bitrate" in self.cfg:
|
||||||
|
mode.set("bitrate")
|
||||||
|
def _switch_mode():
|
||||||
|
if mode.get() == "crf":
|
||||||
|
bitrate_ent.configure(state="disabled")
|
||||||
|
crf_ent.configure(state="normal")
|
||||||
|
else:
|
||||||
|
crf_ent.configure(state="disabled")
|
||||||
|
bitrate_ent.configure(state="normal")
|
||||||
|
tk.Radiobutton(self, text="使用 CRF", variable=mode, value="crf", command=_switch_mode).grid(row=row, column=0, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
tk.Radiobutton(self, text="使用码率", variable=mode, value="bitrate", command=_switch_mode).grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# CRF输入框,并在标签中解释参数含义
|
||||||
|
self._grid_label(row, "CRF 值(质量常数,数值越低质量越高)")
|
||||||
|
crf_var = self._str_var("crf")
|
||||||
|
crf_ent = tk.Entry(self, textvariable=crf_var, width=6)
|
||||||
|
crf_ent.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# 码率输入框 (kbit/s)
|
||||||
|
self._grid_label(row, "码率 (例如6M, 200k)")
|
||||||
|
bitrate_var = tk.StringVar(value=str(self.cfg.get("bitrate", "")))
|
||||||
|
def _update_bitrate(*_):
|
||||||
|
if mode.get() == "bitrate":
|
||||||
|
try:
|
||||||
|
self.cfg["bitrate"] = bitrate_var.get().strip()
|
||||||
|
except ValueError:
|
||||||
|
self.cfg.pop("bitrate", None)
|
||||||
|
bitrate_var.trace_add("write", _update_bitrate)
|
||||||
|
bitrate_ent = tk.Entry(self, textvariable=bitrate_var, width=8)
|
||||||
|
bitrate_ent.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# 预设选项
|
||||||
|
self._grid_label(row, "预设 (决定压缩的速度和质量)")
|
||||||
|
preset_var = tk.StringVar(value=self.preset)
|
||||||
|
|
||||||
|
def _update_preset_option(*_):
|
||||||
|
preset_menu['values'] = preset_options[accel.get()]
|
||||||
|
preset_menu.set("")
|
||||||
|
def _update_preset(*_):
|
||||||
|
print(preset_var.get())
|
||||||
|
if self.cfg["extra"].count("-preset")>0:
|
||||||
|
idx = self.cfg["extra"].index("-preset")
|
||||||
|
self.cfg["extra"].pop(idx)
|
||||||
|
self.cfg["extra"].pop(idx)
|
||||||
|
if preset_var.get():
|
||||||
|
self.cfg["extra"].extend(["-preset", preset_var.get()])
|
||||||
|
preset_var.trace_add("write", _update_preset)
|
||||||
|
accel.trace_add("write",_update_preset_option)
|
||||||
|
preset_menu = ttk.Combobox(self, textvariable=preset_var, values=preset_options[get_brand()], state="readonly", width=10)
|
||||||
|
preset_menu.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# ffmpeg路径
|
||||||
|
self._grid_label(row, "ffmpeg 可执行文件")
|
||||||
|
ffmpeg_var = self._str_var("ffmpeg")
|
||||||
|
ffmpeg_ent = tk.Entry(self, textvariable=ffmpeg_var, width=28)
|
||||||
|
ffmpeg_ent.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
ttk.Button(self, text="浏览", command=lambda: self._pick_ffmpeg(ffmpeg_var)).grid(row=row, column=2, padx=2, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# 视频扩展名列表
|
||||||
|
self._grid_label(row, "视频扩展名 (.x,.y)")
|
||||||
|
self._list_var_entry("video_ext").grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# 额外参数列表
|
||||||
|
self._grid_label(row, "额外参数列表")
|
||||||
|
extra_entry = self._list_var_entry("extra")
|
||||||
|
extra_entry.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# 手动参数列表
|
||||||
|
self._grid_label(row, "手动参数列表")
|
||||||
|
manual_var = tk.StringVar(value="" if self.cfg.get("manual") is None else " ".join(self.cfg["manual"]))
|
||||||
|
def _update_manual(*_):
|
||||||
|
txt = manual_var.get().strip()
|
||||||
|
self.cfg["manual"] = None if not txt else txt.split()
|
||||||
|
manual_var.trace_add("write", _update_manual)
|
||||||
|
tk.Entry(self, textvariable=manual_var, width=28).grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 训练模式复选框
|
||||||
|
train_var = self._bool_var("train")
|
||||||
|
tk.Checkbutton(self, text="启用训练(实验性)", variable=train_var).grid(row=row, column=0, columnspan=2, sticky="w", padx=padx_val, pady=pady_val)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
|
||||||
|
# 按钮
|
||||||
|
ttk.Button(self, text="保存", command=lambda: self._on_save(codec_base, accel, mode)).grid(row=row, column=0, pady=8, padx=padx_val)
|
||||||
|
ttk.Button(self, text="退出", command=self.destroy).grid(row=row, column=1, pady=8)
|
||||||
|
_switch_mode() # 初始启用/禁用
|
||||||
|
|
||||||
|
# ── callbacks -----------------------------------------------------------
|
||||||
|
def _on_save(self, codec_base_var, accel_var, mode_var):
|
||||||
|
# 重构codec字符串,同时处理显卡品牌映射
|
||||||
|
base = codec_base_var.get()
|
||||||
|
brand = accel_var.get()
|
||||||
|
brand_map = {"NVIDIA": "nvenc", "AMD": "amf", "Intel": "qsv", "不使用": ""}
|
||||||
|
if brand != "不使用":
|
||||||
|
self.cfg["codec"] = f"{base}_{brand_map[brand]}"
|
||||||
|
else:
|
||||||
|
self.cfg["codec"] = base
|
||||||
|
# 处理码率和crf的配置
|
||||||
|
if mode_var.get() == "crf":
|
||||||
|
self.cfg.pop("bitrate", None)
|
||||||
|
else:
|
||||||
|
br = self.cfg.get("bitrate", "")
|
||||||
|
if not (br.endswith("M") or br.endswith("k")):
|
||||||
|
messagebox.showwarning("警告", "码率参数可能配置错误。例如:3M, 500k")
|
||||||
|
tmp = self.cfg["extra"]
|
||||||
|
idx = 0
|
||||||
|
if len(tmp) == 0:idx = -1
|
||||||
|
else:
|
||||||
|
while True:
|
||||||
|
if tmp[idx] == "-preset":break
|
||||||
|
elif idx+2==len(tmp):
|
||||||
|
idx = -1
|
||||||
|
break
|
||||||
|
else: idx+=1
|
||||||
|
if idx!=-1:
|
||||||
|
preset = self.cfg["extra"][idx+1]
|
||||||
|
if preset not in preset_options[brand]:
|
||||||
|
messagebox.showwarning("警告", "预设(preset)参数可能配置错误!")
|
||||||
|
|
||||||
|
save_config(self.cfg)
|
||||||
|
|
||||||
|
def _pick_ffmpeg(self, var):
|
||||||
|
path = filedialog.askopenfilename(title="Select ffmpeg executable")
|
||||||
|
if path:
|
||||||
|
var.set(path)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
main_program.main()
|
||||||
|
else:
|
||||||
|
app = ConfigApp()
|
||||||
|
app.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,6 +1,7 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
from time import time
|
||||||
@ -15,6 +16,7 @@ ESTI_FILE = Path("esti.out")
|
|||||||
CFG_FILE = Path("config.json")
|
CFG_FILE = Path("config.json")
|
||||||
CFG = {
|
CFG = {
|
||||||
"crf":"18",
|
"crf":"18",
|
||||||
|
"bitrate": None,
|
||||||
"codec": "h264",
|
"codec": "h264",
|
||||||
"extra": [],
|
"extra": [],
|
||||||
"ffmpeg": "ffmpeg",
|
"ffmpeg": "ffmpeg",
|
||||||
@ -34,18 +36,31 @@ def get_cmd(video_path,output_file):
|
|||||||
]
|
]
|
||||||
command.extend(CFG["manual"])
|
command.extend(CFG["manual"])
|
||||||
command.append(output_file)
|
command.append(output_file)
|
||||||
|
return command
|
||||||
|
|
||||||
|
if CFG["bitrate"] is not None:
|
||||||
|
command = [
|
||||||
|
CFG["ffmpeg"],
|
||||||
|
"-hide_banner",
|
||||||
|
"-i", video_path,
|
||||||
|
"-vf", "scale=-1:1080",
|
||||||
|
"-c:v", CFG["codec"],
|
||||||
|
"-b:v", CFG["bitrate"],
|
||||||
|
"-r","30",
|
||||||
|
"-y",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
command = [
|
||||||
|
CFG["ffmpeg"],
|
||||||
|
"-hide_banner",
|
||||||
|
"-i", video_path,
|
||||||
|
"-vf", "scale=-1:1080",
|
||||||
|
"-c:v", CFG["codec"],
|
||||||
|
"-global_quality", str(CFG["crf"]),
|
||||||
|
"-r","30",
|
||||||
|
"-y",
|
||||||
|
]
|
||||||
|
|
||||||
command = [
|
|
||||||
CFG["ffmpeg"],
|
|
||||||
"-hide_banner", # 隐藏 ffmpeg 的横幅信息
|
|
||||||
"-i", video_path,
|
|
||||||
"-vf", "scale=-1:1080", # 设置视频高度为 1080,宽度按比例自动计算
|
|
||||||
"-c:v", CFG["codec"], # 使用 Intel Quick Sync Video 编码
|
|
||||||
"-global_quality", CFG["crf"], # 设置全局质量(数值越低质量越高)
|
|
||||||
"-r","30",
|
|
||||||
"-preset", "slow", # 设置压缩速度为慢(压缩效果较好)
|
|
||||||
"-y",
|
|
||||||
]
|
|
||||||
command.extend(CFG["extra"])
|
command.extend(CFG["extra"])
|
||||||
command.append(output_file)
|
command.append(output_file)
|
||||||
return command
|
return command
|
||||||
@ -70,11 +85,11 @@ def setup_logging():
|
|||||||
log_dir.mkdir(exist_ok=True)
|
log_dir.mkdir(exist_ok=True)
|
||||||
log_file = log_dir / f"video_compress_{datetime.now().strftime('%Y%m%d')}.log"
|
log_file = log_dir / f"video_compress_{datetime.now().strftime('%Y%m%d')}.log"
|
||||||
stream = RichHandler(rich_tracebacks=True,tracebacks_show_locals=True)
|
stream = RichHandler(rich_tracebacks=True,tracebacks_show_locals=True)
|
||||||
stream.setLevel(logging.DEBUG)
|
stream.setLevel(logging.INFO)
|
||||||
stream.setFormatter(logging.Formatter("%(message)s"))
|
stream.setFormatter(logging.Formatter("%(message)s"))
|
||||||
|
|
||||||
file = logging.FileHandler(log_file, encoding='utf-8')
|
file = logging.FileHandler(log_file, encoding='utf-8')
|
||||||
file.setLevel(logging.INFO)
|
file.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
@ -158,7 +173,8 @@ def save_esti():
|
|||||||
# 保存为逗号分隔的文本格式
|
# 保存为逗号分隔的文本格式
|
||||||
ESTI_FILE.write_text(','.join(map(str, coeffs)))
|
ESTI_FILE.write_text(','.join(map(str, coeffs)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("保存估算数据失败", exc_info=e)
|
logging.warning("保存估算数据失败")
|
||||||
|
logging.debug("error at save_esti",exc_info=e)
|
||||||
|
|
||||||
def fmt_time(t:int) -> str:
|
def fmt_time(t:int) -> str:
|
||||||
if t>3600:
|
if t>3600:
|
||||||
@ -193,7 +209,8 @@ def func(sz:int,src=False):
|
|||||||
return fmt_time(t)
|
return fmt_time(t)
|
||||||
except KeyboardInterrupt as e:raise e
|
except KeyboardInterrupt as e:raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("无法计算预计时间",exc_info=e)
|
logging.warning("无法计算预计时间")
|
||||||
|
logging.debug("esti time exception", exc_info=e)
|
||||||
return -1 if src else "NaN"
|
return -1 if src else "NaN"
|
||||||
|
|
||||||
def process_video(video_path: Path):
|
def process_video(video_path: Path):
|
||||||
@ -202,9 +219,9 @@ def process_video(video_path: Path):
|
|||||||
sz=video_path.stat().st_size//(1024*1024)
|
sz=video_path.stat().st_size//(1024*1024)
|
||||||
if esti is not None or TRAIN:
|
if esti is not None or TRAIN:
|
||||||
use = func(sz,True)
|
use = func(sz,True)
|
||||||
logging.debug(f"开始处理文件: {video_path.relative_to(root)},大小{sz}M,预计{fmt_time(use)}")
|
logging.info(f"开始处理文件: {video_path.relative_to(root)},大小{sz}M,预计{fmt_time(use)}")
|
||||||
else:
|
else:
|
||||||
logging.debug(f"开始处理文件: {video_path.relative_to(root)},大小{sz}M")
|
logging.info(f"开始处理文件: {video_path.relative_to(root)},大小{sz}M")
|
||||||
|
|
||||||
|
|
||||||
bgn=time()
|
bgn=time()
|
||||||
@ -218,26 +235,9 @@ def process_video(video_path: Path):
|
|||||||
logging.warning(f"文件{output_file}存在,跳过")
|
logging.warning(f"文件{output_file}存在,跳过")
|
||||||
return use
|
return use
|
||||||
|
|
||||||
# 4x
|
|
||||||
# command = [
|
|
||||||
# "ffmpeg.exe", # 可以修改为 ffmpeg 的完整路径,例如:C:/ffmpeg/bin/ffmpeg.exe
|
|
||||||
# "-hide_banner", # 隐藏 ffmpeg 的横幅信息
|
|
||||||
# "-i", str(video_path.absolute()),
|
|
||||||
# "-filter:v", "setpts=0.25*PTS", # 设置视频高度为 1080,宽度按比例自动计算
|
|
||||||
# "-filter:a", "atempo=4.0",
|
|
||||||
# "-c:v", "h264_qsv", # 使用 Intel Quick Sync Video 编码
|
|
||||||
# "-global_quality", "28", # 设置全局质量(数值越低质量越高)
|
|
||||||
# "-r","30",
|
|
||||||
# "-preset", "fast", # 设置压缩速度为慢(压缩效果较好)
|
|
||||||
# "-y",
|
|
||||||
# str(output_file.absolute())
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# 1x
|
|
||||||
command = get_cmd(str(video_path.absolute()),output_file)
|
command = get_cmd(str(video_path.absolute()),output_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 调用 ffmpeg,并捕获标准输出和错误信息
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
command,
|
command,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
@ -246,16 +246,13 @@ def process_video(video_path: Path):
|
|||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# 检查 ffmpeg 的错误输出
|
|
||||||
if result.stderr:
|
if result.stderr:
|
||||||
# 记录所有警告和错误信息
|
|
||||||
for line in result.stderr.splitlines():
|
for line in result.stderr.splitlines():
|
||||||
if 'warning' in line.lower():
|
if 'warning' in line.lower():
|
||||||
logging.warning(f"[FFmpeg]({video_path}): {line}")
|
logging.warning(f"[FFmpeg]({video_path}): {line}")
|
||||||
elif 'error' in line.lower():
|
elif 'error' in line.lower():
|
||||||
logging.error(f"[FFmpeg]({video_path}): {line}")
|
logging.error(f"[FFmpeg]({video_path}): {line}")
|
||||||
|
|
||||||
# 检查 ffmpeg 执行的返回码
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
logging.error(f"处理文件 {video_path} 失败,返回码: {result.returncode},cmd={' '.join(command)}")
|
logging.error(f"处理文件 {video_path} 失败,返回码: {result.returncode},cmd={' '.join(command)}")
|
||||||
logging.error(result.stdout)
|
logging.error(result.stdout)
|
||||||
@ -297,21 +294,69 @@ def traverse_directory(root_dir: Path):
|
|||||||
|
|
||||||
with Progress() as prog:
|
with Progress() as prog:
|
||||||
task = prog.add_task("压缩视频",total=sm)
|
task = prog.add_task("压缩视频",total=sm)
|
||||||
# prog.print("进度条右侧时间为不精确估算(当所有文件处理时间相同时估算精确)")
|
|
||||||
# 使用 rglob 递归遍历所有文件
|
|
||||||
for file in root_dir.rglob("*"):
|
for file in root_dir.rglob("*"):
|
||||||
if file.parent.name == "compress":continue
|
if file.parent.name == "compress":continue
|
||||||
if file.is_file() and file.suffix.lower() in video_extensions:
|
if file.is_file() and file.suffix.lower() in video_extensions:
|
||||||
t = process_video(file)
|
t = process_video(file)
|
||||||
# if esti is not None:
|
|
||||||
# sm-=t
|
|
||||||
# prog.update(task,advance=1,description=f"预计剩余{fmt_time(sm)}")
|
|
||||||
if t is None:
|
if t is None:
|
||||||
prog.advance(task)
|
prog.advance(task)
|
||||||
else:
|
else:
|
||||||
prog.advance(task,t)
|
prog.advance(task,t)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def test():
|
||||||
|
try:
|
||||||
|
subprocess.run([CFG["ffmpeg"],"-version"],stdout=-3,stderr=-3).check_returncode()
|
||||||
|
except Exception as e:
|
||||||
|
logging.critical("无法运行ffmpeg")
|
||||||
|
exit(-1)
|
||||||
|
try:
|
||||||
|
ret = subprocess.run(
|
||||||
|
"ffmpeg -hide_banner -f lavfi -i testsrc=duration=1:size=1920x1080:rate=30 -c:v libx264 -y -pix_fmt yuv420p compress_video_test.mp4",
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
if ret.returncode != 0:
|
||||||
|
logging.warning("无法生成测试视频.")
|
||||||
|
logging.debug(ret.stdout)
|
||||||
|
logging.debug(ret.stderr)
|
||||||
|
ret.check_returncode()
|
||||||
|
cmd = get_cmd("compress_video_test.mp4","compressed_video_test.mp4",)
|
||||||
|
ret = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
if ret.returncode != 0:
|
||||||
|
logging.error("测试视频压缩失败")
|
||||||
|
logging.debug(ret.stdout)
|
||||||
|
logging.debug(ret.stderr)
|
||||||
|
logging.error("Error termination via test failed.")
|
||||||
|
exit(-1)
|
||||||
|
os.remove("compress_video_test.mp4")
|
||||||
|
os.remove("compressed_video_test.mp4")
|
||||||
|
except Exception as e:
|
||||||
|
if os.path.exists("compress_video_test.mp4"):
|
||||||
|
os.remove("compress_video_test.mp4")
|
||||||
|
logging.warning("测试未通过,继续运行可能出现未定义行为。")
|
||||||
|
logging.debug("Test error",exc_info=e)
|
||||||
|
|
||||||
|
def init_train():
|
||||||
|
global esti
|
||||||
|
if CFG["train"]:
|
||||||
|
train_init()
|
||||||
|
else:
|
||||||
|
if ESTI_FILE.exists():
|
||||||
|
try:
|
||||||
|
# 从文件读取系数
|
||||||
|
coeffs_str = ESTI_FILE.read_text().strip().split(',')
|
||||||
|
esti = [float(coeff) for coeff in coeffs_str]
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"预测输出文件{str(ESTI_FILE)}存在但无法读取", exc_info=e)
|
||||||
|
|
||||||
|
def main(_root = None):
|
||||||
|
global root, esti
|
||||||
setup_logging()
|
setup_logging()
|
||||||
tot_bgn = time()
|
tot_bgn = time()
|
||||||
logging.info("-------------------------------")
|
logging.info("-------------------------------")
|
||||||
@ -326,23 +371,20 @@ if __name__ == "__main__":
|
|||||||
logging.warning("Invalid config file, ignored.")
|
logging.warning("Invalid config file, ignored.")
|
||||||
logging.debug(e)
|
logging.debug(e)
|
||||||
|
|
||||||
# 通过命令行参数传入需要遍历的目录
|
if _root is not None:
|
||||||
if len(sys.argv) < 2:
|
root = Path(_root)
|
||||||
print(f"用法:python {__file__} <目标目录>")
|
|
||||||
logging.warning("Error termination via invalid input.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
root = Path(sys.argv[1])
|
|
||||||
if CFG["train"]:
|
|
||||||
train_init()
|
|
||||||
else:
|
else:
|
||||||
if ESTI_FILE.exists():
|
# 通过命令行参数传入需要遍历的目录
|
||||||
try:
|
if len(sys.argv) < 2:
|
||||||
# 从文件读取系数
|
print(f"用法:python {__file__} <目标目录>")
|
||||||
coeffs_str = ESTI_FILE.read_text().strip().split(',')
|
logging.warning("Error termination via invalid input.")
|
||||||
esti = [float(coeff) for coeff in coeffs_str]
|
sys.exit(1)
|
||||||
except Exception as e:
|
root = Path(sys.argv[1])
|
||||||
logging.warning(f"预测输出文件{str(ESTI_FILE)}存在但无法读取", exc_info=e)
|
|
||||||
|
logging.info("开始验证环境")
|
||||||
|
test()
|
||||||
|
|
||||||
|
init_train()
|
||||||
|
|
||||||
if not root.is_dir():
|
if not root.is_dir():
|
||||||
print("提供的路径不是一个有效目录。")
|
print("提供的路径不是一个有效目录。")
|
||||||
@ -359,4 +401,5 @@ if __name__ == "__main__":
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Error termination via unhandled error, CHECK IF LAST PROCSSING VIDEO IS COMPLETED.",exc_info=e)
|
logging.error("Error termination via unhandled error, CHECK IF LAST PROCSSING VIDEO IS COMPLETED.",exc_info=e)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
Reference in New Issue
Block a user