Replace MinIO with S3-compatible storage and reorganize project structure
- Switch from MinIO to boto3 for S3-compatible object storage (Cloudflare R2)
- Rename storage config vars from R2_* to generic S3_*
- Organize root directory: docs/, tools/, output/, Archive/{audio,results}/
- Output transcriptions to output/ directory
- Add transcribe_legacy.py, transcribe_all.py, and docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
147
transcribe_all.py
Normal file
147
transcribe_all.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from minio import Minio
|
||||
|
||||
load_dotenv()
|
||||
|
||||
API_KEY = os.getenv("X_API_KEY")
|
||||
RESOURCE_ID = os.getenv("X_API_RESOURCE_ID")
|
||||
MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT")
|
||||
MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY")
|
||||
MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY")
|
||||
MINIO_BUCKET = os.getenv("MINIO_BUCKET")
|
||||
|
||||
SUBMIT_URL = "https://openspeech.bytedance.com/api/v3/auc/bigmodel/submit"
|
||||
QUERY_URL = "https://openspeech.bytedance.com/api/v3/auc/bigmodel/query"
|
||||
|
||||
client = Minio(
|
||||
MINIO_ENDPOINT,
|
||||
access_key=MINIO_ACCESS_KEY,
|
||||
secret_key=MINIO_SECRET_KEY,
|
||||
secure=True,
|
||||
)
|
||||
|
||||
def fmt_time(ms):
|
||||
s = ms / 1000
|
||||
m = int(s // 60)
|
||||
sec = s % 60
|
||||
return f"{m:02d}:{sec:05.2f}"
|
||||
|
||||
|
||||
def build_markdown(data, audio_file):
|
||||
result = data.get("result", {})
|
||||
utterances = result.get("utterances", [])
|
||||
duration = data.get("audio_info", {}).get("duration", 0)
|
||||
|
||||
speaker_order = []
|
||||
for u in utterances:
|
||||
sp = u.get("additions", {}).get("speaker", "unknown")
|
||||
if sp not in speaker_order:
|
||||
speaker_order.append(sp)
|
||||
|
||||
lines = [
|
||||
"# 转写结果",
|
||||
"",
|
||||
f"**源文件**: {audio_file} ",
|
||||
f"**音频时长**: {fmt_time(duration)} ",
|
||||
f"**说话人数量**: {len(speaker_order)} 人 ",
|
||||
f"**分句数量**: {len(utterances)}",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 按时间线",
|
||||
"",
|
||||
]
|
||||
for u in utterances:
|
||||
sp = u.get("additions", {}).get("speaker", "?")
|
||||
start = u.get("start_time", 0)
|
||||
end = u.get("end_time", 0)
|
||||
t = u.get("text", "").strip()
|
||||
if not t:
|
||||
continue
|
||||
lines.append(f"- **{fmt_time(start)} - {fmt_time(end)}** **说话人{sp}** {t}")
|
||||
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
audio_file = sys.argv[1] if len(sys.argv) > 1 else "2026年05月26日 17点53分.mp3"
|
||||
audio_ext = os.path.splitext(audio_file)[1].lstrip(".")
|
||||
audio_name = os.path.splitext(os.path.basename(audio_file))[0]
|
||||
|
||||
obj_name = f"audio/{uuid.uuid4().hex[:8]}_{os.path.basename(audio_file)}"
|
||||
print(f"Uploading {audio_file} ...")
|
||||
client.fput_object(MINIO_BUCKET, obj_name, audio_file)
|
||||
url = client.presigned_get_object(MINIO_BUCKET, obj_name, expires=timedelta(hours=1))
|
||||
|
||||
task_id = str(uuid.uuid4())
|
||||
headers = {
|
||||
"X-Api-Key": API_KEY,
|
||||
"X-Api-Resource-Id": RESOURCE_ID,
|
||||
"X-Api-Request-Id": task_id,
|
||||
"X-Api-Sequence": "-1",
|
||||
}
|
||||
body = {
|
||||
"user": {"uid": "audioscribe"},
|
||||
"audio": {"url": url, "format": audio_ext},
|
||||
"request": {
|
||||
"model_name": "bigmodel",
|
||||
"enable_itn": True,
|
||||
"enable_punc": True,
|
||||
"enable_ddc": True,
|
||||
"enable_speaker_info": True,
|
||||
"show_utterances": True,
|
||||
},
|
||||
}
|
||||
|
||||
resp = requests.post(SUBMIT_URL, json=body, headers=headers)
|
||||
logid = resp.headers.get("X-Tt-Logid", "")
|
||||
status = resp.headers.get("X-Api-Status-Code", "")
|
||||
print(f"Submit: {status}")
|
||||
|
||||
if status != "20000000":
|
||||
print(f"FAILED: {resp.headers.get('X-Api-Message', '')}")
|
||||
else:
|
||||
while True:
|
||||
resp = requests.post(
|
||||
QUERY_URL,
|
||||
json={},
|
||||
headers={
|
||||
"X-Api-Key": API_KEY,
|
||||
"X-Api-Resource-Id": RESOURCE_ID,
|
||||
"X-Api-Request-Id": task_id,
|
||||
"X-Tt-Logid": logid,
|
||||
},
|
||||
)
|
||||
code = resp.headers.get("X-Api-Status-Code", "")
|
||||
if code == "20000000":
|
||||
data = resp.json()
|
||||
text = data.get("result", {}).get("text", "")
|
||||
utterances = data.get("result", {}).get("utterances", [])
|
||||
|
||||
json_path = f"{audio_name}_转写结果.json"
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
md_path = f"{audio_name}_转写结果.md"
|
||||
with open(md_path, "w", encoding="utf-8") as f:
|
||||
f.write(build_markdown(data, audio_file))
|
||||
|
||||
print(f"Done ({len(text)} chars, {len(utterances)} utterances)")
|
||||
print(f"Saved: {json_path}, {md_path}")
|
||||
break
|
||||
elif code in ("20000001", "20000002"):
|
||||
print(f" Processing... ({code})")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(f"FAILED: {code} {resp.headers.get('X-Api-Message', '')}")
|
||||
break
|
||||
|
||||
print("\nAll done.")
|
||||
Reference in New Issue
Block a user