Use requests Session with Retry adapter (5 retries, backoff factor 1) and add 30s timeout to both submit and query requests for better reliability against transient network errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
170 lines
5.0 KiB
Python
170 lines
5.0 KiB
Python
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import uuid
|
|
|
|
import requests
|
|
from requests.adapters import HTTPAdapter
|
|
from urllib3.util.retry import Retry
|
|
from boto3 import client as s3_client
|
|
from botocore.config import Config
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
APP_ID = os.getenv("X_API_APP_KEY")
|
|
ACCESS_TOKEN = os.getenv("X_API_ACCESS_KEY")
|
|
RESOURCE_ID = os.getenv("X_API_RESOURCE_ID")
|
|
S3_ENDPOINT = os.getenv("S3_ENDPOINT")
|
|
S3_ACCESS_KEY_ID = os.getenv("S3_ACCESS_KEY_ID")
|
|
S3_SECRET_ACCESS_KEY = os.getenv("S3_SECRET_ACCESS_KEY")
|
|
S3_BUCKET = os.getenv("S3_BUCKET")
|
|
|
|
SUBMIT_URL = "https://openspeech.bytedance.com/api/v3/auc/bigmodel/submit"
|
|
QUERY_URL = "https://openspeech.bytedance.com/api/v3/auc/bigmodel/query"
|
|
|
|
retry = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
|
|
session = requests.Session()
|
|
session.mount("http://", HTTPAdapter(max_retries=retry))
|
|
session.mount("https://", HTTPAdapter(max_retries=retry))
|
|
|
|
s3 = s3_client(
|
|
"s3",
|
|
endpoint_url=S3_ENDPOINT,
|
|
aws_access_key_id=S3_ACCESS_KEY_ID,
|
|
aws_secret_access_key=S3_SECRET_ACCESS_KEY,
|
|
config=Config(signature_version="s3v4"),
|
|
region_name="auto",
|
|
)
|
|
|
|
|
|
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)
|
|
|
|
|
|
def make_headers(task_id, with_sequence=False):
|
|
headers = {
|
|
"X-Api-App-Key": APP_ID,
|
|
"X-Api-Access-Key": ACCESS_TOKEN,
|
|
"X-Api-Resource-Id": RESOURCE_ID,
|
|
"X-Api-Request-Id": task_id,
|
|
}
|
|
if with_sequence:
|
|
headers["X-Api-Sequence"] = "-1"
|
|
return headers
|
|
|
|
|
|
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} ...")
|
|
s3.upload_file(audio_file, S3_BUCKET, obj_name)
|
|
url = s3.generate_presigned_url(
|
|
"get_object",
|
|
Params={"Bucket": S3_BUCKET, "Key": obj_name},
|
|
ExpiresIn=3600,
|
|
)
|
|
|
|
task_id = str(uuid.uuid4())
|
|
headers = make_headers(task_id, with_sequence=True)
|
|
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 = session.post(SUBMIT_URL, json=body, headers=headers, timeout=30)
|
|
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 = session.post(
|
|
QUERY_URL,
|
|
json={},
|
|
headers={
|
|
**make_headers(task_id),
|
|
"X-Tt-Logid": logid,
|
|
},
|
|
timeout=30,
|
|
)
|
|
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", [])
|
|
|
|
os.makedirs("output", exist_ok=True)
|
|
json_path = f"output/{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"output/{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.")
|