manifest.json里extract audio, transcribe audio 共用一条记录
This commit is contained in:
@@ -56,13 +56,32 @@ def log(msg: str, level: str = "INFO"):
|
||||
print(f"[{ts}] {prefix} {msg}", flush=True)
|
||||
|
||||
|
||||
# ─── Manifest 操作 ─────────────────────────────────────────────────────────────
|
||||
# ─── Manifest 操作 (v2 schema) ────────────────────────────────────────────────
|
||||
#
|
||||
# v2 schema:每个条目形如
|
||||
# {
|
||||
# "extraction": { "extracted": bool, "audio_path": str, "split_segments": [],
|
||||
# "error": str|None, "updated_at": str },
|
||||
# "transcriptions": {
|
||||
# "<model>[:<suffix>]": { "transcribed": bool, "txt_path": str, ... }
|
||||
# }
|
||||
# }
|
||||
# transcribe_audio.py 会把转录结果并入同一条视频记录的 transcriptions 里。
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
MANIFEST_VERSION = 2
|
||||
|
||||
|
||||
def load_manifest(manifest_path: Path) -> dict:
|
||||
if manifest_path.exists():
|
||||
with open(manifest_path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
return {"version": 1, "files": {}}
|
||||
if not manifest_path.exists():
|
||||
return {"version": MANIFEST_VERSION, "files": {}}
|
||||
with open(manifest_path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
# 旧版 manifest (v1) 直接废弃,从头开始(用户已明确接受)
|
||||
if data.get("version", 1) < MANIFEST_VERSION:
|
||||
log("检测到旧版 manifest.json (v1),将以 v2 schema 重新初始化 - 旧记录将被覆盖", "WARN")
|
||||
return {"version": MANIFEST_VERSION, "files": {}}
|
||||
return data
|
||||
|
||||
|
||||
def save_manifest(manifest_path: Path, manifest: dict):
|
||||
@@ -72,34 +91,41 @@ def save_manifest(manifest_path: Path, manifest: dict):
|
||||
|
||||
|
||||
def get_file_entry(manifest: dict, video_path: str) -> dict:
|
||||
"""获取或初始化某个视频文件的 manifest 条目"""
|
||||
"""获取或初始化某个视频文件的 manifest 条目 (v2 schema)"""
|
||||
if video_path not in manifest["files"]:
|
||||
manifest["files"][video_path] = {
|
||||
"audio_extracted": False,
|
||||
"audio_path": None,
|
||||
"split_segments": [], # 切分后的片段列表(若有)
|
||||
"error": None,
|
||||
"updated_at": None,
|
||||
"extraction": None,
|
||||
"transcriptions": {},
|
||||
}
|
||||
return manifest["files"][video_path]
|
||||
entry = manifest["files"][video_path]
|
||||
if "transcriptions" not in entry:
|
||||
entry["transcriptions"] = {}
|
||||
return entry
|
||||
|
||||
|
||||
def mark_done(manifest: dict, manifest_path: Path, video_path: str,
|
||||
audio_path: str, segments: list = None):
|
||||
entry = get_file_entry(manifest, video_path)
|
||||
entry["audio_extracted"] = True
|
||||
entry["audio_path"] = audio_path
|
||||
entry["split_segments"] = segments or []
|
||||
entry["error"] = None
|
||||
entry["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
entry["extraction"] = {
|
||||
"extracted": True,
|
||||
"audio_path": audio_path,
|
||||
"split_segments": segments or [],
|
||||
"error": None,
|
||||
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
}
|
||||
save_manifest(manifest_path, manifest)
|
||||
|
||||
|
||||
def mark_failed(manifest: dict, manifest_path: Path, video_path: str, error: str):
|
||||
entry = get_file_entry(manifest, video_path)
|
||||
entry["audio_extracted"] = False
|
||||
entry["error"] = error
|
||||
entry["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
prev = entry.get("extraction") or {}
|
||||
entry["extraction"] = {
|
||||
"extracted": False,
|
||||
"audio_path": prev.get("audio_path"),
|
||||
"split_segments": prev.get("split_segments") or [],
|
||||
"error": error,
|
||||
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
}
|
||||
save_manifest(manifest_path, manifest)
|
||||
|
||||
|
||||
@@ -220,22 +246,25 @@ def show_status(manifest: dict, all_videos: list):
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 提取音频进度状态")
|
||||
print("=" * 60)
|
||||
done = sum(1 for v in all_videos
|
||||
if manifest["files"].get(v, {}).get("audio_extracted"))
|
||||
failed = sum(1 for v in all_videos
|
||||
if manifest["files"].get(v, {}).get("error"))
|
||||
pending = len(all_videos) - done
|
||||
done, failed = 0, 0
|
||||
for v in all_videos:
|
||||
ext = ((manifest["files"].get(v) or {}).get("extraction")) or {}
|
||||
if ext.get("extracted"):
|
||||
done += 1
|
||||
elif ext.get("error"):
|
||||
failed += 1
|
||||
pending = len(all_videos) - done - failed
|
||||
print(f" 总文件数 : {len(all_videos)}")
|
||||
print(f" 已完成 : {done}")
|
||||
print(f" 失败 : {failed}")
|
||||
print(f" 待处理 : {pending}")
|
||||
print("=" * 60)
|
||||
for v in all_videos:
|
||||
entry = manifest["files"].get(v, {})
|
||||
if entry.get("audio_extracted"):
|
||||
ext = ((manifest["files"].get(v) or {}).get("extraction")) or {}
|
||||
if ext.get("extracted"):
|
||||
status = "✅ 完成"
|
||||
elif entry.get("error"):
|
||||
status = f"❌ 失败: {entry['error']}"
|
||||
elif ext.get("error"):
|
||||
status = f"❌ 失败: {ext['error']}"
|
||||
else:
|
||||
status = "⏳ 待处理"
|
||||
print(f" {status} {Path(v).name}")
|
||||
@@ -292,9 +321,10 @@ def main():
|
||||
pending = []
|
||||
for v in all_videos:
|
||||
entry = get_file_entry(manifest, v)
|
||||
already_done = entry.get("audio_extracted") and entry.get("audio_path") and \
|
||||
mp3_exists(entry["audio_path"])
|
||||
is_failed = bool(entry.get("error"))
|
||||
ext = entry.get("extraction") or {}
|
||||
already_done = ext.get("extracted") and ext.get("audio_path") and \
|
||||
mp3_exists(ext["audio_path"])
|
||||
is_failed = bool(ext.get("error"))
|
||||
|
||||
if already_done:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user