1. 为什么要在 Cursor 里给 Streamlit 工作台接一条统一通道
Vibe Coding 这个词最近被聊得很多,但真正落到项目里,大多数人卡在同一个地方:Cursor 能帮你写代码,却没法帮你把「模型调用」这件事管起来。你写一个 Streamlit 视频处理工作台,前端要调 yt-dlp 解析链接、要调 FFmpeg 转码合并,中间还想让模型帮你做提示词优化、格式判断、错误解释,这时候如果每个功能都单独去配一套 Key、一套请求地址,项目还没跑起来,配置文件已经乱成一团。
这篇要解决的就是这个问题。我会带你在 Cursor 里,通过一份settings.json骨架,把 TaoToken 作为统一的 Key 与 API 通道接进来,然后驱动一个 Streamlit 前端,调用 yt-dlp 完成视频下载、调用 FFmpeg 完成转码合并。整条链路是:Cursor 负责生成与迭代代码,TaoToken 负责统一模型入口,Streamlit 负责界面,yt-dlp 和 FFmpeg 负责本地工具链。
适合谁看?如果你已经会用 Python 写点小工具,知道 Streamlit 大概是什么,但没试过把「对话生成」和「本地命令行工具」串成一条完整流程,那这篇就是给你准备的。全程可复制,配置片段、页面骨架、验证动作都会给到,跑通一次你就能照着改出自己的版本。
我试过把模型调用散落在各个文件里,后期改一个地址要翻五个地方,所以这次一开始就把通道收敛到一处。
2. TaoToken 前置:把 Key 和 API 通道先准备好
在写任何 Streamlit 代码之前,先把模型侧的入口固定下来。TaoToken 在这里扮演的角色是「统一 Key / API 通道」——你不需要在项目里到处写不同的服务地址,只需要一个 API Key 和一个基础地址,后续所有模型调用都走它。
官网入口在这里,注册和查看文档都从这进:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=API 基础地址(注意这个不带 UTM,配置里直接用它):
https://taotoken.net/api你需要做的准备动作只有两步。第一步,在控制台创建一个 API Key,建议单独为这个项目建一个,方便后面轮换和排查。第二步,把 Key 记下来,等会儿写进settings.json,不要硬编码进 Python 文件。
创建 Key 的入口:
https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite如果你后面想长期用 Cursor 做编码类任务,比如让模型帮你批量改 Streamlit 组件、生成 yt-dlp 的 format 过滤逻辑,可以顺手了解一下 Coding Plan,它更适合这种持续性的编码场景:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite注意:API Key 属于敏感信息,写进配置文件后记得把该文件加入
.gitignore,不要提交到公开仓库。
到这里前置就结束了,你手里应该有一个可用的 Key 和一个基础地址。接下来进入真正的配置环节。
3. 可复制配置:settings.json 骨架与 Streamlit 页面结构
这一节是全文的核心,分两块:一块是 Cursor 侧的settings.json骨架,一块是 Streamlit 页面骨架。两块拼起来,就是「对话生成 → 本地工具链联动」的最小可跑单元。
3.1 settings.json 骨架
在项目根目录建一个.cursor文件夹,里面放settings.json。这份配置的作用是把模型通道、项目约定、工具链路径集中管理,Cursor 在生成代码时会参考它。
{ "taotoken": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "default_model": "claude-sonnet", "timeout_seconds": 60 }, "project": { "name": "streamlit-video-workbench", "python_version": "3.11", "entry": "main.py", "components_dir": "components" }, "toolchain": { "yt_dlp": { "binary": "yt-dlp", "cookies_file": "cookies.txt", "output_template": "downloads/%(title)s.%(ext)s" }, "ffmpeg": { "binary": "ffmpeg", "auto_detect": true, "merge_format": "mp4" } }, "conventions": { "language": "zh-CN", "comment_style": "detailed", "type_hints": true } }几个关键点解释一下。api_key_env写的是环境变量名而不是 Key 本身,这样配置文件可以安全地进版本库,真正的 Key 放在系统环境变量里。toolchain段落把 yt-dlp 和 FFmpeg 的路径、输出模板、合并格式都声明出来,后面 Python 代码读取这份配置,就不用到处写魔法字符串。
环境变量这样设置(Linux / macOS):
export TAOTOKEN_API_KEY="你的Key"Windows PowerShell:
$env:TAOTOKEN_API_KEY="你的Key"3.2 读取配置的 Python 模块
建一个config.py,负责把settings.json和环境变量读进来,供其他模块调用。
import json import os import shutil from pathlib import Path from typing import Any BASE_DIR = Path(__file__).resolve().parent SETTINGS_PATH = BASE_DIR / ".cursor" / "settings.json" def load_settings() -> dict[str, Any]: """读取 settings.json,并注入环境变量中的 API Key。""" with open(SETTINGS_PATH, "r", encoding="utf-8") as f: settings = json.load(f) key_env = settings["taotoken"]["api_key_env"] api_key = os.getenv(key_env) if not api_key: raise EnvironmentError(f"环境变量 {key_env} 未设置,请先配置 TaoToken API Key") settings["taotoken"]["api_key"] = api_key return settings def detect_ffmpeg() -> str | None: """自动检测系统 FFmpeg 路径,找不到返回 None。""" return shutil.which("ffmpeg") def ensure_download_dir() -> Path: """确保下载目录存在。""" out_dir = BASE_DIR / "downloads" out_dir.mkdir(parents=True, exist_ok=True) return out_dir这段代码做了三件事:读配置、校验 Key 是否存在、检测 FFmpeg。detect_ffmpeg用shutil.which走系统 PATH,如果返回 None,说明你还没装 FFmpeg 或者没配环境变量,后面页面里会给出提示。
3.3 Streamlit 页面骨架
建main.py,这是入口。页面结构分三块:链接输入区、解析结果区、下载操作区。
import streamlit as st from config import load_settings, detect_ffmpeg, ensure_download_dir from components.input_section import render_input_section from components.downloader import render_download_section st.set_page_config(page_title="视频处理工作台", layout="wide") st.title("视频处理工作台") st.caption("粘贴链接 → 解析信息 → 选择清晰度 → 下载并转码") settings = load_settings() ffmpeg_path = detect_ffmpeg() if ffmpeg_path is None: st.warning("未检测到 FFmpeg,请先安装并配置到系统 PATH,否则合并功能不可用。") else: st.success(f"FFmpeg 已就绪:{ffmpeg_path}") ensure_download_dir() metadata = render_input_section() if metadata and not metadata.get("error"): render_download_section(metadata, settings)页面骨架本身很薄,真正的逻辑放在components目录下的两个模块里。这样做的好处是,Cursor 在帮你迭代时,每次只需要聚焦一个文件,不会把整个项目搅在一起。
3.4 输入与解析组件
components/input_section.py负责接收链接、调用 yt-dlp 解析、返回结构化元数据。
import streamlit as st from yt_dlp import YoutubeDL from yt_dlp.utils import DownloadError, ExtractorError from typing import Any def extract_video_metadata(url: str) -> dict[str, Any]: """纯后端函数,可被下载模块复用。""" ydl_opts = { "quiet": True, "no_warnings": True, "extract_flat": False, "ignoreerrors": False, } try: with YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=False) except (DownloadError, ExtractorError) as e: return {"error": f"解析失败:{e}"} except Exception as e: return {"error": f"未知错误:{e}"} formats = [] for f in info.get("formats", []): if f.get("vcodec") != "none" and f.get("height"): formats.append({ "id": f["format_id"], "label": f"{f['height']}p {f.get('fps', '')}fps", "height": f["height"], }) formats.sort(key=lambda x: x["height"], reverse=True) return { "title": info.get("title"), "thumbnail": info.get("thumbnail"), "duration": info.get("duration"), "extractor": info.get("extractor_key") or info.get("ie_key"), "formats": formats, "error": None, } def render_input_section() -> dict[str, Any] | None: url = st.text_input("粘贴视频链接", placeholder="支持 YouTube、Bilibili 等") if st.button("解析视频") and url: with st.spinner("正在解析..."): result = extract_video_metadata(url) if result.get("error"): st.error(result["error"]) return None st.session_state["metadata"] = result st.session_state["source_url"] = url return st.session_state.get("metadata")这里把「输入」和「解析」合在一个组件里,好处是输入即校验,用户点一下就知道链接能不能用。extract_video_metadata是纯函数,下载模块可以直接 import 复用,不用重复写解析逻辑。
3.5 下载与转码组件
components/downloader.py负责根据用户选择的清晰度,调用 yt-dlp 下载,再用 FFmpeg 合并。
import streamlit as st from yt_dlp import YoutubeDL from config import ensure_download_dir from typing import Any def render_download_section(metadata: dict[str, Any], settings: dict[str, Any]) -> None: st.subheader(metadata["title"]) if metadata.get("thumbnail"): st.image(metadata["thumbnail"], width=320) formats = metadata.get("formats", []) if not formats: st.info("没有可用的视频格式。") return labels = [f["label"] for f in formats] choice = st.selectbox("选择清晰度", labels) selected = formats[labels.index(choice)] if st.button("开始下载"): out_dir = ensure_download_dir() ydl_opts = { "format": f"{selected['id']}+bestaudio/best", "outtmpl": str(out_dir / "%(title)s.%(ext)s"), "merge_output_format": settings["toolchain"]["ffmpeg"]["merge_format"], "quiet": True, } with st.spinner("下载并合并中..."): try: with YoutubeDL(ydl_opts) as ydl: ydl.download([st.session_state["source_url"]]) st.success(f"完成,文件已保存到 {out_dir}") except Exception as e: st.error(f"下载失败:{e}")format参数用+bestaudio/best的组合,让 yt-dlp 自动把视频流和音频流分开下载,再交给 FFmpeg 合并成 MP4。merge_output_format从配置里读,保持和settings.json一致。
4. 验证请求:一次端到端跑通
配置写完了,接下来验证。整个过程分四步,每一步都有明确的成功标志。
第一步,安装依赖。在项目根目录建requirements.txt:
streamlit>=1.30 yt-dlp>=2024.1.1然后执行:
pip install -r requirements.txt第二步,确认 FFmpeg 可用:
ffmpeg -version能打印出版本信息就说明 PATH 配好了。如果提示找不到命令,去 FFmpeg 官网下载对应平台的包,把bin目录加进系统环境变量。
第三步,启动 Streamlit:
streamlit run main.py第一次运行会弹一个邮箱输入框,直接回车跳过即可。浏览器会自动打开http://localhost:8501。
第四步,端到端验证。在输入框粘贴一个视频链接,点「解析视频」。成功标志是页面显示标题、封面、时长和清晰度下拉框。选一个清晰度,点「开始下载」,进度条走完后,downloads目录里应该出现一个合并好的 MP4 文件。
如果解析阶段报错,先看错误信息里是不是提示需要 cookies。部分平台对未登录请求有限制,这时候需要导出cookies.txt放到项目根目录,然后在ydl_opts里加上:
"cookiefile": "cookies.txt",加上之后重新解析,通常就能拿到元数据了。
5. 本篇常见错排查
跑不通的时候,大部分问题集中在下面几个点,对照着查基本能定位。
FFmpeg 找不到。现象是页面顶部出现黄色警告,或者下载到最后一步报ffmpeg not found。原因是 FFmpeg 没装或者没进 PATH。解决方式是先跑ffmpeg -version确认,装好后重启终端和 Streamlit。
解析报 403 或需要登录。现象是extract_video_metadata返回「解析失败」。原因是平台风控。解决方式是导出 cookies 文件,在ydl_opts里加cookiefile字段。
下载完成但没有声音。现象是 MP4 能播但没音轨。原因是format参数只选了视频流。检查ydl_opts["format"]是不是{id}+bestaudio/best这种组合写法。
API Key 未设置。现象是启动时抛EnvironmentError。原因是环境变量没配。回到第 3.1 节,确认TAOTOKEN_API_KEY已经 export,并且当前终端能读到。
Streamlit 改了代码不生效。现象是页面还是旧逻辑。原因是缓存。在页面右上角菜单点「Rerun」,或者终端里 Ctrl+C 重启。
提示:排查时优先看终端输出,Streamlit 的报错堆栈会打印在启动它的那个终端里,比页面上的提示详细得多。
如果你在接入环节反复卡住,可以直接对照接入文档确认参数格式:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite想先验证模型通道本身是否通,可以用模型对话页面发一条测试消息:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite6. 把这条链路用起来
整套流程跑通之后,你会发现真正的价值不在于「写了一个下载工具」,而在于你拥有了一条可复用的骨架:Cursor 负责生成和迭代,TaoToken 负责统一模型入口,Streamlit 负责界面,yt-dlp 和 FFmpeg 负责本地执行。下次你想做字幕提取、批量转码、封面下载,只需要在components目录下加一个新模块,主入口挂上去就行。
几个实用建议。第一,把settings.json里的toolchain段落当成项目契约,所有路径和格式都从这里读,别在代码里写死。第二,extract_video_metadata这种纯函数尽量和 UI 解耦,方便单测和复用。第三,API Key 永远走环境变量,配置文件只存变量名。
如果你打算长期用这套方式做编码类项目,Coding Plan 会比按次调用更顺手:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite需要新建或轮换 Key 的时候,回到控制台操作:
https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite最后一步,把downloads目录加进.gitignore,把cookies.txt也加进去,然后提交你的第一版。跑通一次之后,剩下的就是在这个骨架上不断加功能了。