惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
V
V2EX
Cloudbric
Cloudbric
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
量子位
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
K
Kaspersky official blog
博客园 - 【当耐特】
T
Tenable Blog
L
Lohrmann on Cybersecurity
The Cloudflare Blog
S
Schneier on Security
A
Arctic Wolf
Latest news
Latest news
C
Cyber Attacks, Cyber Crime and Cyber Security
罗磊的独立博客
T
The Exploit Database - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
Simon Willison's Weblog
Simon Willison's Weblog
雷峰网
雷峰网
NISL@THU
NISL@THU
人人都是产品经理
人人都是产品经理
月光博客
月光博客
J
Java Code Geeks
V
Visual Studio Blog
S
Security Affairs
博客园 - Franky
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
H
Heimdal Security Blog
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术
AWS News Blog
AWS News Blog
G
GRAHAM CLULEY
T
Troy Hunt's Blog
SecWiki News
SecWiki News
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 聂微东

LINUX DO - 最新话题

谷歌云盘下载700g数据集,求方法 OpenAI推出了100美元的Pro订阅后,plus的Codex 5小时限额大幅缩水 之前买的super grok居然还没掉 关于CPA认证文件周限 佬们,默认CDK的要求是什么等级啊? 最新版本的微信群聊机器人方案 有没有人知道如何free号没有封,那么是否可以循环使用,因为我看主要是周限 L站改版了?吓我一跳,我以为我浏览器崩了 淘宝这种宽带可信吗,500兆移动宽带月费8元到2099年 docker内部应用访问宿主机mysql和redis时被拒绝connection refuse Erp全栈想转行做Ai有什么推荐的吗 boost有bug 佬们,有没有靠谱点的 Plus 购买渠道 大妈,狗妈用的 lg 服务有源头开源项目吗? 有人有能过验证码打码的嘛 上次帖里好像发过通过大模型来打码的 gpt plus 封号似乎也太快了点,一天就给封号了 按流量/token收费的国产官方AI推荐 我算是知道了为什么Oracle总是ABC了 佬友们帮我分析一下 ChatGPT Team账号只有一个人使用和4个席位邀请满了使用的总额度是一样的吗? gpt-free 10个带rt CPA反代claude是默认1m吗? 我终于敢说我做出来windows上tmux的替代了,目标windows/全平台最强的终端Ai编程工具 claude pro升级max,除了原来的$20,好像还能再领一次$100 关于AI agent的知识框架 独乐乐不如众乐乐,分享一下我的的AI对话程序 佬们自建网站支付问题是怎么解决的 怎么能让gpt模仿claude风格输出 codex free已经死了,下一个会是plus或者team吗 请问chatgpt pro里的fast模式,速度快了,降智吗 天才程序员想要复活,还有可用的codex公益站么 里斯本丸沉没照进现代了 [富可敌国] [一叶知秋API]友仔们 我们换域名了~~ 记得更新一下哦 有点莫名其妙,被阿里云警告了 从道观回家之前,我和师兄问道 【picpi 皮皮公益站】为了防止有人拿去卖,邀请码发放规则更新。 美国 FAA: 我们需要你,游戏玩家,来当空管吧 vibe时用文言省tok吗? 有没有用? 会降表现吗? Codex CLI 官方这个 imagegen 的 Skill 到底是干啥的?哪有对应工具啊? 求问关于尼区和美区开通Claude 换设备登录telegram国内号码老账号 需要收费咋办? 发现hotmail的额度特别耐用 最近还有能正常用的claude中转站吗? 避雷闲鱼上面的CC中转站 现在cursor的优势是什么呢? OpenAI 回应马斯克要求罢免奥尔特曼:搞法律突袭,扰乱诉讼 谁在吹opencode go套餐啊,又慢量又少 【SamAltman】奥特曼被燃烧瓶袭击后的回应 咸鱼上359买的claude MAX 5x ,美国假家宽,看看能活几天 想问问跳蚤市场开的Pro和Plus 虚拟卡链接求助 [开源插件] 做了一个适合科研佬的GPT插件 【AI小说】拿AI跑了一部小说,佬们看看质量怎么样 总是能在首页看到opus4.6鞭尸推送 这个别名邮箱可以注册gpt 一个人在外地的话,佬们周末都做什么 你们ddg还能行不 获取不到新的邮箱 了····· claude code修复codex windows升级0.120.0 无法打开问题 我现在Zeabur上搭建了CPA服务,怎么再接入new api来做分发 杭州有么有佬友在搞AI应用这块的,四年前端转AI开发 汇丰、渣打两家银行获得香港稳定币牌照 【开源推广】 AIUsage:聚合多个 AI 平台配额与用量的 高颜值 macOS端 CPA看板 APP Newapi吃服务器内存多吗 中行跨境通疑限制无卡连续交易 或为应对盗刷 突然不能用表情回应话题了 codex是不是降额度了 反馈关于 “快问快答”标签的乱象 opencode版本1.4.3 无法上传图片问题 想问一下怎么解决这个问题,就是终端太多? codex更新到0.120.0之后无法加载以前的会话 sub2api怎么部署? 分享一个自用的南京继续教育平台视频自动播放下一集的油猴脚本 zotero9出来了 Claude正在向我推销付费项目,那能让你轻易得逞嘛 甲骨文用脚本开出来4个2+12咋办啊佬们,我还是免费号 各个厂的coding plan lite都绝版了? claude code 20美金账户问题 联通元景套餐续费问题 ai时代下的一些思考(诚邀大家讨论) 出境易GPT订阅pro求助 今年到目前股市的操作。 刚收到短信之前跑路的那家可以兑换了 佬们都用境外服务器做什么呢? 甲骨文4+24 求助领pro时候报错-付款页面出错。请重试。如果问题依然存在,请访问help.openai.com。 cloudflare 浏览器渲染增加了 CDP与mcp支持 SUB2API 导入 rt 时报错显示 Request failed with status code 502 如何解决 讨论一下怎么整理笔记 codex0.120.0更新后无法启动,回退 0.119.0正常使用 冰佬的公益站也不行了吗 三角洲直接给我封了10年 有佬友知道怎么起诉么 88VIP邀请 经过排查大概确定反重力代理报错问题了 【求助】openrouter 今年4月用国内visa卡充值后导致封禁,无法使用外国模型 奥特曼家被炸 自用,高信息量回复收集 求助sub2api分组问题 【新人报道】注册成功了 分享100个codex free账号 招聘 深圳客户端开发(flutter) 20k+
千问HappyHorse视频去水印解析代码Python+cloudflare woker 部署js代码
任然 · 2026-04-29 · via LINUX DO - 最新话题
mimo-v2.5-pro写的,有些小瑕疵,问题不大。感兴趣的自己改改。 演示: 千问视频无水印下载 js版: /** * 通义千问 AI 视频无水印下载 - Cloudflare Worker * * 支持两种分享链接: * 类型1: https://www.qianwen.com/share/chat/{id} * 类型2: https://activity.qianwen.com/r/ai-studio-mobile/qwen-external-share?shareId=xxx&authorId=xxx * * 路由: * GET / → 前端 UI * POST /api/parse → 解析分享链接 * GET /api/proxy → 代理下载 */ export default { async fetch(request, env) { const url = new URL(request.url); if (request.method === "GET" && url.pathname === "/") { return new Response(HTML_PAGE, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); } if (request.method === "POST" && url.pathname === "/api/parse") { return handleParse(request); } if (request.method === "GET" && url.pathname === "/api/proxy") { return handleProxy(url); } // CORS preflight if (request.method === "OPTIONS") { return new Response(null, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }, }); } return new Response("Not Found", { status: 404 }); }, }; // ── 解析入口 ────────────────────────────────────────────── async function handleParse(request) { try { const { url } = await request.json(); const linkInfo = detectShareType(url); let result; if (linkInfo.type === "qianwen_chat") { result = await fetchType1(linkInfo.shareId); } else { result = await fetchType2(linkInfo.shareId, linkInfo.authorId); } return jsonResponse(result); } catch (e) { return jsonResponse({ error: e.message }, 500); } } // ── URL 识别 ────────────────────────────────────────────── function detectShareType(url) { // 类型1: www.qianwen.com/share/chat/{id} const m1 = url.match(/\/share\/chat\/([a-f0-9]+)/); if (m1) return { type: "qianwen_chat", shareId: m1[1] }; // 类型2: activity.qianwen.com/...?shareId=xxx&authorId=xxx if (url.includes("activity.qianwen.com")) { const u = new URL(url); const shareId = u.searchParams.get("shareId"); const authorId = u.searchParams.get("authorId") || ""; if (shareId) return { type: "activity_share", shareId, authorId }; } throw new Error("无法识别的链接格式,请粘贴千问分享链接"); } // ── 类型1: chat2-api ────────────────────────────────────── async function fetchType1(shareId) { const resp = await fetch( "https://chat2-api.qianwen.com/api/v1/share/info?pr=qwen&fr=web", { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": UA, Referer: "https://www.qianwen.com/", }, body: JSON.stringify({ share_id: shareId, biz_id: "ai_qwen" }), } ); if (!resp.ok) throw new Error(`chat2-api 返回 ${resp.status}`); const data = await resp.json(); if (data.code !== 0) throw new Error(data.msg || "API 错误"); return parseType1(data.data); } function parseType1(apiData) { const title = apiData.title || "未知"; const records = apiData.session?.record_list || []; for (const record of records) { for (const msg of record.response_messages || []) { for (const item of msg.meta_data?.multi_load || []) { if (item.type !== "ai_generate_video") continue; const content = item.content || {}; const infos = content.resource_infos || []; const layout = (content.layout_list || [])[0] || {}; const displayRefs = layout.video || []; const downloadRefs = layout.download_video || []; const coverRefs = layout.cover || []; const resMap = {}; for (const r of infos) resMap[r.refer_id] = r; const extra = content.extra_info?.content?.extra || {}; const all = []; // video = 无水印 for (const id of displayRefs) { if (resMap[id]?.url?.includes(".mp4")) all.push({ ...resMap[id], label: "无水印", type: "video" }); } // download_video = 有水印 for (const id of downloadRefs) { if (resMap[id]?.url?.includes(".mp4")) all.push({ ...resMap[id], label: "有水印", type: "video" }); } // 封面 for (const id of coverRefs) { if (resMap[id]) all.push({ ...resMap[id], label: "封面", type: "image" }); } // 其他 const known = new Set([...displayRefs, ...downloadRefs, ...coverRefs]); for (const r of infos) { if (!known.has(r.refer_id)) all.push({ ...r, label: r.url?.includes(".png") ? "原图" : "其他", type: "image" }); } return { source: "qianwen_chat", title, model: extra.model_name || content.generate_model_name || "", scene: content.scene || "", duration: content.duration || 0, resolution: content.ratio || "", prompt: extra.query || content.prompt || "", allResources: all, }; } } } throw new Error("未找到视频资源"); } // ── 类型2: zaodian ──────────────────────────────────────── async function fetchType2(shareId, authorId) { const resp = await fetch( "https://qwen-api.zaodian.com/api/v1/share/get", { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": UA, Referer: "https://activity.qianwen.com/", }, body: JSON.stringify({ shareId, authorId, chid: "null", product: "ai_studio", }), } ); if (!resp.ok) throw new Error(`zaodian-api 返回 ${resp.status}`); const data = await resp.json(); if (data.code !== 0) throw new Error(data.msg || "API 错误"); return parseType2(data.data); } function parseType2(data) { const title = data.title || data.shareTitle || "未知"; const prompt = data.content?.prompt || ""; const scene = data.objectType || ""; const play = data.playInfo || {}; const img = data.image || {}; const all = []; if (play.url) all.push({ refer_id: "video_main", url: play.url, label: "视频(无水印)", type: "video" }); if (play.playUrl) all.push({ refer_id: "video_play", url: play.playUrl, label: "视频(预览)", type: "video" }); if (play.downloadUrl) all.push({ refer_id: "video_download", url: play.downloadUrl, label: "视频(有水印)", type: "video" }); if (img.url) all.push({ refer_id: "cover", url: img.url, width: img.width, height: img.height, label: "封面", type: "image" }); return { source: "activity_share", title, model: "", scene, duration: play.videoTotalTime || 0, resolution: "", prompt, allResources: all, }; } // ── 代理下载 ────────────────────────────────────────────── const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; const ALLOWED_DOMAINS = [ "workspace-zb-cdn.quark.cn", "quark-aistudio-cdn.quark.cn", "img.alicdn.com", ]; async function handleProxy(url) { const target = url.searchParams.get("u"); if (!target) return new Response("Missing u param", { status: 400 }); const parsed = new URL(target); if (!ALLOWED_DOMAINS.some((d) => parsed.hostname.endsWith(d))) { return new Response("Domain not allowed", { status: 403 }); } const upstream = await fetch(target, { headers: { "User-Agent": UA, Referer: "https://www.qianwen.com/" }, }); if (!upstream.ok) return new Response(`Upstream ${upstream.status}`, { status: upstream.status }); const headers = new Headers(); headers.set("Content-Type", upstream.headers.get("Content-Type") || "application/octet-stream"); headers.set("Content-Disposition", 'attachment'); headers.set("Cache-Control", "public, max-age=3600"); headers.set("Access-Control-Allow-Origin", "*"); return new Response(upstream.body, { headers }); } // ── 工具 ────────────────────────────────────────────────── function jsonResponse(data, status = 200) { return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }); } // ── 前端 HTML ───────────────────────────────────────────── const HTML_PAGE = `<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width,initial-scale=1"/> <title>千问视频无水印下载</title> <style> *{margin:0;padding:0;box-sizing:border-box} body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; background:#0a0a0a;color:#e0e0e0;min-height:100vh;display:flex;flex-direction:column;align-items:center} .container{max-width:720px;width:100%;padding:24px 16px} h1{font-size:1.6rem;font-weight:700;text-align:center;margin-bottom:4px; background:linear-gradient(135deg,#6366f1,#a855f7,#ec4899);-webkit-background-clip:text; -webkit-text-fill-color:transparent} .subtitle{text-align:center;color:#888;font-size:.85rem;margin-bottom:28px} .input-group{display:flex;gap:8px;margin-bottom:20px} input[type=text]{flex:1;padding:12px 16px;border-radius:10px;border:1px solid #2a2a2a; background:#141414;color:#fff;font-size:.95rem;outline:none;transition:border .2s} input[type=text]:focus{border-color:#6366f1} input[type=text]::placeholder{color:#555} button{padding:12px 24px;border-radius:10px;border:none;font-weight:600;font-size:.95rem; cursor:pointer;transition:all .2s} .btn-primary{background:linear-gradient(135deg,#6366f1,#a855f7);color:#fff} .btn-primary:hover{opacity:.9;transform:translateY(-1px)} .btn-primary:disabled{opacity:.4;cursor:not-allowed;transform:none} .btn-download{background:linear-gradient(135deg,#10b981,#06b6d4);color:#fff; padding:10px 20px;font-size:.85rem} .btn-download:hover{opacity:.9} .btn-download:disabled{opacity:.4;cursor:not-allowed} .examples{margin-bottom:20px;padding:12px 14px;background:#111;border:1px solid #1a1a1a; border-radius:10px;font-size:.78rem;color:#666;line-height:1.8} .examples b{color:#888} .examples code{color:#a78bfa;background:#1a1028;padding:1px 5px;border-radius:4px;font-size:.76rem} .status{text-align:center;padding:20px;color:#888;font-size:.9rem} .spinner{display:inline-block;width:20px;height:20px;border:2px solid #333; border-top-color:#6366f1;border-radius:50%;animation:spin .6s linear infinite; vertical-align:middle;margin-right:8px} @keyframes spin{to{transform:rotate(360deg)}} .error{color:#f87171;text-align:center;padding:16px;background:#1a0a0a; border-radius:10px;margin-bottom:16px;border:1px solid #3a1a1a} .info-card{background:#141414;border:1px solid #2a2a2a;border-radius:14px; padding:20px;margin-bottom:20px} .info-card h2{font-size:1.1rem;margin-bottom:12px;color:#fff} .info-row{display:flex;gap:8px;margin-bottom:6px;font-size:.85rem} .info-label{color:#888;min-width:60px} .info-value{color:#ccc;word-break:break-all} .source-tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:.7rem; font-weight:600;margin-left:8px;vertical-align:middle} .source-qwen{background:#1a1a2a;color:#818cf8;border:1px solid #2a2a4a} .source-activity{background:#1a2a1a;color:#34d399;border:1px solid #2a4a2a} .prompt-text{color:#aaa;font-size:.82rem;line-height:1.5;margin-top:8px; padding:10px;background:#0f0f0f;border-radius:8px;max-height:80px;overflow-y:auto} .resources{margin-top:16px} .resources h3{font-size:.95rem;margin-bottom:12px;color:#ddd} .resource-item{display:flex;align-items:center;gap:12px;padding:12px 14px; background:#141414;border:1px solid #2a2a2a;border-radius:10px;margin-bottom:8px; transition:border-color .2s;cursor:pointer} .resource-item:hover{border-color:#3a3a3a} .resource-item.selected{border-color:#6366f1;background:#14142a} .resource-item input[type=checkbox]{width:18px;height:18px;accent-color:#6366f1; cursor:pointer;flex-shrink:0} .resource-thumb{width:56px;height:56px;border-radius:8px;object-fit:cover; background:#1a1a1a;flex-shrink:0} .resource-info{flex:1;min-width:0} .resource-name{font-size:.85rem;color:#ddd;font-weight:500; overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .resource-meta{font-size:.75rem;color:#888;margin-top:2px} .tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:.7rem; font-weight:600;margin-left:6px} .tag-no-wm{background:#0a2a1a;color:#34d399;border:1px solid #1a4a2a} .tag-wm{background:#2a1a0a;color:#fbbf24;border:1px solid #4a2a1a} .tag-dl{background:#0a1a2a;color:#60a5fa;border:1px solid #1a2a4a} .tag-play{background:#2a0a2a;color:#c084fc;border:1px solid #4a1a4a} .tag-cover{background:#1a1a2a;color:#818cf8;border:1px solid #2a2a4a} .tag-other{background:#1a1a1a;color:#888;border:1px solid #2a2a2a} .action-bar{display:flex;align-items:center;justify-content:space-between; margin-top:16px;padding:12px 0;border-top:1px solid #2a2a2a} .select-all{display:flex;align-items:center;gap:8px;font-size:.85rem;color:#aaa;cursor:pointer} .select-all input{accent-color:#6366f1} .footer{text-align:center;color:#444;font-size:.75rem;margin-top:auto;padding:20px 0} @media(max-width:480px){ .container{padding:16px 12px} h1{font-size:1.3rem} .input-group{flex-direction:column} .btn-primary{width:100%} } </style> </head> <body> <div class="container"> <h1>🎬 千问视频无水印下载</h1> <p class="subtitle">支持两种千问分享链接,解析并下载无水印视频</p> <div class="examples"> <b>支持格式:</b><br/> ① <code>https://www.qianwen.com/share/chat/xxx</code><br/> ② <code>https://activity.qianwen.com/r/ai-studio-mobile/qwen-external-share?shareId=xxx&authorId=xxx</code> </div> <div class="input-group"> <input type="text" id="urlInput" placeholder="粘贴千问分享链接..." onkeydown="if(event.key==='Enter')parseUrl()"/> <button class="btn-primary" id="parseBtn" onclick="parseUrl()">解析</button> </div> <div id="errorBox" class="error" style="display:none"></div> <div id="loadingBox" class="status" style="display:none"> <span class="spinner"></span>正在解析... </div> <div id="resultBox" style="display:none"></div> </div> <div class="footer">仅供学习交流 · 资源版权归原作者所有</div> <script> const BASE = location.origin; async function parseUrl() { const input = document.getElementById("urlInput"); const btn = document.getElementById("parseBtn"); const errBox = document.getElementById("errorBox"); const loadBox = document.getElementById("loadingBox"); const resBox = document.getElementById("resultBox"); const url = input.value.trim(); if (!url) { showErr("请输入链接"); return; } errBox.style.display = "none"; resBox.style.display = "none"; loadBox.style.display = "block"; btn.disabled = true; try { const resp = await fetch(BASE + "/api/parse", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }), }); const data = await resp.json(); if (data.error) { showErr(data.error); return; } renderResult(data); } catch (e) { showErr("请求失败: " + e.message); } finally { loadBox.style.display = "none"; btn.disabled = false; } } function showErr(msg) { const box = document.getElementById("errorBox"); box.textContent = msg; box.style.display = "block"; document.getElementById("loadingBox").style.display = "none"; } function esc(s) { if (!s) return ""; const d = document.createElement("div"); d.textContent = s; return d.innerHTML; } function renderResult(data) { const box = document.getElementById("resultBox"); const all = data.allResources || []; let html = ""; // 信息卡片 const srcTag = data.source === "activity_share" ? '<span class="source-tag source-activity">Activity</span>' : '<span class="source-tag source-qwen">千问</span>'; html += '<div class="info-card">'; html += '<h2>' + esc(data.title) + srcTag + '</h2>'; if (data.model) html += row("模型", data.model); if (data.scene) html += row("场景", data.scene); if (data.duration) html += row("时长", data.duration + "秒"); if (data.resolution) html += row("分辨率", data.resolution); if (data.prompt) html += '<div class="prompt-text">💬 ' + esc(data.prompt) + '</div>'; html += '</div>'; // 资源列表 html += '<div class="resources">'; html += '<h3>📦 可下载资源 (' + all.length + ')</h3>'; const tagMap = { "无水印": "tag-no-wm", "有水印": "tag-wm", "视频(有水印)": "tag-dl", "视频(预览)": "tag-play", "视频(无水印)": "tag-play", "封面": "tag-cover", "原图": "tag-cover" }; all.forEach((r, i) => { const tc = tagMap[r.label] || "tag-other"; const ext = r.type === "video" ? "MP4" : r.url?.includes(".png") ? "PNG" : "JPG"; const sz = r.width && r.height ? r.width + "×" + r.height : ""; const thumb = BASE + "/api/proxy?u=" + encodeURIComponent( r.url?.includes(".mp4") ? r.url.split("?")[0] + "?x-oss-process=video/snapshot,t_1000,f_jpg" : r.url ); const autoCheck = (r.label === "无水印" || r.label === "视频(有水印)" || r.label === "视频(无水印)") ? " checked" : ""; html += '<label class="resource-item" id="item-' + i + '">'; html += '<input type="checkbox" data-idx="' + i + '" onchange="updSel()"' + autoCheck + '/>'; html += '<img class="resource-thumb" src="' + thumb + '" onerror="this.style.display=\\'none\\'" loading="lazy"/>'; html += '<div class="resource-info">'; html += '<div class="resource-name">' + esc(r.refer_id) + " · " + ext + ' <span class="tag ' + tc + '">' + esc(r.label) + '</span></div>'; html += '<div class="resource-meta">' + sz + (r.md5 ? " · " + r.md5.slice(0,8) : "") + '</div>'; html += '</div></label>'; }); html += '</div>'; // 操作栏 html += '<div class="action-bar">'; html += '<label class="select-all"><input type="checkbox" id="selAll" onchange="toggleAll()"/> 全选</label>'; html += '<button class="btn-download" id="dlBtn" onclick="dlSel()" disabled>下载选中 (0)</button>'; html += '</div>'; box.innerHTML = html; box.style.display = "block"; window._res = all; updSel(); } function row(label, value) { return '<div class="info-row"><span class="info-label">' + label + '</span><span class="info-value">' + esc(value) + '</span></div>'; } function toggleAll() { const ck = document.getElementById("selAll").checked; document.querySelectorAll('.resource-item input[type=checkbox]').forEach(c => c.checked = ck); updSel(); } function updSel() { const cbs = document.querySelectorAll('.resource-item input[type=checkbox]'); let n = 0; cbs.forEach(c => { const el = document.getElementById("item-" + c.dataset.idx); if (c.checked) { n++; el.classList.add("selected"); } else { el.classList.remove("selected"); } }); const btn = document.getElementById("dlBtn"); btn.textContent = "下载选中 (" + n + ")"; btn.disabled = n === 0; document.getElementById("selAll").checked = n === cbs.length && cbs.length > 0; } async function dlSel() { const resources = window._res || []; const cbs = document.querySelectorAll('.resource-item input[type=checkbox]:checked'); for (const cb of cbs) { const r = resources[cb.dataset.idx]; if (!r?.url) continue; const ext = r.type === "video" ? "mp4" : (r.url?.match(/\\.([^?.]+)/)?.[1] || "jpg"); const proxyUrl = BASE + "/api/proxy?u=" + encodeURIComponent(r.url); const a = document.createElement("a"); a.href = proxyUrl; a.download = "qianwen_" + r.refer_id + "." + ext; a.target = "_blank"; document.body.appendChild(a); a.click(); document.body.removeChild(a); await new Promise(r => setTimeout(r, 500)); } } </script> </body> </html>`; Python版: #!/usr/bin/env python3 """ 通义千问(Qianwen) AI 生成视频无水印下载工具 支持两种分享链接: 类型1: https://www.qianwen.com/share/chat/{id} 类型2: https://activity.qianwen.com/r/ai-studio-mobile/qwen-external-share?shareId=xxx&authorId=xxx 用法: python qianwen_video_downloader.py <share_url> [-o output.mp4] [--all] [--info] """ import re import json import sys import argparse from pathlib import Path from urllib.parse import urlparse, parse_qs, unquote try: import requests except ImportError: print("📦 正在安装 requests...") import subprocess subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "-q"]) import requests # ── 常量 ───────────────────────────────────────────────────────── HEADERS = { "User-Agent": ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ), "Referer": "https://www.qianwen.com/", "Content-Type": "application/json", } # 允许代理下载的 CDN 域名白名单 ALLOWED_CDN_DOMAINS = [ "workspace-zb-cdn.quark.cn", "quark-aistudio-cdn.quark.cn", "img.alicdn.com", ] # ── URL 识别 ──────────────────────────────────────────────────── def detect_share_type(url: str) -> dict: """ 识别分享链接类型,返回: {"type": "qianwen_chat", "share_id": "xxx"} 或 {"type": "activity_share", "share_id": "xxx", "author_id": "xxx"} """ parsed = urlparse(url) # 类型1: www.qianwen.com/share/chat/{id} m = re.search(r'/share/chat/([a-f0-9]+)', url) if m: return {"type": "qianwen_chat", "share_id": m.group(1)} # 类型2: activity.qianwen.com/...?shareId=xxx&authorId=xxx if "activity.qianwen.com" in parsed.hostname: qs = parse_qs(parsed.query) share_id = qs.get("shareId", [None])[0] author_id = qs.get("authorId", [None])[0] if share_id: return { "type": "activity_share", "share_id": share_id, "author_id": author_id or "", } raise ValueError(f"无法识别的链接格式: {url}") # ── API 调用 ──────────────────────────────────────────────────── def fetch_type1(share_id: str) -> dict: """类型1: chat2-api.qianwen.com""" print(f"📡 类型1 API (chat2-api) share_id={share_id[:16]}...") resp = requests.post( "https://chat2-api.qianwen.com/api/v1/share/info", headers=HEADERS, json={"share_id": share_id, "biz_id": "ai_qwen"}, timeout=30, ) resp.raise_for_status() data = resp.json() if data.get("code") != 0: raise ValueError(f"API 错误: {data.get('msg')}") return parse_type1_data(data["data"]) def fetch_type2(share_id: str, author_id: str) -> dict: """类型2: qwen-api.zaodian.com""" print(f"📡 类型2 API (zaodian) share_id={share_id[:16]}...") resp = requests.post( "https://qwen-api.zaodian.com/api/v1/share/get", headers={**HEADERS, "Referer": "https://activity.qianwen.com/"}, json={ "shareId": share_id, "authorId": author_id, "chid": "null", "product": "ai_studio", }, timeout=30, ) resp.raise_for_status() data = resp.json() if data.get("code") != 0: raise ValueError(f"API 错误: {data.get('msg')}") return parse_type2_data(data["data"]) # ── 数据解析 ──────────────────────────────────────────────────── def parse_type1_data(api_data: dict) -> dict: """解析类型1 API 响应 (chat2-api)""" title = api_data.get("title", "未知") records = api_data.get("session", {}).get("record_list", []) for record in records: for msg in record.get("response_messages", []): meta = msg.get("meta_data", {}) for item in meta.get("multi_load", []): if item.get("type") != "ai_generate_video": continue content = item.get("content", {}) resource_infos = content.get("resource_infos", []) layout_list = content.get("layout_list", []) layout = layout_list[0] if layout_list else {} display_refs = layout.get("video", []) download_refs = layout.get("download_video", []) cover_refs = layout.get("cover", []) resources = {r["refer_id"]: r for r in resource_infos} extra = content.get("extra_info", {}).get("content", {}).get("extra", {}) all_res = [] # video = 无水印 for ref_id in display_refs: if ref_id in resources and ".mp4" in resources[ref_id].get("url", ""): all_res.append({**resources[ref_id], "label": "无水印", "type": "video"}) # download_video = 有水印 for ref_id in download_refs: if ref_id in resources and ".mp4" in resources[ref_id].get("url", ""): all_res.append({**resources[ref_id], "label": "有水印", "type": "video"}) # 封面 for ref_id in cover_refs: if ref_id in resources: all_res.append({**resources[ref_id], "label": "封面", "type": "image"}) # 其他 known = set(display_refs + download_refs + cover_refs) for ref_id, res in resources.items(): if ref_id not in known: ext = "原图" if ".png" in res.get("url", "") else "其他" all_res.append({**res, "label": ext, "type": "image"}) return { "source": "qianwen_chat", "title": title, "model": extra.get("model_name", content.get("generate_model_name", "")), "scene": content.get("scene", ""), "duration": content.get("duration", 0), "resolution": content.get("ratio", ""), "prompt": extra.get("query", content.get("prompt", "")), "allResources": all_res, } raise ValueError("未找到视频资源") def parse_type2_data(data: dict) -> dict: """解析类型2 API 响应 (zaodian)""" title = data.get("title", data.get("shareTitle", "未知")) prompt = data.get("content", {}).get("prompt", "") scene = data.get("objectType", "") all_res = [] play_info = data.get("playInfo") or {} image_info = data.get("image") or {} # 视频资源 if play_info.get("url"): all_res.append({ "refer_id": "video_main", "url": play_info["url"], "label": "视频(主)", "type": "video", }) if play_info.get("playUrl"): all_res.append({ "refer_id": "video_play", "url": play_info["playUrl"], "label": "视频(播放)", "type": "video", }) if play_info.get("downloadUrl"): all_res.append({ "refer_id": "video_download", "url": play_info["downloadUrl"], "label": "视频(下载)", "type": "video", }) # 封面图 if image_info.get("url"): all_res.append({ "refer_id": "cover", "url": image_info["url"], "width": image_info.get("width"), "height": image_info.get("height"), "label": "封面", "type": "image", }) return { "source": "activity_share", "title": title, "model": "", "scene": scene, "duration": play_info.get("videoTotalTime") or 0, "resolution": "", "prompt": prompt, "allResources": all_res, } # ── 下载 ──────────────────────────────────────────────────────── def download_file(url: str, output_path: str, desc: str = "文件") -> str: """下载文件,带进度条""" print(f"⬇️ 正在下载{desc}...") resp = requests.get(url, headers={"User-Agent": HEADERS["User-Agent"]}, stream=True, timeout=120) resp.raise_for_status() total = int(resp.headers.get("content-length", 0)) downloaded = 0 with open(output_path, "wb") as f: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) downloaded += len(chunk) if total > 0: pct = downloaded / total * 100 filled = int(pct // 2) bar = "█" * filled + "░" * (50 - filled) print(f"\r [{bar}] {pct:.1f}% ({downloaded:,}/{total:,} bytes)", end="", flush=True) print() size_mb = Path(output_path).stat().st_size / (1024 * 1024) print(f"✅ 已保存: {output_path} ({size_mb:.2f} MB)") return output_path def generate_filename(info: dict, suffix: str = "", is_video: bool = True) -> str: parts = ["qianwen"] if info.get("model"): parts.append(info["model"].replace(" ", "_").replace("/", "_")) if suffix: parts.append(suffix) ext = "mp4" if is_video else "jpg" return "_".join(parts) + f".{ext}" def print_info(info: dict): print(f"\n{'='*60}") print(f"🎬 视频信息 [{info['source']}]") print(f"{'='*60}") print(f" 标题: {info['title']}") if info.get("model"): print(f" 模型: {info['model']}") if info.get("scene"): print(f" 场景: {info['scene']}") if info.get("duration"): print(f" 时长: {info['duration']}秒") if info.get("resolution"): print(f" 分辨率: {info['resolution']}") if info.get("prompt"): prompt = info["prompt"] if len(prompt) > 120: prompt = prompt[:120] + "..." print(f" 提示词: {prompt}") print(f"\n📦 资源列表:") for i, res in enumerate(info["allResources"]): w = res.get("width", "?") h = res.get("height", "?") url = res.get("url", "") ext = "MP4" if (res.get("type") == "video" or ".mp4" in url) else "PNG" if ".png" in url else "JPG" size_str = f"{w}×{h}" if w != "?" else "" tag_class = {"无水印": "✅", "有水印": "⚠️", "封面": "🖼️", "原图": "🖼️", "视频(主)": "🎬", "视频(播放)": "▶️", "视频(下载)": "📥"}.get(res["label"], "📎") print(f" [{i}] {tag_class} {res['label']} · {ext} {size_str}") # ── 主流程 ─────────────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser( description="通义千问 AI 视频无水印下载工具 (支持两种分享链接)", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("url", help="千问分享链接 (支持 qianwen.com 和 activity.qianwen.com)") parser.add_argument("-o", "--output", help="输出文件名") parser.add_argument("--all", action="store_true", help="下载所有资源") parser.add_argument("--info", action="store_true", help="仅显示信息,不下载") parser.add_argument("-i", "--index", type=int, default=-1, help="下载指定索引的资源 (从 --info 列表中选)") parser.add_argument("--watermark", action="store_true", help="下载带水印版本 (类型1)") args = parser.parse_args() try: link_info = detect_share_type(args.url) if link_info["type"] == "qianwen_chat": info = fetch_type1(link_info["share_id"]) else: info = fetch_type2(link_info["share_id"], link_info["author_id"]) print_info(info) if args.info: print(f"\n(--info 模式,不下载)") return print(f"\n{'='*60}") all_res = info["allResources"] if args.all: for res in all_res: url = res.get("url", "") is_vid = res.get("type") == "video" or ".mp4" in url ext = "mp4" if is_vid else ("png" if ".png" in url else "jpg") fname = generate_filename(info, res["refer_id"], is_vid).replace(".mp4", f".{ext}") download_file(url, fname, f"{res['label']}") elif args.index >= 0: if args.index >= len(all_res): print(f"❌ 索引 {args.index} 超出范围 (共 {len(all_res)} 个)") sys.exit(1) res = all_res[args.index] fname = args.output or generate_filename(info, res["refer_id"], res.get("type") == "video") download_file(res["url"], fname, res["label"]) else: # 默认下载第一个无水印视频 target = None for res in all_res: if "无水印" in res["label"] or "下载" in res["label"]: target = res break if not target: target = all_res[0] if all_res else None if not target: print("❌ 没有可下载的资源") sys.exit(1) fname = args.output or generate_filename(info, is_video=target.get("type") == "video") download_file(target["url"], fname, target["label"]) print(f"\n🎉 完成!") except Exception as e: print(f"\n❌ 错误: {e}") sys.exit(1) if __name__ == "__main__": main() 1 个帖子 - 1 位参与者 阅读完整话题