File size: 4,540 Bytes
124fc9e e097ca3 2711b6f e097ca3 2711b6f e097ca3 2711b6f e097ca3 2711b6f e097ca3 124fc9e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | import { request } from "@/api/controllers/core.ts";
import logger from "@/lib/logger.ts";
/**
* 图片URL提取工具
* 统一从不同格式的响应中提取图片URL
*/
/**
* 从API响应项中提取图片URL
* @param item API响应中的单个项目
* @param index 项目索引(用于日志)
* @returns 图片URL或null
*/
export function extractImageUrl(item: any, index?: number): string | null {
const logPrefix = index !== undefined ? `图片 ${index + 1}` : '图片';
// 只提取 large_images
if (item?.image?.large_images?.[0]?.image_url) {
let imageUrl = item.image.large_images[0].image_url;
// 将URL中的 \u0026 转换为 &
imageUrl = imageUrl.replace(/\\u0026/g, '&');
logger.debug(`${logPrefix}: 使用 large_images URL`);
return imageUrl;
}
// 无法提取URL,记录警告
logger.warn(`${logPrefix}: 无法提取URL,缺少 image.large_images[0].image_url 字段。item结构: ${JSON.stringify(item, null, 2)}`);
return null;
}
/**
* 从项目列表中批量提取图片URLs
* @param itemList 项目列表
* @returns 图片URL数组
*/
export function extractImageUrls(itemList: any[]): string[] {
return itemList
.map((item, index) => extractImageUrl(item, index))
.filter((url): url is string => url !== null);
}
/**
* 从视频响应项中提取视频URL
* @param item 视频响应项
* @returns 视频URL或null
*/
export function extractVideoUrl(item: any): string | null {
// 优先尝试 transcoded_video.origin.video_url
if (item?.video?.transcoded_video?.origin?.video_url) {
return item.video.transcoded_video.origin.video_url;
}
// 尝试 play_url
if (item?.video?.play_url) {
return item.video.play_url;
}
// 尝试 download_url
if (item?.video?.download_url) {
return item.video.download_url;
}
// 尝试 url
if (item?.video?.url) {
return item.video.url;
}
return null;
}
/**
* 通过 get_local_item_list API 获取高质量视频下载URL
* 浏览器下载视频时使用此API获取高码率版本(~6297 vs 预览版 ~1152)
*
* @param itemId 视频项目ID
* @param refreshToken 刷新令牌
* @returns 高质量视频URL,失败时返回 null
*/
export async function fetchHighQualityVideoUrl(itemId: string, refreshToken: string): Promise<string | null> {
try {
logger.info(`尝试获取高质量视频下载URL,item_id: ${itemId}`);
const result = await request("post", "/mweb/v1/get_local_item_list", refreshToken, {
data: {
item_id_list: [itemId],
pack_item_opt: {
scene: 1,
need_data_integrity: true,
},
is_for_video_download: true,
},
});
const responseStr = JSON.stringify(result);
logger.info(`get_local_item_list 响应大小: ${responseStr.length} 字符`);
// 策略1: 从结构化字段中提取视频URL
const itemList = result.item_list || result.local_item_list || [];
if (itemList.length > 0) {
const item = itemList[0];
const videoUrl =
item?.video?.transcoded_video?.origin?.video_url ||
item?.video?.download_url ||
item?.video?.play_url ||
item?.video?.url;
if (videoUrl) {
logger.info(`从get_local_item_list结构化字段获取到高清视频URL: ${videoUrl}`);
return videoUrl;
}
}
// 策略2: 正则匹配 dreamnia.jimeng.com 高质量URL
const hqUrlMatch = responseStr.match(/https:\/\/v[0-9]+-dreamnia\.jimeng\.com\/[^"\s\\]+/);
if (hqUrlMatch && hqUrlMatch[0]) {
logger.info(`正则提取到高质量视频URL (dreamnia): ${hqUrlMatch[0]}`);
return hqUrlMatch[0];
}
// 策略3: 匹配任何 jimeng.com 域名的视频URL
const jimengUrlMatch = responseStr.match(/https:\/\/v[0-9]+-[^"\\]*\.jimeng\.com\/[^"\s\\]+/);
if (jimengUrlMatch && jimengUrlMatch[0]) {
logger.info(`正则提取到jimeng视频URL: ${jimengUrlMatch[0]}`);
return jimengUrlMatch[0];
}
// 策略4: 匹配任何视频URL(兜底)
const anyVideoUrlMatch = responseStr.match(/https:\/\/v[0-9]+-[^"\\]*\.(vlabvod|jimeng)\.com\/[^"\s\\]+/);
if (anyVideoUrlMatch && anyVideoUrlMatch[0]) {
logger.info(`从get_local_item_list提取到视频URL: ${anyVideoUrlMatch[0]}`);
return anyVideoUrlMatch[0];
}
logger.warn(`未能从get_local_item_list响应中提取到视频URL`);
return null;
} catch (error) {
logger.warn(`获取高质量视频下载URL失败: ${error.message}`);
return null;
}
}
|