init version for extract audio

This commit is contained in:
2026-08-23 20:43:06 +08:00
parent 9286f0b5b2
commit 86c0dd1c53

View File

@@ -0,0 +1,171 @@
#!/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()