章节标记(chapters)要有递增的
start与非空title。这篇在写入 Auphonic metadata 前做本地校验,避免整集返工。
先看清成品
- sample_ok → OK;乱序 start 的 sample_bad → INVALID。
- 3 项 pytest 通过。
合规提醒:只处理你有权处理的录音;采访/播客需取得嘉宾与版权授权。Auphonic 按处理时长计费,免费额度与套餐以官网为准,本文不编造分钟数或价格。API Token 等同账号权限,勿提交公开仓库。
运行说明:无正式 Auphonic Token。A/C 为本地元数据与章节校验;B 用 mock 演示 create→upload→start。对官方
auphonic.com/api无效 Token 实测 HTTP 403(Token doesn't exist)。2026-10-23(Asia/Shanghai)pytest 通过。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
chapters_check.py |
章节校验 |
| 样例 JSON | ok/bad |
test_101c.py |
测试 |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.10+ |
关键事实
| 事实 | 来源 |
|---|---|
| 章节可随 metadata 提交;start 支持秒数或时码 | 官方 metadata / 实践 |
| start 必须严格递增 | 本教程规则 |
| 章节文案勿泄露未公开嘉宾隐私 | 合规 |
文件结构
101-auphonic-C-src/
├── chapters_check.py
├── sample_ok.json
├── sample_bad.json
└── test_101c.py
第 1 步:校验脚本
#!/usr/bin/env python3
"""Validate podcast chapter marks (start timestamps + titles) for Auphonic metadata."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
TS = re.compile(r"^(\d{1,2}:)?\d{1,2}:\d{2}(\.\d+)?$|^\d+(\.\d+)?$")
def parse_seconds(start: str) -> float:
s = str(start).strip()
if re.fullmatch(r"\d+(\.\d+)?", s):
return float(s)
parts = s.split(":")
parts = [float(p) for p in parts]
if len(parts) == 3:
return parts[0] * 3600 + parts[1] * 60 + parts[2]
if len(parts) == 2:
return parts[0] * 60 + parts[1]
raise ValueError(start)
def validate(chapters: list) -> list[str]:
errs = []
if not isinstance(chapters, list) or len(chapters) < 1:
return ["chapters 至少 1 条"]
prev = -1.0
for i, ch in enumerate(chapters):
if not isinstance(ch, dict):
errs.append(f"[{i}] 不是对象")
continue
if not str(ch.get("title") or "").strip():
errs.append(f"[{i}] title 为空")
start = ch.get("start")
if start is None or not TS.match(str(start).strip()):
errs.append(f"[{i}] start 格式无效: {start}")
continue
try:
sec = parse_seconds(str(start))
except ValueError:
errs.append(f"[{i}] 无法解析 start")
continue
if sec < prev:
errs.append(f"[{i}] start 未按时间递增 ({sec} < {prev})")
prev = sec
return errs
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("chapters_json")
args = ap.parse_args(argv)
data = json.loads(Path(args.chapters_json).read_text(encoding="utf-8"))
chapters = data if isinstance(data, list) else data.get("chapters") or []
errs = validate(chapters)
if errs:
print("INVALID")
for e in errs:
print("-", e)
return 1
print("OK", len(chapters), "chapters")
return 0
if __name__ == "__main__":
raise SystemExit(main())
第 2 步:测试
from pathlib import Path
import json, subprocess, sys
from chapters_check import validate
HERE = Path(__file__).resolve().parent
def test_ok():
assert validate(json.loads((HERE/"sample_ok.json").read_text())) == []
def test_bad():
assert validate(json.loads((HERE/"sample_bad.json").read_text()))
def test_cli():
r = subprocess.run([sys.executable, str(HERE/"chapters_check.py"), str(HERE/"sample_ok.json")], capture_output=True, text=True)
assert r.returncode == 0
第 3 步:运行
$ python chapters_check.py sample_ok.json
OK 3 chapters
$ python chapters_check.py sample_bad.json
INVALID
- [1] start 未按时间递增 (60.0 < 300.0)
$ pytest -q
... [100%]
3 passed in 0.03s
怎么检查结果?
- OK 无错误。
- bad 指出 start 未递增。
调试示例
从剪辑时间线复制时码却忘了排序。 校验按秒比较;先在表格里按 start 排序再导出 JSON。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 只要 HH:MM:SS | 支持 | 也支持纯秒 |
| 空 title | 拒绝 | 听众看不到意义 |
| 与 preset 冲突 | 少见 | 以网页试听为准 |
模板规则
- 章节标题可读且无剧透敏感信息。
- 提交前本地校验。
- 与音频时长对齐。
项目资源
| 用途 | 地址 |
|---|---|
| API Docs(官方) | https://auphonic.com/api-docs/index.html |
| Help(官方) | https://auphonic.com/help/ |
| Auphonic 官网 | https://auphonic.com/ |
章节整齐,播客导航才专业。