Skip to main content

Podcast Maker

播客制作智能体。素材进,成品出:脚本 → 声音 → 字幕 → 画面 → 产物校验,一条链走完。

定位很明确:播客的重点在听,不在看。所以视频侧只提供固定档位(背景、封面、动画、说话人指示、BGM), 够用、稳定、可复现,不追花哨。真正花力气的地方是脚本、时长、字幕和产物校验。


一、它解决什么

上一代播客工具留下的七个问题,这一版逐条堵死。

编号 现象 根因 本版的修法
1 设了 192 kbps,导出实测只有 160 kbps 采样率 22050 Hz 属 MPEG-2 LSF,码率上限 160 kbps,编码器静默钳制 内置采样率-码率上限表,越界在编码前直接报错
2 字幕在词中间断开(Claude-3|.5) 逐字符判定西文 token,- 与 . 不匹配 改为正则区间判定,token 整体不可切
3 音画漂移数秒 用 -shortest 让 ffmpeg 自己决定长度 显式传 -t <音频时长>,并对齐断言
4 调语速后音调变尖 用采样点插值做变速 统一走 atempo,变速不变调
5 字幕不显示中文 把字体文件路径填进 ASS 的 Fontname 传字体族名 + fontsdir,并用真实族名
6 重跑覆盖上一集产物 目录名用序号,已存在就往下写 时间戳 + 标题 slug,已存在即报错
7 视频里看不出谁在说话 无说话人指示 ASS 矢量绘图层,当前说话方高亮

二、安装

需要 Python 3.11 或更新(py -3.11 或 PATH 上那个 python 都认),以及 ffmpeg 放进 PATH。然后:

python -m pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/

(Windows 双击 setup.bat 会自动做这一步并起服务。)

依赖 用途 许可证
edge-tts 备选语音引擎(调微软 Edge 的在线语音接口) LGPL-3.0
Pillow 背景图与封面绘制 HPND
ffmpeg / ffprobe 拼接、混音、编码、合成 LGPL/GPL

主程序的依赖只有这两个包,其余全部走标准库。本地语音服务那套(torch 等,约 4.8 GB) 不进主程序依赖,它待在自己的环境里,见下。

下载源统一写在 tools/sources.py(PyPI 用清华,内有阿里云 / 腾讯云 / 中科大备选; PyTorch 的 CUDA 轮子用上海交大,备选官方源)。脚本与依赖清单里各抄了一份, tests/test_sources.py 盯着几处不许走散。

两条语音路的版权状况不同,差别不在「本地还是云端」:

  • Qwen3-TTS(默认):模型权重与代码均为 Apache-2.0(阿里云通义千问团队), 推理全程在本机,不经过任何第三方服务。音色跟着项目走:每个项目立项后在 合成开工前自动录一段 5~8 秒的参考音频(用模型自带的九个合成音色之一当基准, 不是真人录音;文案固定为陈述+疑问+惊叹三句型混合——Base 克隆会整条继承 ref 的韵律先验,混合文案让输出全局韵律更生动,工具自带念全校验防残废音频), 落在项目的「音色」目录里,整期所有句子都以它为音色源头; 换项目互不影响,要与别的项目一致就把那份目录整个复制过来。用 Base 变体 + ICL 而不是内置音色表,是因为句间音色漂移小一半以上(F0 相对极差 46.9% → 22.5%)。 它那套依赖(PyTorch 等,约 4.8 GB)不进主程序依赖,也不同住一个环境——按需搭建: 配置页把「语音引擎」选成 Qwen3-TTS,那张卡上就会出现环境状态与一个「搭建」按钮, 点一次即可,环境 / 依赖 / 模型都由程序自己装,不需要你预装任何 Python 包。 语气也走这条路:脚本每句的 emotion 标签会折成语气指令交给它(只认真情绪, 语篇功能标签不转——「用过渡的语气说」那种指令对模型是纯噪音)。给不给语气、 给到什么程度,由素材类型的范式卡决定(emotion_level):论述类、教程、论文这些 「不写心情」的文体,合成侧整句不发情绪指令——脚本里即便出现「平静」这类词也不发 (它在那一档是被当作「不加修饰」的代码字填进来的,不是心情);小说、散文这类「只到略」的 文体,拼的是「用略带X的语气说」。实测里「明显/非常」两档方向会反转(同一个词在 肯定句上抬高起伏、在中性句上又压低),所以不进产品。档位只有一个来源——写脚本 能填什么、合成拼什么、约束解码的枚举里有几个标签,取的是同一个值;风格倾向里 「情绪密度」那一项也按它发挥:不写心情的文体只谈功能标签怎么分布,允许略情绪才谈 起伏多大。同一句话的波形 是确定的:种子按 (文本, 音色标识) 派生(克隆时标识是参考音频的内容指纹), 出了问题能照原样复现。 之后不用管它:合成开工前先探一次,没在线就地拉起来,整期(整批)跑完自动停掉、 把显存还回去;已经在跑的服务一律不动。细节见 tts_service/README.md。
  • Edge-TTS(备选):edge-tts 这个 Python 客户端库是 LGPL-3.0;但它合成的 声音来自微软 Edge 浏览器的在线朗读接口——那不是微软公开授权、发放凭据的 API, 而是浏览器自用通道,条款与可用性都由微软单方面决定;产出的音频同样受微软服务条款约束。 想要权属清晰的声音,用本地那条。

设置页如实显示服务状态;起不来时把原因写进设置页与任务日志,不假装可用。 探活分的是环境没建、依赖没装齐、模型没下、只是没在跑四种状态—— 前三样的动作都在配置页那张卡上(点「搭建」),第四样什么都不用做,合成时会自动拉起。 这四句措辞互不相同,因为把「该去下模型」说成「该去装包」就是让人白忙一场。

素材只支持 md / txt / docx。PDF 不支持:它是出版格式而非记录格式,章节层级 是从源文档投影出来的,要拿回来只能反推,而反推就是猜;而且它的文本顺序是排版 顺序(双栏、页边注、页眉页脚都按坐标给),不是阅读顺序。上传 PDF 会明确提示 「请提供源文件」。

Windows 下可直接双击 setup.bat:杀残留进程 → 检查依赖 → 起服务。 (或者直接双击 _run.bat 跳过检查。)


三、启动

