Files
atlas/synthmind/nas_audio_extract_v3.py

172 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
NAS 视频音频批量提取 v3
流程: ssh cat .mp4 → 本地文件 → FFmpeg → shell重定向上传 → 删除本地
"""
import subprocess
import os
import time
NAS_BASE = "/volume2/work/Public Cloud Learning Sessions"
MAC_MP4 = os.path.expanduser("~/.openclaw/temp/xingshu/nas_mp4")
MAC_MP3 = os.path.expanduser("~/.openclaw/temp/xingshu/nas_mp3_out")
MAC_LOG = os.path.expanduser("~/.openclaw/temp/xingshu/logs/nas_audio_v3.log")
PROGRESS = os.path.expanduser("~/.openclaw/temp/xingshu/nas_audio_v3.done")
SSH_USER = "shenwei"
NAS_HOST = "192.168.3.17"
os.makedirs(MAC_MP4, exist_ok=True)
os.makedirs(MAC_MP3, exist_ok=True)
FFMPEG_BIN = "/opt/homebrew/bin/ffmpeg"
FFMPEG_CMD = [FFMPEG_BIN, "-y", "-i", "pipe:0",
"-vn", "-acodec", "libmp3lame", "-ab", "64k", "-ar", "22050", "-ac", "1",
"-f", "mp3", "pipe:1"]
def log(msg):
ts = time.strftime("%H:%M:%S")
line = f"[{ts}] {msg}"
print(line)
with open(MAC_LOG, "a") as f:
f.write(line + "\n")
def ssh_download(nas_path, local_path):
"""NAS 文件通过 ssh cat 落地到本地(1MB 分块)"""
cmd = ["ssh", f"{SSH_USER}@{NAS_HOST}", f"cat '{nas_path}'"]
with open(local_path, "wb") as fout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
while True:
chunk = proc.stdout.read(1024 * 1024)
if not chunk:
break
fout.write(chunk)
proc.stdout.close()
proc.wait()
return proc.returncode == 0
def ssh_upload(local_path, nas_path):
"""本地文件通过 shell 重定向上传到 NAS(本地路径需加引号防空格)"""
cmd = f'ssh {SSH_USER}@{NAS_HOST} "cat > \'{nas_path}\'" < "{local_path}"'
return subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL).returncode == 0
def get_nas_mp3s():
r = subprocess.run(["ssh", f"{SSH_USER}@{NAS_HOST}",
f"ls '{NAS_BASE}'/*.mp3 2>/dev/null"],
capture_output=True, text=True)
if r.returncode != 0:
return set()
return set(l.strip() for l in r.stdout.split("\n") if l.strip())
def get_nas_mp4s():
r = subprocess.run(["ssh", f"{SSH_USER}@{NAS_HOST}",
f"ls '{NAS_BASE}'/*.mp4 2>/dev/null"],
capture_output=True, text=True)
if r.returncode != 0:
return []
return [l.strip() for l in r.stdout.split("\n") if l.strip()]
def load_done():
if os.path.exists(PROGRESS):
with open(PROGRESS) as f:
return set(f.read().splitlines())
return set()
def save_done(done_set):
with open(PROGRESS, "w") as f:
f.write("\n".join(done_set))
def main():
log("=" * 60)
log("🚀 v3 开始批量提取")
done = load_done()
nas_mp3s = get_nas_mp3s()
all_mp4s = get_nas_mp4s()
pending = []
for v in all_mp4s:
name = os.path.basename(v)
mp3_name = name.replace(".mp4", ".mp3")
nas_mp3_path = f"{NAS_BASE}/{mp3_name}"
if name not in done and nas_mp3_path not in nas_mp3s:
pending.append(name)
total = len(all_mp4s)
done_count = total - len(pending)
log(f"📊 总体: {done_count}/{total} 已完成,{len(pending)} 待处理")
if not pending:
log("✅ 全部已完成")
return
success, failed = 0, []
for i, video in enumerate(pending, 1):
mp3_name = video.replace(".mp4", ".mp3")
nas_mp4 = f"{NAS_BASE}/{video}"
nas_mp3 = f"{NAS_BASE}/{mp3_name}"
local_mp4 = f"{MAC_MP4}/{video}"
local_mp3 = f"{MAC_MP3}/{mp3_name}"
log(f"\n[{i}/{len(pending)}] ▶ {video}")
try:
# Step 1: 下载 .mp4 → 本地
log(f" 📥 downloading...")
if not ssh_download(nas_mp4, local_mp4):
raise RuntimeError("download failed")
size_mp4 = os.path.getsize(local_mp4)
log(f" ✅ downloaded {size_mp4//1024//1024}MB")
# Step 2: FFmpeg 提取 .mp3
log(f" 🔊 extracting...")
with open(local_mp3, "wb") as fout:
proc = subprocess.Popen(["/bin/cat", local_mp4],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
ffmpeg = subprocess.Popen(FFMPEG_CMD,
stdin=proc.stdout, stdout=fout, stderr=subprocess.DEVNULL)
proc.stdout.close()
ret = ffmpeg.wait()
proc.wait()
if ret != 0:
raise RuntimeError("ffmpeg failed")
# 删除 .mp4 释放空间
os.remove(local_mp4)
size_mp3 = os.path.getsize(local_mp3)
log(f" ✅ extracted {size_mp3//1024//1024}MB ({size_mp3//1024}KB)")
# Step 3: 上传 .mp3 → NAS
log(f" 📤 uploading...")
if not ssh_upload(local_mp3, nas_mp3):
raise RuntimeError("upload failed")
log(f" ✅ uploaded to NAS")
# 删除 .mp3
os.remove(local_mp3)
done.add(video)
save_done(done)
success += 1
log(f" 📊 进度: {done_count + success}/{total}")
except Exception as e:
log(f" ❌ {e}")
failed.append(video)
for p in [local_mp4, local_mp3]:
if os.path.exists(p):
os.remove(p)
log(f"\n{'='*60}")
log(f"🏁 完成: {success}/{len(pending)},失败: {len(failed)}")
if failed:
log(f"失败列表: {failed}")
if __name__ == "__main__":
main()