init version for extract audio
This commit is contained in:
171
synthmind/nas_audio_extract_v3.py
Normal file
171
synthmind/nas_audio_extract_v3.py
Normal 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()
|
||||||
Reference in New Issue
Block a user