python main.py                 # 默认 http://127.0.0.1:8811
python main.py --port 8812     # 指定端口
python main.py --check         # 只测 LLM 后端连通性
python main.py --voices        # 列出可用音色
python main.py --fonts         # 列出可用中文字体
python main.py --continue <树根> --episode 3   # 从锁文件恢复续跑第 3 期

--continue 收的是树根(项目目录,或单集的目录),期号由 --episode 给。省略期号 时从锁文件反推,但只有「恰好一期卡在门禁上」才自动定,其余一律把选择权交回来—— 猜错了就是拿着另一期的产物当这一期的接着做,而这种错在产物上看不出来。

界面是深色顶栏加白画布的单页,四个标签页按制作顺序排列:

标签页 管什么
项目 立项(含规划方式)、项目列表与进度、成稿入库、期数地图与插入、期号、未归属产物认领
脚本 归属项目、要生成哪几期、素材导入、锚点切章、风格预设、目标时长、A/B 称呼与语速、生成与门禁结果、把改过的脚本存回本期
合成 归属项目、要合成哪几期(只列有脚本的期)、本期标题(改,不是填)、出片开关、实时日志、产物校验报告
配置 全部可调项,按命名空间自动分成十五张卡片:写作目标、门禁阈值、片头片尾、项目、语言模型、角色与音色、音频输出、背景音乐、画面输出、背景、动画、说话人指示、字幕、封面、字体

脚本与合成两页只摆最常调的几项,其余全部可调项在「配置」页。 声音与画面不是独立标签,它们是脚本与合成的子配置——摆成并列标签会让人以为 那是一个并列的工序,而不是两页各自的参数。

