format
This commit is contained in:
@ -42,11 +42,7 @@ def get_cmd(video_path:str|Path,output_file:str|Path) -> list[str]:
|
||||
output_file = str(output_file.resolve())
|
||||
|
||||
if CFG["manual"] is not None:
|
||||
command=[
|
||||
CFG["ffmpeg"],
|
||||
"-hide_banner",
|
||||
"-i", video_path
|
||||
]
|
||||
command = [CFG["ffmpeg"], "-hide_banner", "-i", video_path]
|
||||
command.extend(CFG["manual"])
|
||||
command.append(output_file)
|
||||
return command
|
||||
@ -56,34 +52,58 @@ def get_cmd(video_path:str|Path,output_file:str|Path) -> list[str]:
|
||||
"-hide_banner",
|
||||
]
|
||||
if CFG["hwaccel"] is not None:
|
||||
command.extend([
|
||||
"-hwaccel", CFG["hwaccel"],
|
||||
])
|
||||
command.extend([
|
||||
"-i", video_path,
|
||||
])
|
||||
command.extend(
|
||||
[
|
||||
"-hwaccel",
|
||||
CFG["hwaccel"],
|
||||
]
|
||||
)
|
||||
command.extend(
|
||||
[
|
||||
"-i",
|
||||
video_path,
|
||||
]
|
||||
)
|
||||
|
||||
if CFG["bitrate"] is not None:
|
||||
|
||||
if CFG['resolution'] is not None:
|
||||
command.extend([
|
||||
"-vf", f"scale={CFG['resolution']}",])
|
||||
command.extend([
|
||||
"-c:v", CFG["codec"],
|
||||
"-b:v", CFG["bitrate"],
|
||||
"-r",CFG["fps"],
|
||||
if CFG["resolution"] is not None:
|
||||
command.extend(
|
||||
[
|
||||
"-vf",
|
||||
f"scale={CFG['resolution']}",
|
||||
]
|
||||
)
|
||||
command.extend(
|
||||
[
|
||||
"-c:v",
|
||||
CFG["codec"],
|
||||
"-b:v",
|
||||
CFG["bitrate"],
|
||||
"-r",
|
||||
CFG["fps"],
|
||||
"-y",
|
||||
])
|
||||
]
|
||||
)
|
||||
else:
|
||||
if CFG['resolution'] is not None:
|
||||
command.extend([
|
||||
"-vf", f"scale={CFG['resolution']}",])
|
||||
command.extend([
|
||||
"-c:v", CFG["codec"],
|
||||
"-global_quality", str(CFG["crf"]),
|
||||
"-r",CFG["fps"],
|
||||
if CFG["resolution"] is not None:
|
||||
command.extend(
|
||||
[
|
||||
"-vf",
|
||||
f"scale={CFG['resolution']}",
|
||||
]
|
||||
)
|
||||
command.extend(
|
||||
[
|
||||
"-c:v",
|
||||
CFG["codec"],
|
||||
"-global_quality",
|
||||
str(CFG["crf"]),
|
||||
"-r",
|
||||
CFG["fps"],
|
||||
"-y",
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
command.extend(CFG["extra"])
|
||||
command.append(output_file)
|
||||
@ -91,7 +111,6 @@ def get_cmd(video_path:str|Path,output_file:str|Path) -> list[str]:
|
||||
return command
|
||||
|
||||
|
||||
|
||||
# 配置logging
|
||||
def setup_logging():
|
||||
log_dir = Path("logs")
|
||||
@ -101,18 +120,16 @@ def setup_logging():
|
||||
stream.setLevel(logging.INFO)
|
||||
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.DEBUG)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(levelname) 7s - %(message)s',
|
||||
handlers=[
|
||||
file,
|
||||
stream
|
||||
]
|
||||
format="%(asctime)s - %(levelname) 7s - %(message)s",
|
||||
handlers=[file, stream],
|
||||
)
|
||||
|
||||
|
||||
def fmt_time(t: float | int) -> str:
|
||||
if t > 3600:
|
||||
return f"{t//3600}h {t//60}min {t%60}s"
|
||||
@ -121,11 +138,12 @@ def fmt_time(t:float|int) -> str:
|
||||
else:
|
||||
return f"{round(t)}s"
|
||||
|
||||
|
||||
def process_video(
|
||||
video_path: Path,
|
||||
compress_dir: Optional[Path] = None,
|
||||
update_func:Optional[Callable[[Optional[int],Optional[str]],None]]=None):
|
||||
|
||||
update_func: Optional[Callable[[Optional[int], Optional[str]], None]] = None,
|
||||
):
|
||||
|
||||
if compress_dir is None:
|
||||
# 在视频文件所在目录下创建 compress 子目录(如果不存在)
|
||||
@ -152,7 +170,7 @@ def process_video(
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
encoding="utf-8",
|
||||
text=True
|
||||
text=True,
|
||||
)
|
||||
|
||||
total = ""
|
||||
@ -163,9 +181,9 @@ def process_video(
|
||||
line += result.stderr.read(1)
|
||||
total += line[-1]
|
||||
# print(line[-1])
|
||||
if 'warning' in line.lower():
|
||||
if "warning" in line.lower():
|
||||
logging.warning(f"[FFmpeg]({video_path_str}): {line}")
|
||||
elif 'error' in line.lower():
|
||||
elif "error" in line.lower():
|
||||
logging.error(f"[FFmpeg]({video_path_str}): {line}")
|
||||
elif "assertion" in line.lower():
|
||||
logging.error(f"[FFmpeg]({video_path_str}): {line}")
|
||||
@ -179,13 +197,20 @@ def process_video(
|
||||
update_func(frame_number, rate)
|
||||
|
||||
if result.returncode != 0:
|
||||
logging.error(f"处理文件 {video_path_str} 失败,返回码: {result.returncode},cmd={' '.join(command)}")
|
||||
logging.error(
|
||||
f"处理文件 {video_path_str} 失败,返回码: {result.returncode},cmd={' '.join(command)}"
|
||||
)
|
||||
output_file.unlink(missing_ok=True)
|
||||
assert result.stdout is not None
|
||||
logging.error(result.stdout.read())
|
||||
logging.error(total)
|
||||
if CFG["hwaccel"] == "mediacodec" and CFG["codec"] in ["h264_mediacodec","hevc_mediacodec"]:
|
||||
logging.info("mediacodec硬件加速器已知在较短片段上存在异常,将禁用加速重试。")
|
||||
if CFG["hwaccel"] == "mediacodec" and CFG["codec"] in [
|
||||
"h264_mediacodec",
|
||||
"hevc_mediacodec",
|
||||
]:
|
||||
logging.info(
|
||||
"mediacodec硬件加速器已知在较短片段上存在异常,将禁用加速重试。"
|
||||
)
|
||||
output_file.unlink(missing_ok=True)
|
||||
bak = CFG.copy()
|
||||
CFG["hwaccel"] = None
|
||||
@ -203,10 +228,12 @@ def process_video(
|
||||
output_file.unlink(missing_ok=True)
|
||||
bak = CFG.copy()
|
||||
CFG["hwaccel"] = None
|
||||
if CFG['codec'].endswith("_mediacodec") or \
|
||||
CFG['codec'].endswith("_qsv") or \
|
||||
CFG['codec'].endswith("_nvenc") or\
|
||||
CFG['codec'].endswith("_amf"):
|
||||
if (
|
||||
CFG["codec"].endswith("_mediacodec")
|
||||
or CFG["codec"].endswith("_qsv")
|
||||
or CFG["codec"].endswith("_nvenc")
|
||||
or CFG["codec"].endswith("_amf")
|
||||
):
|
||||
CFG["codec"] = CFG["codec"].split("_")[0]
|
||||
assert not output_file.exists()
|
||||
ret = process_video(video_path, compress_dir, update_func)
|
||||
@ -217,12 +244,17 @@ def process_video(
|
||||
else:
|
||||
logging.debug(f"文件处理成功: {video_path_str} -> {output_file}")
|
||||
|
||||
except KeyboardInterrupt as e:raise e
|
||||
except KeyboardInterrupt as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logging.error(f"执行 ffmpeg 命令时发生异常, 文件:{str(video_path_str)},cmd={' '.join(map(str,command))}",exc_info=e)
|
||||
logging.error(
|
||||
f"执行 ffmpeg 命令时发生异常, 文件:{str(video_path_str)},cmd={' '.join(map(str,command))}",
|
||||
exc_info=e,
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def traverse_directory(root_dir: Path):
|
||||
video_extensions = set(CFG["video_ext"])
|
||||
sm = None
|
||||
@ -232,14 +264,16 @@ def traverse_directory(root_dir: Path):
|
||||
while que:
|
||||
d = que.pop()
|
||||
for file in d.glob("*") if d.is_dir() else [d]:
|
||||
if file.parent.name == CFG["compress_dir_name"] or file.name == CFG["compress_dir_name"]:
|
||||
if (
|
||||
file.parent.name == CFG["compress_dir_name"]
|
||||
or file.name == CFG["compress_dir_name"]
|
||||
):
|
||||
continue
|
||||
if file.is_file() and file.suffix.lower() in video_extensions:
|
||||
video_files.append(file)
|
||||
elif file.is_dir():
|
||||
que.append(file)
|
||||
|
||||
|
||||
if not video_files:
|
||||
logging.warning("未找到需要处理的视频文件")
|
||||
return
|
||||
@ -265,7 +299,7 @@ def traverse_directory(root_dir: Path):
|
||||
if file in cached_data and cached_data[file] > 0:
|
||||
frames[file] = cached_data[file]
|
||||
continue
|
||||
cmd = f'ffprobe -v error -select_streams v:0 -show_entries stream=avg_frame_rate,duration -of default=nokey=1:noprint_wrappers=1'.split()
|
||||
cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=avg_frame_rate,duration -of default=nokey=1:noprint_wrappers=1".split()
|
||||
cmd.append(str(file.resolve()))
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if proc.returncode != 0:
|
||||
@ -274,19 +308,23 @@ def traverse_directory(root_dir: Path):
|
||||
continue
|
||||
if proc.stdout.strip():
|
||||
try:
|
||||
avg_frame_rate, duration = proc.stdout.strip().split('\n')
|
||||
tmp = avg_frame_rate.split('/')
|
||||
avg_frame_rate, duration = proc.stdout.strip().split("\n")
|
||||
tmp = avg_frame_rate.split("/")
|
||||
avg_frame_rate = float(tmp[0]) / float(tmp[1])
|
||||
if duration == "N/A":
|
||||
duration = 0
|
||||
logging.debug(f"无法获取视频信息: {file}, 时长为N/A,默认使用0s")
|
||||
logging.debug(
|
||||
f"无法获取视频信息: {file}, 时长为N/A,默认使用0s"
|
||||
)
|
||||
duration = float(duration)
|
||||
frames[file] = duration * avg_frame_rate
|
||||
except (ValueError, IndexError) as e:
|
||||
logging.debug(f"解析视频信息失败: {file}, 错误: {e}")
|
||||
frames[file] = 0
|
||||
if 0 in frames.values():
|
||||
logging.warning(f"视频{', '.join([f.name for f,frames in frames.items() if frames==0])}文件帧数信息获取失败。总进度估计将不准确。")
|
||||
logging.warning(
|
||||
f"视频{', '.join([f.name for f,frames in frames.items() if frames==0])}文件帧数信息获取失败。总进度估计将不准确。"
|
||||
)
|
||||
prog.remove_task(task)
|
||||
try:
|
||||
info_file.write_bytes(dumps(frames))
|
||||
@ -296,11 +334,12 @@ def traverse_directory(root_dir: Path):
|
||||
|
||||
logging.debug(f"开始遍历目录: {root_dir}, 共{len(frames)}个视频文件")
|
||||
|
||||
|
||||
# 创建进度条
|
||||
with Progress() as prog:
|
||||
total_frames = sum(frames.values())
|
||||
main_task = prog.add_task("总进度", total=total_frames if total_frames > 0 else len(frames))
|
||||
main_task = prog.add_task(
|
||||
"总进度", total=total_frames if total_frames > 0 else len(frames)
|
||||
)
|
||||
|
||||
# 创建文件队列
|
||||
for file in frames.keys():
|
||||
@ -313,19 +352,27 @@ def traverse_directory(root_dir: Path):
|
||||
else:
|
||||
file_task = prog.add_task(f"{filename}", total=frames[file])
|
||||
|
||||
|
||||
with prog._lock:
|
||||
completed_start = prog._tasks[main_task].completed
|
||||
|
||||
def update_progress(x, rate):
|
||||
if frames[file] == 0:
|
||||
prog.update(file_task,description=f"{filename} 已处理{x}帧 {f'速率{rate}' if rate else ''}")
|
||||
prog.update(
|
||||
file_task,
|
||||
description=f"{filename} 已处理{x}帧 {f'速率{rate}' if rate else ''}",
|
||||
)
|
||||
else:
|
||||
prog.update(file_task,completed=x,description=f"{filename} {f'速率{rate}' if rate else ''}")
|
||||
prog.update(
|
||||
file_task,
|
||||
completed=x,
|
||||
description=f"{filename} {f'速率{rate}' if rate else ''}",
|
||||
)
|
||||
prog.update(main_task, completed=completed_start + x)
|
||||
|
||||
if CFG["save_to"] == "single":
|
||||
process_video(file, root_dir/CFG["compress_dir_name"], update_progress)
|
||||
process_video(
|
||||
file, root_dir / CFG["compress_dir_name"], update_progress
|
||||
)
|
||||
else:
|
||||
process_video(file, None, update_progress)
|
||||
|
||||
@ -338,11 +385,16 @@ def traverse_directory(root_dir: Path):
|
||||
except Exception as e:
|
||||
logging.warning("无法删除视频信息缓存文件", exc_info=e)
|
||||
|
||||
|
||||
def test():
|
||||
os.environ["PATH"] = Path(__file__).parent.as_posix() + os.pathsep + os.environ["PATH"]
|
||||
os.environ["PATH"] = (
|
||||
Path(__file__).parent.as_posix() + os.pathsep + os.environ["PATH"]
|
||||
)
|
||||
|
||||
try:
|
||||
subprocess.run([CFG["ffmpeg"],"-version"],stdout=-3,stderr=-3).check_returncode()
|
||||
subprocess.run(
|
||||
[CFG["ffmpeg"], "-version"], stdout=-3, stderr=-3
|
||||
).check_returncode()
|
||||
except Exception as e:
|
||||
print(__file__)
|
||||
logging.critical("无法运行ffmpeg")
|
||||
@ -352,19 +404,19 @@ def test():
|
||||
f"ffmpeg -hide_banner -f lavfi -i testsrc=duration=1:size={CFG['test_video_resolution']}:rate={CFG['test_video_fps']} -c:v libx264 -y -pix_fmt yuv420p {CFG['test_video_input']}".split(),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
text=True,
|
||||
)
|
||||
if ret.returncode != 0:
|
||||
logging.warning("无法生成测试视频.")
|
||||
logging.debug(ret.stdout)
|
||||
logging.debug(ret.stderr)
|
||||
ret.check_returncode()
|
||||
cmd = get_cmd(CFG["test_video_input"],CFG["test_video_output"],)
|
||||
cmd = get_cmd(
|
||||
CFG["test_video_input"],
|
||||
CFG["test_video_output"],
|
||||
)
|
||||
ret = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
if ret.returncode != 0:
|
||||
logging.error("测试视频压缩失败")
|
||||
@ -374,7 +426,8 @@ def test():
|
||||
exit(-1)
|
||||
os.remove("compress_video_test.mp4")
|
||||
os.remove("compressed_video_test.mp4")
|
||||
except KeyboardInterrupt as e:raise e
|
||||
except KeyboardInterrupt as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
if os.path.exists("compress_video_test.mp4"):
|
||||
os.remove("compress_video_test.mp4")
|
||||
@ -383,11 +436,12 @@ def test():
|
||||
|
||||
|
||||
def exit_pause():
|
||||
if os.name == 'nt':
|
||||
if os.name == "nt":
|
||||
os.system("pause")
|
||||
elif os.name == 'posix':
|
||||
elif os.name == "posix":
|
||||
os.system("read -p 'Press Enter to continue...'")
|
||||
|
||||
|
||||
def main(_root=None):
|
||||
|
||||
atexit.register(exit_pause)
|
||||
@ -396,20 +450,23 @@ def main(_root = None):
|
||||
setup_logging()
|
||||
tot_bgn = time()
|
||||
logging.info("-------------------------------")
|
||||
logging.info(datetime.now().strftime('Video Compress started at %Y/%m/%d %H:%M'))
|
||||
logging.info(datetime.now().strftime("Video Compress started at %Y/%m/%d %H:%M"))
|
||||
|
||||
if CFG_FILE.exists():
|
||||
try:
|
||||
import json
|
||||
|
||||
cfg: dict = json.loads(CFG_FILE.read_text())
|
||||
CFG.update(cfg)
|
||||
except KeyboardInterrupt as e:raise e
|
||||
except KeyboardInterrupt as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logging.warning("Invalid config file, ignored.")
|
||||
logging.debug(e)
|
||||
else:
|
||||
try:
|
||||
import json
|
||||
|
||||
CFG_FILE.write_text(json.dumps(CFG, indent=4))
|
||||
logging.info("Config file created.")
|
||||
except Exception as e:
|
||||
@ -444,9 +501,15 @@ def main(_root = None):
|
||||
logging.info(f"Elapsed time: {fmt_time(tot_end-tot_bgn)}")
|
||||
logging.info("Normal termination of Video Compress.")
|
||||
except KeyboardInterrupt:
|
||||
logging.warning("Error termination via keyboard interrupt, CHECK IF LAST PROCSSING VIDEO IS COMPLETED.")
|
||||
logging.warning(
|
||||
"Error termination via keyboard interrupt, CHECK IF LAST PROCSSING VIDEO IS COMPLETED."
|
||||
)
|
||||
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