AI 工具

Auphonic 章节标记:校验时间顺序与标题

章节标记(chapters)要有递增的 start 与非空 title。这篇在写入 Auphonic metadata 前做本地校验,避免整集返工。

BINBHO · 图文教程

章节标记(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/

章节整齐,播客导航才专业。