所有控件的取值范围都由后端下发,前端不硬编码任何选项。地址栏 hash 就是路由 (#project #script #render #config),可以直接把某一页发给别人。

项目与期数

一档节目一个项目。立项就是建项目目录——此后素材、地图、脚本、成品各归其位, 都在这个项目的子文件夹里。项目记住节目名、副标题、风格倾向、音色、称呼与期号,出片时自动往后排:

场景 行为
计划期数留空 由期数地图定:按凝缩与文体做合并与切分,分出几组就是几期
计划期数填了 以人为准,排图照这个数排(超出「地图期数上限」时报错)
压缩档 「写作目标」里选 1:3 / 1:5 / 1:10(默认 1:5):一期装多少原文按档位折算,排图照此核对每期分量——口径:1 = 成稿目标(每期时长 × 标准语速,不含偏移),x = 1 × 1.25 × 档位;上限 = 档位 × 1.25,下限 = 1.25 × 1.2(25 分钟一期约 9900 字原文;不足就写不满,只能靠复读凑字数),破线回炉重排
脚本页选了项目 期号自动取项目的「下一期」,生成的脚本直接挂在该项目下
出片通过门禁 记入项目、下一期号自动进位。未通过的片子不占期号
同一项目再生成 自动续接到下一期,不必手填期号
节目名 / 副标题 整档节目共用一份,卡片上「节目名/副标题」改一次,往后各期画面都跟着变;节目名就是画面上最大的那行
只出一集 照样立项——规划方式选「单集」,不排图、出完即完结。没有第二条路可走
项目设定 vs 全局配置 项目优先,且只作用于本次运行,不回写全局

节目名与副标题不出现在配置页——它们是项目设定,不是全局默认值。改的地方是项目 卡片上的「节目名/副标题」。

项目目录长这样(十个子文件夹在立项那一刻一次建齐):

<输出目录>/
  _projects.json                 登记表:只存身份与设定
  20260912-121051/               目录名即项目 id
    素材/   地图/   脚本/   封面/   背景/
    音视频/ 字幕/   图文/   报告/   过程/

未归属的产物仍单独列出,不自动并入任何项目:归属是人决定的,猜错了比不猜 更糟。这一栏是历史账——磁盘上早先躺着的不归属产物得有人认领;界面上已经不会再产生 新的这类目录(只出一集也是一个项目)。

项目下线有两条路,分得很清,也可以叠加(先归档、再删除):

动作 做什么 产物
归档 只翻一个标记,项目从各处列表里下线 全部保留,随时「恢复」
删除 登记表条目拔掉,项目目录(素材、地图、脚本、各期成品)整个删掉 不留

删除要点两下:第一下只是把按钮点亮(六秒没下文自动熄灭),第二下才动手。删掉的是 一整个目录,没有回收站,所以不提供「一键完成」的省事。

一次做多期

脚本与合成是两个页签,就是两步,不是一步。脚本可以批量生成,改好某一期再合上, 然后批量合成——中间随时可以停下来改某一期。

脚本页与合成页的「选期」都是下拉里带勾选,收起时只显示已选几期,不占地方:

页签 可选的期 批量时的行为
脚本 该项目的全部期(含还没脚本的) 串行生成,单期失败跳过
合成 只列有脚本的期 读盘上定稿那一份,串行出片,单期失败跳过

合成页不列没脚本的期,是为了让「选了一期却没脚本」这个状态压根产生不了——门禁在入口, 不做事后补救。批量跑的时候:

  • 串行。 语音合成与视频渲染吃满机器,并着跑只会互相拖慢。
  • 单期失败跳过,失败的期号与原因记下来,重勾一次就能补。
  • 连续三期同一个原因失败就停下,后面几期不再白跑(模型后端挂了、合成服务没起这类 系统性问题,跳过等于把同一个错误重复 N 遍)。
  • 中止是软的:点「中止」后当前这一期做完就停,不硬杀线程——硬杀会把正写着的文件 截断,半截的清单比没有更坏。

规划方式:立项时定死

立项必须选一种,选定之后不可更改——中途换模式会让已排的地图失去来处。

方式 适用 行为
成稿规划 手上有成稿(一本书、一部报题集),要一次排完 成稿入库 → 排期数地图 → 按图逐期出片,每期素材由落点自动取。排图之前不进脚本页与合成页的可选集
逐期即兴 每期临时定讲什么 每期自己给素材,期号只往后走;立了项就能选
单集 只出一集 不排地图,素材当场给,出完即完结(不再有「下一期」)。同样是一个项目:有节目名、有封面、产物落在自己目录

三种方式回答的是同一个问题——本期素材从哪来、要不要排图。只出一集也走这条路: 从前它是「不选项目」,产物落进一个没人认领的目录、画面印不出节目名;收编之后流程 只剩一条:立项 → 脚本 → 合成。

本功能上线前立的项目读作「未定(旧项目)」,在项目卡片上补选一次即可, 补选之后同样定死。

成稿与期数地图

成稿落在项目的 素材/ 下:一份 index.json 记索引,每份素材一个 <sid>.md。画地图分五步,一次长调用变成一批小调用:

三件长活都在后台跑:素材入库即探查、排图、插入建议——全是「模型慢慢算」的活(探查几分钟、排图几十分钟),点了按钮弹窗里就有实时进度与逐条日志(凝缩到第几节一目了然),弹窗可以收起、任务照跑,跑完自动重开面板。同一项目这些活互相排他(它们写的是同一批探查文件),别的项目不受影响。

  1. 结构从稿子里读出来(#、一、、Word 标题样式),不喂模型。层级归一、同系列 归纳、附录挑出来单列,每条记下它的起止行与所属章节链——都是可查的事实。
  2. 定凝缩的单元层级:按这批素材的文体判断哪一级才算一个完整的表达单元(方法论看 论证链、小说看情节单元、采访看话题板块……判据写在范式卡里)。这一步定的是颗粒:选 篇一级,凝缩 39 次;选章一级,就是 179 次。判据不是"标题看起来像不像一个完整话题" ——越具体的标题越像,照着猜只会把层级判细、把单元切碎。
  3. 逐单元凝缩:一次调用只看一个单元的原文,做的是保逻辑的有损压缩——留下它说清楚了 什么、反对什么、有哪几条主线,以及跟着每条主线的细节区分。几十万字的原文进不了 上下文,几十条凝缩可以——每次的上下文与输出都不随全书体量增长。按哪种文体压,由 范式卡里的 condense 一项给出(与排图用的"重点判据"是两个字段,见下)。 凝缩带着位置落盘:压的是原文的哪一块(第几行到第几行)、属于哪一篇,由取料那一步 实测的范围回填,不是另算一遍。
  4. 模型合并与切分:拿一份凝缩清单(每条带行号范围、所属章节与字数)回答"哪些节合起来 是一期、这一期叫什么、讲什么"。分出几组就是几期——期数由内容结构定,不由字数除。 它不抄标题、不编期号、不算字数,这些都由代码做,所以也就没有"抄错一条落点就取不到料" 这件事。分完做压比体检——每期原文量对照压缩档核对,两个方向各有红线:超过上限 (成稿目标 × 1.25 × 档位)是一期塞不下,低于下限(成稿目标 × 1.25 × 1.2)是料不够——写不满时模型 只能把写过的段落再背一遍凑数。把问题清单喂回去整体重排(凝缩复用,只是重新分组, 最多重试 2 次);重排仍超上限就报错停下,仍低于下限则落警告交人。
  5. 按落点取原文:生成某一期时取回该期涵盖的单元原文(含子节),不截断—— 照地图上标的行号切,切走的与凝缩时看的是同一块。

两级之间只有「章节标题 + 行号」这一个标识,所以 md 上传时标题行会原样保留:# 是结构锚点, 不是装饰,剥掉之后按章切分就无从谈起。地图长这样:

期号 标题 主旨 要点 素材落点
1 链与两头 判断的两头与中间的链 确定性交代码;解释空间给模型 书稿.md · 第一章 链与两头
2 代码编期号 期号错位看不出来 模型编期号会错位且看不出来 书稿.md · 第二章 代码编期号

期号与落点一律由代码填。 模型只给分组、标题、主旨、要点。落点是「素材 + 标题 + 行号」——同一本书里两节都叫「小结」是常态,只按标题取会取到靠前那一条,而地图上写的 是同一串字,错了也看不出来。

期数不是算出来的。 早先的做法是:总字数除「一期容量(目标时长 × 语速)」得期数, 再把「共 N 期,请正好排出 N 期」写进提示词。素材按逻辑需要 100 期时,那等于命令模型把 超出来的内容无声塞进别的期里,每一期都超载。现在分组的结果就是期数;「地图期数 上限」是排完之后的红线,超了报错要求合并,不做排前截断。排图之前倒有一道产能预检: 每期连下限(素材不足成稿目标 × 1.25 × 1.2)都摊不到的,当场报错拦下,不烧一次注定失败的排图; 素材远多于期数能消化的,警告期数偏少,不拦。

漏掉的单元会被补回来。 模型给的分组经 _clean_groups() 归一成一个不重不漏的划分: 越界与重复的序号丢弃、分组间顺序打结时按首个序号重排、没被任何一期提到的单元补成 独立一期。漏掉的不是"少讲一点",是那几节从此不在任何一期里,而地图看上去完整。

凝缩拿不到就停下。 凝缩结果是分组的依据,没有它排出来的图是按字数硬切的——那种图 看着完整,才是最难的假成功。

凝缩要留住"判断合并所需的东西"。 排图那一步只看凝缩、不看原文,所以凝缩里没有 的信息,排图永远看不到:两节讲同一条论证链的不同环节,凝缩里都写成"讲了 X 的重要性", 模型就看不出它们该合。因此凝缩不设长度上限与条数上限——40 字装不下一条带条件与反例的 主干,而"取前 6 条"那道砍在清单里也要撤掉。

插入与排图是同一条管线,只是容器换了。 画地图是在空根容器里建 1、2、3……,插入是 选定锚点期做父容器,在它下面建 3a、3b……。所以插入走的也是那套「逐单元凝缩 → 序号 分组 → 落点由代码映射 → 压比体检」:新素材的每一节先单独凝缩(已凝缩过的节直接用缓存), 模型只判断插在哪一期之后、把序号分组成期,落点由程序按分到的单元直接取用——抄错落点 这类错从根上不存在了。

插入分支

项目定了 60 期之后又发现另一本书可作佐证,可以把它插进现有计划。插入面板自带上传区: 要插的那份还没入库,就地传(md / txt / docx 或粘贴正文),入库并探查完自动勾上它, 不必退到「成稿」面板传完再回来。已经排进地图的素材会标出「已在地图中」并默认不打勾, 不勾任何素材时后端也只处理还没排进地图的——同一份素材插两遍,期号会产出 3a、3b 而 内容与既有各期重复。模型对照带主旨的已有计划简报定插入点并在锚点下分组建期, 看过、改过再落库:

1、2、3、3a、4、…、21、21a、21b、21c、…、60

分支期号由后端编(branch_no()),锚点不变、同一锚点再插一批续用后面的字母 (3a 3b → 3c 3d),超过 26 个进位成双字母(3z → 3aa)。规则只有这一处, 界面与出片阶段都不再各编一套。落库位置固定在锚点分支链的末尾:先插的批排在前面, 后插的接着往下排,不按字母序乱插。有地图的项目,「下一期」由地图决定——分支期号夹在主期 之间,靠数字进位算不出来(bump_episode("3a") 会得到 "4a",把分支当成了新主线)。

接 LLM

界面里选后端,模型名可以从本机可用模型里挑,也可以直接填一个列表里没有的名字:

python main.py --backend lm-studio --model <模型名>
python main.py --backend ollama --base-url http://127.0.0.1:11434 --model qwen3:8b

生成脚本必须走 LLM。没有可用后端时直接报错中断,不会退化成模板填充。

推理型与普通模型都要能跑,这是客户端的责任。 用哪种模型由使用者自己定,程序不替他 决定要不要思考——所以不会主动传"别思考"这类字段(只留一个透传口)。真跑了推理,日志里 带「思考 N token」,代价看得见。

后端之间的差异在这里处理:明确拒绝某个可选字段(HTTP 400/422)时逐个摘掉重试,摘一个 标一个,字段之间不连坐——次序是「辅助字段先摘、约束解码最后」,response_format 是三者里 最值钱的,不能因为后端不认一个 usage 开关就顺手把它丢了(那种情况下 meta.usage_dropped 标的是 usage,不是约束解码)。超时、5xx、返回非 JSON 不降级——它们与字段无关,摘字段治 不了,当成"字段不认"只会在日志上写下一个假结论、把真实起因丢掉。思考与答案混在同一个 content 里的后端,会剥掉成对的思考段,找 JSON 时逐个花括号试解析并取最后一个成立的 (没带思考标记的后端是"先想后答",第一个成功的多半是思考里的草稿)。

超时判「卡死」,不判「慢」。 对话一律走流式:后端吐一个字收一个字,收到就重置静默 计时。两道闸门分工不同——

配置项 判的是 默认
llm.idle_timeout 多久没有下一个字 → 判后端卡死 300 秒
llm.timeout(总时限) 整次生成最久允许多久 → 判写得太久 3600 秒

从前只有一个总时长上限,而且回复要整段等,于是"模型在慢慢写"和"后端已经死了"被同一个数 一起判死:写一期长稿必然撞墙,日志上却写着「后端响应超时」。本机实测(0gm-1.0-35b-a3b 冷加载 + system 1.7k + 素材 20.3k 字)第一个字要等 57.7 秒——预填充本身就要几十秒, 静默时限按最坏情况给,不是按"答一句话要多久"给。

流式中途断掉(没有 [DONE]、也没有 finish_reason)会直接报错并带上已收字数,不把半截 稿子当成品交给下游——否则错误会推迟到「JSON 无法解析」那一步,起因被埋在两个阶段之外。


四、产物

产物按类归位到项目的子文件夹里,不摊在一个「集目录」中:

位置 文件 说明
脚本/ <期号>.json 脚本(含逐句实测时长)。落盘定稿那一份,合成读的就是它
音视频/ <期号>.mp3 / .aac 成片音频(按配置)
音视频/ <期号>.mp4 横屏视频
音视频/ <期号>_v.mp4 竖屏视频(可关)
字幕/ <期号>.srt / .ass 字幕
图文/ <期号>.md 对话体图文
背景/ <期号>_bg.png / _bg_v.png 横竖背景图
背景/ <期号>_bgm.wav 内置背景音乐
封面/ `<期号>_cover_16x9 3x4
报告/ <期号>.manifest.json 本期被要求产出什么(含视频开关)、产物清单与配置快照
报告/ <期号>.report.json 校验报告
过程/ <期号>/ 中间件;blocked.lock.json 门禁未通过时出现在这里

文件名前缀一律是期号(分支期号 3a 也不会与主期撞名)。

各阶段各自落盘:素材入库即落、地图排完即落、脚本生成即落、出片产物各自归位。 脚本要是不落盘,「先生成、改一改、再合成」这条路就走不通——脚本与合成会被绑成一步。

产物校验只要求「这一期被要求产出什么」。关掉视频开关后还去要视频, 每一次出片都会判不过;判不过就不占期号,项目进度永远停在原地, 而磁盘上每份产物看着都好好的。所以视频开关写进 manifest.json, 不适用的检查项直接不进报告,而不是留一行「不适用」占位。


五、门禁

共 21 条(生成阶段 13 条、产物阶段 8 条;其中 fail 17、warn 4,另有 soft 1),分 fail 与 warn 两级。

条目里的 stage 不是分类标签,是执行权:生成阶段的条目只在脚本阶段执行,产物阶段的 只在合成阶段执行,没有谁越界去替对方判一遍。产物阶段 fail 不过即写 blocked.lock.json 硬中断,不产出次品;warn 在严格模式下同样拦截(script.gate_strict)。脚本阶段不过不拦人 ——稿子和问题一起落盘,改稿还是直接出片由人定,合成前只弹一次提醒。此外还有一类 soft:判据本身是估算 值的那些(目前只有总时长偏差一条),不达标只在报告与界面上记「提示·不阻断」,既不拦放行、 也不回灌重写——口径本身只是一个通用语速,拿它把稿子打回去,模型只能围着一个它既测不出、 也控不住的秒数反复改。

严格模式默认开启:宁可不出片,不出错片。

阶段 门禁
脚本生成·形式 JSON 合法、字段完整、同一人连续句数(上限取自素材类型的范式卡)、句长区间、单句时长、情绪标签、情绪档位(不写心情的文体不许出现心情词)、禁用词、可朗读(不含 emoji、图标、不可见字符、网址、命令参数、排版符号)、片头片尾
脚本生成·软项 总时长偏差(≤ 15%,只记账不拦人)
脚本生成·内容 语义检、承诺链检(LLM 一次调用判完;不过就定点修补,检不出句号的交人工)
产物 音画时长差、实测码率、实测采样率、断词率、字体族名、背景水印、产物完整性、封面三尺寸

产物阶段的「产物完整性」与「音画时长差」只在要求视频时才成立;关掉视频开关后 这两项不适用,直接不进报告。要求一个从没被要求的东西,等于让每一期都判不过。

脚本的事归脚本,合成的事归合成。 两个阶段各判各的、各落各的,合成端不碰脚本的账:

脚本阶段 合成阶段
判什么 生成阶段那 13 条(形式项 + 总时长软项 + 内容检) 产物阶段那 8 条
结论落点 过程/<期号>/gate.generate.json 报告/<期号>.report.json
不过怎么办 定点修补:只把点名的句子交给模型改,全篇行数一个字不动;结构坏了才整篇重出;指不出句号的交人工。修满轮次仍未过就把这一版稿子连同结论一起落盘,阶段到此为止 写 blocked.lock.json 硬中断,不产出次品
越界防线 — 不写稿(没稿子就报错,不顺手现写一份)、不判稿(不拿生成阶段的条目再审一遍)

两者之间只有一次提醒:点合成时读那份脚本阶段的结论,有未通过项就弹框逐条列出, 人点「仍然合成」才继续,并在清单里留一个 script_issues_ack 作凭证。提醒不是闸门—— 不点确认只是不发请求,不是判它过了。框里写明这份记录产生于脚本生成那一刻,不追踪此后 的人工修改:改过没有以手上的稿子为准,不为了把它算准而再跑一遍。

总时长偏差留在生成阶段,但按软门禁办:这一阶段才看得见它,可它判的是估算值——真正的 时长要等合成出来才算数,口径本身只是一个通用语速。不达标只记账,不拿它打回重写。

估时长用一把与音色无关的尺子。 脚本阶段的口径是标准语速 4.39 有效字/秒(≈256 汉字/分钟,锚在国家语委《普通话水平测试实施纲要》的正常语速与朗读口径之间)。目标字数 反推、逐句估时、总时长门禁、容量反推都用它——稿子的时长目标不随「这一期换了哪个音色」变, 一份稿子在哪里估都是同一个数。音色之间快慢的差异换算成一个比例,挂在配置页每个音色下拉 框下面(音色语速 ÷ 标准语速:0.91 即比标准慢,1.00 即同速),没有实测的音色照实写 「尚无实测」,不拿 1.00 冒充量过的数。

措辞禁忌为什么必须在提示词里先说。 只在生成完之后扫描命中,模型事先不知道要避开 什么,命中与否全看运气,几轮重试就是几次抽签。所以词表连同「换成什么」一起前置进系统 提示词;回灌时再给到句——第几句、命中了哪个词、改用什么,并明确「只改这几句,其余照抄」。

定点修补那一侧同样要前置。 只把命中句与替换词回灌给修补模型是不够的:它改这一句时 会顺手写进另一个同样说满的词——实测里模型补长一句就写进了「毫无」,回门禁当场被拦。 所以修补的系统提示词带的是同一份禁忌表(同一个渲染结果,不另存一份),回门禁那一道只当兜底。

改一句不该重打一遍。 第 1 轮整篇写;之后能定点就定点——只把门禁点名的句子交给模型, 输出是一份补丁({edits: [{index, text, emotion}]}),回来按句号原地替换,全篇行数一个 字不动。行数不变是这条路的根基:门禁报的「第 N 句」在补丁前后指的是同一行,一旦允许 增删,编号全漂,就得回头重编一遍。

「不许增删」不靠提示词劝,靠补丁的结构里根本写不出来——只有「改哪一句」和「改成什么」, 没有「加一句」和「删一句」。说话人也不在补丁里:它归代码管(enforce_max_run 压连续句、 pin_intro_outro 写死首末句),给模型留这道口子等于让格式轴上出现两个说话的人。

补丁只给被点名句与它前后各两句,不给全篇。这不是省字那么简单,是实测出来的:把全篇 220 句一起递过去,模型三次里有两次直接不吐 JSON(被上下文带跑,改写起解释性文字);只给 窗口则三次全中。纯形式项(措辞、句长)不带素材,内容项(语义、承诺链)才带——改「编造」 得知道素材支持什么,改「太长」只跟这一句自己的字面有关。

哪些问题能定点,只看它带不带句号。 形式门禁全都自带(它本来就是逐句判的);内容检要 模型自己给,给了才算。分成三类各有各的去处:带句号的定点改;结构坏了(JSON 不合法、字段 残缺)只能整篇重出;内容检自己指不出句号的——不动稿子,交人工。让模型为一句它自己都 说不清的问题重写两百句,只是重新摇一次骰子,还会把已经好的句子一起改坏。

每条提示词都按块分类。 一个推进点位牵扯的资料不止一种:要处理的正文、判断用的参考、 上一步的产物,以及这一步当前该产出什么。全堆在一起,模型只能靠语序猜哪块是依据。所以 每一块都自带名字与用途——块名用【】框起(【素材】【已有计划】【待审脚本】),括注说清它 怎么用(「判断依据」「当前任务:按这里逐条改」)。风格、背景这类与具体内容无关的块,在 括注里写明「以当前任务为准」:冲突时让位的必须是它们。

贵的东西按需跑,便宜的东西每轮全跑。 定点修补之后,形式门禁(走代码、毫秒级)每轮 全套重跑——改一句话的字数会牵动单句时长与总时长,改一个用词可能撞上新的禁用词,只重跑 被触发的那一项会放走改出来的新毛病。内容检(走模型、要读整篇)只重判上一轮没过或没判过 的那几项,已经判通过的那一项本轮不重喂,结论从上一轮带过来——不带走的话报告里会凭空少 一行,「这一项检查通过」的记录就丢了。

每轮写完就落盘。 生成一轮要十几分钟,从前等整段跑完才第一次写盘,中途卡住、被中止、 进程被起停脚本杀掉,手上就什么都没有——稿子明明已经写好了。现在每轮定型完就往出片时读的 那个位置写一份,落的是「当前最新的那一版」,与最终返回的那版一致。

内容检为什么挂在脚本生成阶段。 语义检(台词有没有编造素材之外的东西)与承诺链检 (开头开的那个口子收没收),判断依据都在语义层,词汇匹配做不了——对话体前后用词不同 本来就是正常的,拿重叠率去判是拿错了工具。所以这两项交给模型,一次调用同时判完。

位置比判法更要紧:它接在脚本生成的重试回路里,不通过就带着检出的落点去定点修补 (轮数取 script.max_llm_rounds)。等音频视频都渲染完再检,片子已经定死,检出来也改不动。

内容检的输出必须带落点。 每一项问题要给出 {line, quote, problem}:哪一句、那一句 里的原话、毛病是什么。这不是顺手加个字段,是定点修补的前置条件——检查只说「整篇偏题」 而不说哪一句,补丁就无处可下,只能退回整篇重出,而这正是要被根除的那件事。指不出 是哪一句时要求模型老实填 line: 0,代码据此把它归到「交人工」;句号越界也一律归 0, 不许拿第一句或最接近的一句顶替——填了假句号,改稿的人会去改一句没毛病的台词。

模型没给结论,不等于检查通过。 模型漏返回某一项时,那一项记为「未判定」落进待复核, 不默认放行——把「没检查」记成「检查通过」,报告上的绿灯比不检更坏。 判定为不通过的才参与放行(语义检 fail 级、承诺链 warn 级)。

审校的输出预算与写作同一条口径。 取全局 llm.max_tokens,不另设一个小值: 推理型模型把思考过程也算进这份额度,给小了就是思考吃完、答案一个字不剩, 两项检查一同落成「未判定」。它同时是输出上限,服务端校验「输入 + 上限 ≤ 上下文」, 所以该值要落在模型实际加载的上下文之内——模型自身上限与它无关: 加载时给 8192,配置里写 48640,请求要么被拒(HTTP 400 上下文超限), 要么一路生成到塞满。

素材喂多少由素材容量算出来,不由常数定。 内容检与定点修补从前都把素材截到前 6000 字—— 生产里一期素材约 2 万字,只喂进 30%;落在后面的依据在模型眼里就是「不存在」,语义检于是 把本来正确的句子判成编造,再交给定点修补把它改坏。现在按素材容量喂:容量 = 成稿目标 × 1.25 × 压缩档(偏移 1.25 只进算式、不改变参照系的「1」,与排图体检同一把尺),扣掉估算余量与提示词 模板的占用,剩下的才是素材的;后端窗口够不够不靠程序猜——任务开始时日志会给出具体数字 并提醒「请保证后端上下文窗口 ≥ 输出预算 + 素材折算」。字符与 token 的折合比由后端回传的 prompt_tokens 反标,不写死经验值——中文、数字、标点的折合比差得远。

放不下的时候,两条路分工不同。 写作与定点修补不分批:脚本是单篇产物(片头尾写死、 承诺链跨全篇、总字数由目标时长反推),分批写要引入规划层与段间状态传递,那是另一套架构。 素材超出预算时改用凝缩(每节的逻辑骨架)顶替,这是有损的,日志与素材头部都写明。内容检 则分批核:按预算切段、相邻批重叠 1000 字、每批带整篇脚本,合并规则只有一句——同一句 要在每一批里都被报出来才算真问题(某句的依据只落在某一批里,那一批当然找不到它,撤销它 才对;每一批都找不到,才等价于全文里找不到)。批数超过 8 批判「未核」交人工,不把没核的 那部分记成「核过且没问题」。

成稿规划走分段生成,治字数漂移。 整篇一次生成时模型没有全局计数器,实测同一管线 一期超目标 4 倍、另一期只写六成。成稿规划的项目有地图与各节凝缩,脚本这一步先按凝缩 分组规划(模型不报数字,字数配额由程序按凝缩占比分),再逐段生成、逐段按字数核账 (容差与总时长门禁同一把尺),差额滚入下段配额——误差逐段吸收,总账咬住目标。

路线由项目模式定死,不是一个开关:成稿规划(mapped)走分段——有地图与凝缩,逐期 配额、防重复才有依据;逐期即兴与单集走整篇——当场给料、出完即止,没有凝缩可分。成稿 规划万一本期凝缩读不出来(还没排图、版本对不上),也退回整篇照常出稿,不让人卡在 「一步跑不动」上。

无论走哪条路,成稿都要先过一遍程序硬去重:同一句长句在整篇里出现多次时只留第一次。 模型凑数时会把写过的整段再背一遍(实测一期 302 句里 129 句是重复),那不是改写、是复播, 删掉没有任何信息损失。删了会记一笔「重复凑数」:说明这一期的素材其实撑不满目标时长。

其余全部 fail-closed:不过就写 blocked.lock.json,不出片。 门禁未过时,被拒的脚本与逐项明细会一并落盘(script.rejected.json、gate.generate.json), 避免只留一句「门禁未通过」而无从排查。


六、排版与画面

排版是流式的,不是摆坐标。 背景与封面共用同一个纵向流式执行器:每个元素的位置 由上一个元素的实测包围盒推出来,间距一律写成字号的倍数,集中在 LAYOUT 一张表里。

这条约束是有来历的。曾经用「主标题位置加一百一十八像素」表示标题下方框线的位置, 而字号一百零八的字实测高约一百三十——线正好横穿标题字。这类缺陷调数值调不好: 换字号会穿、换画幅会穿、换字体还是会穿。所以位置只能由实测包围盒推出, 间距只能取自规格表,序列里禁止就地写数值,这条有测试守着。

每个元素落下的实测区间会被记录,测试据此断言相邻元素不压叠、框线始终在主标题之下。

最大的一行是节目名,不是期标题。 背景与封面自上而下同一条层级:

主标题 = 节目名(项目)   ← 最大
副标题(项目,可留空)
期标题(地图自动取)
期号 / 版权行             ← 最小

播客卖的是系列品牌,不是这一期讲什么。期标题字数不定,长起来在最大档只能折行, 一期一个样,摆上去就散。字号表按角色命名(hero / second / episode)而不是 按位置,因为主标题换成节目名之后,f_big / f_mid 这种叫法没人分得清。

动画默认静止。 播客重点不在画面上,背景元素还会被缩放带偏。四档动画可选:

档位 说明
静止(默认) 纯静态背景
缓推 全程匀速缩放,幅度可配(默认 1.04)。会带动背景元素位移
波形 半透明声波随音频律动
频谱 频谱条铺底,技术向

缓推档的缩放按整段帧数归一。写成「每帧加固定增量并设上限」会让运动全堆在开头: 六十二秒的片子在第 9.5 秒就撞顶,之后 52 秒完全静止。

字体是可选项,两款各管一处。 配置页「字体」卡片里两个下拉——画面字体(背景 与封面)与字幕字体,都在同一张卡上并排摆着。两个下拉都是自绘的:原生 <select> 的选项不接受自定义字体,浏览器直接忽略选项上的 font-family,整列字长得一模一样, 选字体就成了盲选。现在每个候选项按自己的字形渲染同一句样本,一眼可见长什么样; 本机没装的照样列出来、只是暗显不可选。留空即自动挑一款可用的。

缺字形即报错。 字体缺字形时渲染库不报错,直接画豆腐块。绘制前逐个字符检查覆盖, 缺失就报错并指出是哪个字——换符号、换字体,都比出一版带方块字的封面强。这一条在 自选字体之后更要紧:能挑的字体多了,挑到一款没有中文字形的(Inter、JetBrains Mono 这类)是迟早的事,报错会点名是哪个字。

背景波纹按留白排布,不按像素。 三条正弦波原本各自写死间距与振幅,而间距小于 相邻振幅之和——包络从一开始就是重叠的,再加上千分之三的频率差产生拍频, 相位周期性重合,三条线就绞成一团。现在先算单位振幅,按「相邻波带必须留出正留白」 反解出振幅,周期数按可见宽度归一(换画幅疏密不变),三个周期权重取不成整数比, 避免长期同相。

留白是结构条件而不是手感数值:相邻两条的中线间距取「振幅之和 + 留白」, 于是包络净空恒为 留白 × 单位振幅,与相位、频率、画幅都无关。 几何由 wave_layout() 单独算出,绘制与断言走同一份数据,测试量的是真正落笔的那条折线—— 把留白改小、振幅调大、周期权重取成整数比,三条反证都会立刻报警。


七、目录结构

podcast-maker/
├── main.py                 入口(Web / 自检 / 续跑)
├── setup.bat               一键启动
├── requirements.txt
├── podcast_maker/
│   ├── config_manager.py   配置总表、枚举点位、预设、门禁定义
│   ├── layout.py           项目目录布局的唯一定义处:子目录名(含「音色」)、各期产物路径
│   ├── llm_client.py       LLM 客户端(约束解码、后端兼容、思考段剥离与 JSON 提取)
│   ├── ingest.py           素材导入与锚点切章
│   ├── script_engine.py    脚本生成、格式收束、片头尾写死、门禁、锁文件
│   ├── duration_model.py   字数 ↔ 时长换算、标准语速与音色校准
│   ├── tts_engine.py       语音合成
│   ├── audio_engine.py     拼接、混音、编码
│   ├── subtitle_engine.py  断行、SRT/ASS 生成、时间轴
│   ├── assets_factory.py   背景、封面、BGM(含声波几何 wave_layout)
│   ├── video_engine.py     动画、说话人指示、合成
│   ├── project_store.py    项目登记表:规划方式、期号记忆、期数地图与分支期号、进度
│   ├── source_store.py     素材库:成稿入库、章节清单、按落点取原文(含行号定位)
│   ├── probe.py            结构探查:层级与原文位置、按文体定凝缩单元、准入、逐单元凝缩
│   ├── paradigms.py        素材范式:切分/整合/重点/推进四段(排图)+ condense(凝缩专用)
│   ├── planner.py          期数地图:合并与切分(期数由此定)、压比体检与整体重排、分组归一、落点、插入(与排图同管线)
│   ├── pipeline.py         九步编排与产物校验、后台任务(起任务/进度日志/同项目互斥)
│   └── web_ui.py           界面与 HTTP 接口(含选期、批任务与中止)
├── tools/
│   ├── ui_smoke.py         真实浏览器界面冒烟
│   └── e2e_http.py         走 HTTP 的端到端验收
└── tests/                  单元测试

八、测试

python -m unittest discover -s tests -v    # 单元测试
python main.py --port 8812                 # 另开一个窗口起服务
python tools\ui_smoke.py --url http://127.0.0.1:8812 --shot-dir _smoke
python tools\e2e_http.py  --url http://127.0.0.1:8812
python tools\e2e_http.py  --url http://127.0.0.1:8812 --scratch "$TEMP/pm_e2e/episodes"

--scratch 是验收产物的落点,默认在仓库内的 _smoke/_scratch。若宿主环境对 「非临时路径的删除」设了拦截,脚本开头清理上次残留那一步会被拦下—— 此时把落点指到系统临时区即可,产物内容与判据完全不变。

572 项单元测试,覆盖最容易出错的地方:字数换算、时长模型、字幕断行、编码参数与 BGM 来源解析、 门禁规则、滤镜转义、排版包围盒与动画缩放曲线、配置点位对账、脚本契约、项目期号推进、 规划方式定死(三种)与单集出完即完结、素材类型落库、期数地图与分支期号、 素材索引完整性与按落点取原文、 料源按范式分流(成稿规划忽略手填、逐期即兴与单集用递来的)、 画面字体换一款真的换出另一张图、 项目目录布局与各期产物归位、批量的串行与失败跳过与熔断与软中止、 批量合成读盘上定稿那一份脚本、删除把登记表条目与项目目录一并抹掉、 单元位置与取料口径一致、按文体定凝缩单元层级、 md 锚点存活、结构探查与编号形态、声波包络不相交。

排版测试不看渲染图,只断言实测包围盒:相邻元素不压叠、框线在主标题之下、 字号取六十到二百四十共四档都不越界。

声波测试同样不看渲染图:按实际绘制点逐像素比对相邻两条波的上下关系, 任意 x 上都不允许反转。这一条必须由测试守着——留白被改小、振幅被调大, 界面上只表现为「波浪线又缠上了」,没有任何一处会报错。

配置点位测试做静态对账:扫描管线源码里读过的每个点位,与配置总表逐条比对。 悄悄读一个没声明的点位,取值永远落回默认值、界面上也没有控件去驱动它, 症状只出现在产出的图上——这类缺陷靠人看不出,只能靠对账。同一份对账还管三件事: 每个点位都必须有界面入口、枚举档位的标签不得等于值本身、每个点位都必须有阶段归属。

界面冒烟为什么必须用真浏览器

单元测试跑在 Python 里,看不见界面。而最贵的两类界面缺陷只在浏览器里现形: 控件找不到自己(点号被当成类选择符,getElementById 全返回 null,事件一个都没绑上, 滑块滑了不生效)、下拉只剩英文裸值。所以这一层用 Playwright 跑真浏览器,判据包括:

  • 配置页控件数量与后端下发的点位数一致
  • 界面里每个控件都在登记表里(没有「有控件、没登记」的漏网)
  • 拖动滑块后服务端配置真的变了(回读确认)
  • 枚举下拉的标签不是英文裸值;音色有候选可挑;模型名是下拉且点开能列全部 (判据不是"有个能打字的框"——datalist 那种"输入框 + 候选"是浏览器的补全, 值非空时候选被筛成命中项,框里填着 qwen/qwen3.5-35b-a3b 就只剩它自己, 点开列不出其余 14 个。列表外的名字走末项「手动填写…」)
  • 立项可选三种规划方式且默认选中一项;选「单集」时计划期数与起始期号收起,切回 成稿规划再展开;脚本页有归属项目下拉并给出说明
  • 两款字体下拉都是自绘且每个候选项真的带各自的 font-family——只验「有选项」 会漏掉换回原生 select 时的形状:标签都在、值也对,字却全长一个样
  • 单集卡片不写「下一期」,也没有「改下一期号」按钮
  • 合成页只列有脚本的期(没脚本的期不进可选项)
  • 选期表:全选 / 清空 / 单勾与计数一致,未选项目时整个收起
  • 勾了多期走批任务,不落到单期接口
  • 门禁在接口层也拦得住(缺脚本的批合成、空脚本存盘、中止不存在的任务)
  • 删除要点两下:第一下只点亮、一个请求都不发,第二下才发 action=delete
  • 插入面板自带入库区;已排进地图的素材默认不勾并标出;一份素材都没有时面板照样打开
  • 控件 id 不含点号;无 console error / pageerror

后两条是补齐的教训:脚本页曾经整页读一个不存在的下拉(s-project), 一按生成就抛异常,而配置页那八十几项全都正常。控件数量对得上、值读得出, 都不代表这份界面能被用起来。

选期与批量那几条走的是浏览器内的桩:项目表、期表、批任务接口在页面里就地换掉。 这样「多期是不是真的走了批任务」「合成页是不是真的只列有脚本的期」能在真浏览器里 答出来,而服务端一期活都不干、落盘一个字节都不动——冒烟不该有副作用。桩里另接了 单期接口:多期若从那儿漏过去,判据就记一笔;删除请求也只记不做。

还有一类缺陷上面这些全都照不到:运行时拼出来的 HTML 属性。项目卡片的按钮是 拼串生成的,拼错一个引号,拼串本身仍是合法字符串——语法检查照过,浏览器要到点下去 才在控制台报错,界面上只表现为「点了没反应」。所以另有一道浏览器外的渲染体检 (_smoke/_check_buttons.js):把卡片渲染函数真跑一遍,把拼出来的每个事件属性拿回来 逐个校验语法(修前 24 个里有 2 个不可解析),顺带验下拉的过滤(归档项目、没排图的 成稿项目都不该进选项)。

端到端为什么要走 HTTP

单元测试各测一层,唯独串联处没人测:标题从脚本传到合成、期号从项目记忆里接着往下排。 tools/e2e_http.py 走界面用的那条 HTTP 路径,把两条路都跑通,产物落在临时目录, 跑完还原配置,不动真实产物:

  1. 逐期即兴:立项 → 脚本带标题 → 出片 → 期号前进 → 续接。
  2. 单集:立项(单集)→ 计划期数恒为一期、出完不再排下一期;画面照样印得出节目名。
  3. 成稿规划:立项(成稿规划)→ 上传 md 书稿(走 base64,验锚点还在)→ 排地图 (逐节凝缩后分组,就地核对每期落点都切得出料)→ 生成脚本(素材区已收起,验日志写明 按落点取用、标题取自地图)→ 在插入面板里就地传第二份素材(验库里份数增加、面板自行 重画并勾上它)→ 让模型定插入点 → 落库(故意递一个错的期号,验后端没有采信)→ 核地图 顺序与分支期号。

第二条是补齐的教训:只有真跑一遍才发现,排过地图的项目在生成脚本时仍被"素材为空"拦下。


九、许可

Apache License 2.0,见 LICENSE。第三方组件声明见 NOTICE。

主程序与默认语音引擎这条链路上的许可都是宽松型——主程序 Apache-2.0、Qwen3-TTS 权重 Apache-2.0、推理加速层 faster-qwen3-tts MIT——商用不需要额外授权或付费, 义务只有保留版权与许可声明、修改处标注、附免责声明。唯一的例外是备选的 edge-tts: 库本身 LGPL-3.0,但产出的音频来自微软浏览器自用通道,受微软服务条款约束、权属不清。

要商用就选本地那条,并注意两件许可之外的事:参考音频别用真人录音(那份音频 由程序用模型自带的合成音色生成,走这条链路出来的声音不涉及他人声音权;换成真人 录音去克隆就落进《民法典》第 1023 条声音权保护的范围),发布时按《人工智能生成 合成内容标识办法》加 AI 生成标识。

作者:wUwproject


更新说明

v0.12.2

历史期的成片终于有了试听入口:产物区从前只有「任务刚合成完」一条填充路径,刷新页面、重启服务之后永远显示「尚无产物」。现在历史列表点「报告」同样画出文件链接、成片播放器与门禁表——报告接口本来就算好了这一期的全部产物路径,只是把它扔掉了。更新日志的结构也一并归一:节序、条目形态、引言与计数,15 个版本一个样子。修复一项、变更一项。

修复

  • 历史期成片没有试听入口:产物区(文件链接与 <audio>/<video> 播放器)只由「任务完成」事件填充,历史期的 episode_files 在 api_report 里算好后被丢弃,前端 viewReport 也只画门禁表——文件在手上、两端各扔一半。修复:api_report 把路径表随报告一起回,viewReport 改走 showOutputs,与「刚合成完」共用同一份渲染,不另写第二份。

变更

  • 更新日志结构归一:节序统一为 新增/变更/修复/说明(此前 新增→修复→变更 与 新增→变更→修复 两套并存,v0.11v0.12 各版又各自为政);39 条裸条目补齐加粗短标题(- 配置总表:… → - **配置总表**:…,说明节的说明性条目同样处理);v0.10.0v0.12.1 六版补引言与全版计数、v0.9.0 补计数句;v0.6.0 引言 11 段 1832 字压缩为 3 段。条目内容零信息损失。

Release files for podcast-maker-ldxs 0.12.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for podcast-maker-ldxs 0.12.2
File Interpreter ABI Platform
podcast_maker_ldxs-0.12.2-py3-none-any.whl Python 3 none any Details

Release files / podcast_maker_ldxs-0.12.2-py3-none-any.whl

Download URL podcast_maker_ldxs-0.12.2-py3-none-any.whl
Size 22.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
ef97739764016fa991929cdf873409f38ee14ec8d68d6b6311d9559c05fb9092
BLAKE2b-256 checksum
How to use checksums
14edc64137538997070e310b6e11468f59b97d7a09d847d3679bd0a8f48eec97
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

1.0.0

1 release file

0.42.0

1 release file

0.41.2

1 release file

0.41.1

1 release file

0.40.1

1 release file

0.39.0

1 release file

0.34.4

1 release file

This release

0.12.2 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page