Преглед изворни кода

feat:添加视频理解测试脚本

zhaohaipeng пре 3 недеља
родитељ
комит
c5c7ba6177
3 измењених фајлова са 500 додато и 0 уклоњено
  1. 28 0
      client/PQClient.py
  2. 51 0
      util/file_util.py
  3. 421 0
      video_analyse_test.py

+ 28 - 0
client/PQClient.py

@@ -0,0 +1,28 @@
+from typing import List, Dict, Any
+
+import requests
+
+
+class PQClient:
+    def __init__(self):
+        self._base_url = "http://longvideoapi-internal.piaoquantv.com"
+
+    def batch_select_video_info(self, video_ids: List[int]) -> List[Dict[str, Any]]:
+        url = f"{self._base_url}/longvideoapi/openapi/video/batchSelectVideoInfo"
+        data = {
+            "videoIdList": video_ids,
+        }
+        response = self._post(url, {}, data)
+        return response['data']
+
+    def get_video_info(self, video_id: int) -> Dict[str, Any]:
+        url = f"{self._base_url}/longvideoapi/openapi/video/getVideoInfo"
+        data = {
+            "videoId": video_id,
+        }
+        response = self._post(url, {}, data)
+        return response['data']
+
+    @staticmethod
+    def _post(url: str, headers: Dict[str, str], data: Dict[str, Any]) -> Dict[str, Any]:
+        return requests.post(url, headers=headers, json=data).json()

+ 51 - 0
util/file_util.py

@@ -0,0 +1,51 @@
+import os
+import random
+import time
+from pathlib import Path
+
+import requests
+import urllib3
+from send2trash import send2trash
+
+
+def create_dir(dir_path):
+    dir_path = Path(dir_path)
+    if not dir_path.exists():
+        dir_path.mkdir(parents=True, exist_ok=True)
+
+
+def file_to_trash(path):
+    print(f"移动文件或目录:【{path}】到回收站")
+    send2trash(path)
+
+
+def file_is_exist(path) -> bool:
+    return Path(path).exists()
+
+
+def download_file(url, local_path, max_retries=3, check_file_exists=True):
+    """下载文件并确保成功"""
+    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+    if check_file_exists and file_is_exist(local_path):
+        print(f"文件 {local_path} 已经存在")
+        return
+
+    for attempt in range(max_retries):
+        try:
+            response = requests.get(url, stream=True, timeout=10, verify=False)
+            response.raise_for_status()
+
+            os.makedirs(os.path.dirname(local_path), exist_ok=True)
+
+            with open(local_path, 'wb') as f:
+                for chunk in response.iter_content(chunk_size=8192):
+                    f.write(chunk)
+
+            print(f"下载成功: {url} -> {local_path}")
+            return
+        except Exception as e:
+            # print(f"下载失败 (尝试 {attempt + 1}/{max_retries}): {url} -> {local_path} \n 错误: {e}")
+            time.sleep(random.uniform(1, 3))
+            pass
+    print(f"最终失败: {url}")

+ 421 - 0
video_analyse_test.py

@@ -0,0 +1,421 @@
+# -*- coding: utf-8 -*-
+
+import json
+import os
+from pathlib import Path
+from typing import List, Dict, Any
+
+import pandas as pd
+import volcenginesdkcore
+import volcenginesdkvod20250101
+from volcengine.util.Functions import Function
+from volcengine.vod.VodService import VodService
+from volcengine.vod.models.request.request_vod_pb2 import VodUploadMediaRequest
+
+from client.PQClient import PQClient
+from util import file_util
+
+model = "mini_260428"
+
+base_dir = "/Users/zhao/Desktop/tzld/video"
+result_csv = Path(f"/Users/zhao/Desktop/tzld/文档/分析文档/豆包视频理解效果分析_{model}.csv")
+
+space_name = "aigc-admin"
+ak = "AKLTZWIxNWRkMzUyYjBmNGU2Yjk5MTFiYWVmNmNiY2Q1Njg"
+sk = "WW1NM1l6TTJNRFZrT0dFMk5HSXhZamt5TnpFd1kyWTNPR0V6TURZd056Yw=="
+
+end_prompt = '''
+你是一名专业的短视频引导语写手,你的任务是基于用户提供的视频内容,判断用户可能转发的核心原因,并写出一句用于视频结尾的**分享引导语**。
+目标:让用户产生“我应该转发一下”的冲动。
+---
+请严格遵循以下工作流程:
+第一步:判断分享动机
+从下列8种“分享视频的核心原因”中,选择最适合该视频的分享动机,仅选1项。
+【分享动机类型】
+1)让我看起来更有见识:常见于小众知识、尖端科技发现等内容
+2)让我看起来更有趣/有品位/独特:常见于高水平的艺术表演、幽默段子、 探索罕为人知的美景等内容
+3)表达我的立场/价值观:常见于围绕特定观点/价值观展开的内容
+4)积极情绪:常见于催人泪下的感人故事、难以想象规模/复杂度的建筑/奇观、能激发民族自豪感重大成就等内容
+5)消极情绪:常见于反应全社会/某群体遭受的不公、对某些事的普遍担忧、国家侵略/坏人坏事等内容
+6)普遍经历/困境/痛点:常见于特定身份(如农民)、特定年代(如:40/50/60后)普遍经历/困境等内容
+7)社群/圈层专属内容:常见于特定年龄、身份(如农民)、关系(如老同学、老乡)相关的怀念、感慨等内容
+8)实用性:常见于政策信息、生活小妙招、危险提示等利他信息内容
+---
+第二步:选择模板
+从对应动机的模板中,挑选一个最适合该视频内容的模板结构。注意:结构、语气必须保留,但内容允许根据视频内容进行情绪和细节的替换。
+分享动因:让我看起来更有见识
+模板1:这个关于端午节习俗的视频太及时了,得赶紧告诉朋友们。他们肯定还有人不知道端午节不能说快乐,可能会闹出笑话。为了不让身边的朋友说错,请大家一定要把这个视频转发到至少三个群里,让更多人了解这些传统习俗,提前做好准备,您的分享就是对他人的关爱。让我们互帮互助,共同进步,转发分享,福气多多。
+模板2:这是一篇难得的好文,写给家人、老师、同学、同事、战友好朋友,好兄弟好姐妹,以及爱我的和我爱的人。这篇好文可别独享,手指一点转发出去,转给身边同样退休或者即将退休的朋友,别让这个视频在你手里中止了。
+模板3:没想到这世上还有喊一声就能出水的神奇泉眼,真是长见识了。这样的新鲜事可不能咱们自己偷偷乐呵,赶紧转发分享到群里,让大家一起开开眼界。赠人玫瑰手有余香,您的每一次分享都能给别人带去一份快乐和惊喜,快快转发起来吧,让我们一起把这份快乐传递给更多的人,让更多的朋友们看到祖国的大好河山
+模板3:说的简直太好了,要不是看见这个视频,还真不知道我们平时吃饭用的筷子还有这么多的讲究,真是长见识了看完请一定将这个视频转发分享到你所在的每一个微信群当中,给更多的家人群友都看看尤其是发给子孙后代们好好看看,提醒他们时时刻刻别忘本,这是中国人的做人知识,值得被延续继承下去,多多转发,感恩感德。
+
+分享动因:让我看起来更有趣/有品位/独特
+模板1:朋友们,这个关于人死后全过程的故事,蕴含着深刻的人生哲理,不知是否也看醒了你。如此触动人心,且充满智慧的内容,值得分享给身边每一个人。多多转发传递,让更多人从故事中汲取力量,珍惜当下,豁达生活。别让这份难得的感悟在你这里终止,现在就行动起来转发吧,让更多人能在这个故事里找到生活的方向。
+
+分享动因:表达我的立场/价值观
+模板1:你要是觉得这内容实在,现在赶紧动手分享出去,让咱们的亲戚朋友都清醒清醒,特别是家里有小辈的,一定要让他们知道咱们祖宗的规矩,你发一个群,发一个好友,就是给咱们中国文化添一块砖,咱们一起把这视频顶上去,让全国人都看到,这是咱们的责任啊。
+模板2:今天如果你有缘看到这个视频,恳请您务必将它转发分享到每个群,让更多的人,尤其是青年后辈们,让世界看到5000年中国人的文化自信,有多少群就转发发多少群,共同见证祖国的万里山河,用实际行动传承熠熠生辉的文明之光。
+
+分享动因:积极情绪
+模板1:看完这个视频,我真的为伟大的祖国骄傲,中华民族必将迎来伟大复兴。也请看完视频的你,一定要把视频转发分享出去,让全世界,还有那些还没看明白的人睁大眼瞧瞧,这可不是吹牛皮,是实打实的底气!恳请你为这个视频转发一次,让所有人都看到,动动手指弘扬中国自信,感恩有你。
+模板2:朋友们,您看完千万别直接划走,只要点一下,就能把英雄的故事传给更多人,尤其是青年后辈们,一定要让他们看看。这条视频不需要您花一分钱,但需要您花一点心意,动动手指,就能让这个故事传个角落。您看那些英雄,他们用命换来了咱们今天的安稳日子,咱们为他们做这点小事,难道不应该吗?
+
+分享动因:消极情绪
+模板1:今天如果你看到这个视频,说明大家都在转发支持,让我们接力转发,让整个社会听到老百姓的心声,只有得到有关单位足够的重视,老百姓才能吃的安心吃的放心。每个群都在传播,每个群都在发声,咱们老百姓的饭碗安全才有保障,你转发的不是视频,是千万家庭的健康心愿,你转发的不是内容,是子孙后代的生命防线。今天你不转发,我不转发,问题何时能解决?食品安全不能等,身体健康不能赌,咱们中老年人更要为家人、为自己争这口气。
+模板2:听完这个视频,我心里特别不安。像我们这些普通的老百姓,本来生活就不容易,假如再被骗子骗了,那这后半生还怎么过?这些号码,如果你担心看一遍记不住,可以先保存下来,也可以把它转发给自己的父母、朋友或是家人,让更多的人知道这些危险的号码,别等到受害了才后悔。
+
+
+分享动因:普遍经历/困境/痛点
+模板1:这个视频真是让人惊叹,大实话说的句句在理,听完让人倍感振奋,大家在群里都说,视频里的每一句话都能引发共鸣,太值得分享了!如果你也觉得他说的好,快把这个视频转发到你所有的微信群,让更多的人听到他的声音,让我们一起把这份智慧和快乐传递下去,感恩有你期待更多的分享。
+模板2:这个视频真是说到了咱们老百姓的心坎里去了。现在好多专家就知道纸上谈兵,根本就不懂老百姓的疾苦,还是要多听听老百姓的声音,多为老百姓办实事办好事。我准备把它分享到几个群里,让更多的人都看看,大家也一起分享一下,让更多的人都来学习学习,好不好,特别是当干部的,好好看看,学习学习,怎么为人民服务,这才是关键!
+模板3:家人们,你们觉得老农民应不应该发放养老金呢?我希望我们共同呼吁起来,让更多的人关注到农民养老金的问题,才能推进这个问题的解决。在当今社会,养老金对于每一个人来说,都是不可或缺的保障。尤其是咱们辛勤耕耘了大半生的农民朋友们。为了让更多人了解这个问题,赶紧把它转发到你所有的群里,让我们一起为农民争取应有的权益。分享就是力量,感谢你们的支持。
+
+分享动因:社群/圈层专属内容
+模板1:要是你喜欢这个视频,由衷希望你能动动手指,将这个视频分享出去,分享到同学群里,让更多的老同学看到,咱们把这份情谊传递下去。让大家都回忆起那段美好的校园时光,想起那些无忧无虑的日子。老同学,你在远方还好吗?工作顺利吗?生活幸福吗?分享给你这个视频,希望能给你带去一丝温暖,一丝回忆。
+模板2:各位,我们这一代,出生在困难时期, 学习在动乱时期,工作在改革时期,养老在追梦时期。如果你看到了此视频, 说明咱们这代人都在转发支持,如果你也觉得不错,请把它转发到每个微信群, 让所有人都看到,即使时间带走了咱们的容颜, 也带不走我们对生活的热爱。转发传递,送给所有吃过苦头,但也不怕吃苦的老朋友!
+模板3:看完才知道现在的一万块在1978年竟然值那么多钱。想起咱们那个年代,物价低,生活简单,真是让人怀念。今天大家都在群里转发这个视频,看了都感叹时代变迁真是快,如果你也觉得这个视频有趣又有意义,赶紧把它转发到你所有的群里吧,让更多的人看看,了解一下过去的生活,多多转发,感恩有你。
+
+分享动因:实用性
+模板1:原来这样交电费能省这么多钱,这种实实在在的福利,咱们得让更多群友知道。现在知道的人还不多,转发出去,您就是传递好消息的贵人。听我说句掏心窝的话,这个视频你要是划过去不分享,后面的人就看不到了,有多少人想知道这种消息却还不知道呢,千万别让视频在您手里断了。咱们最讲究积德行善,您每转发一次就是攒一份功德,系统都给您记着呢。拜托各位了,这个视频能不能继续传播,全看您愿不愿意伸伸手,咱们一起让更多人看见真相。
+模板2:家人们,看完我才知道高铁站打伞这么危险!这不是小事,特别是家里有孩子的,你要是不提醒他们,就是没尽到责任。您多转一个群,多发给一个人,说不定就救了谁一命。你要是不转发,亲戚朋友没看到,真出了事您心里过得去吗?咱们这个年纪啊,就图个全家平安,您动动手指头的事,就能让更多人躲过危险,这功德多大。要是已经转过的群,您隔半天再转一次,总有人没看见的。说白了,咱们就是尽自己最大努力,让自己最在乎的人都平平安安的。
+模板3:这个视频真是太实用了,给我们这些做父母的提供了很多实用的建议。虽然我们都希望孩子能有一个美好的未来,但有时候却不知道该如何引导他们。这个视频就像是一盏明灯,指引我们在教育的路上不再迷茫。群里的朋友们都在转发,说对他们帮助很大。如果你也想给孩子一个更好的教育,赶紧把这个视频分享到你的群里吧,让更多的父母受益,一起为孩子的未来努力。
+
+分享动因:通用送祝福(用于无法分辨用户分享动因的内容)
+模板1:这个视频看完,记得现在立刻动动手指分享到群,每个群里都有需要这些道理的亲戚朋友。分享到三个群以上,还能收到平台送的福气勋章。你转发的每个群都会有人默默记着你的好,多一次转发就多一份福气,转出去的智慧早晚会回到你身上。让更多人受益,就是功德无量。千万别让好内容在你手里中断,转发越多福气越多,千万别私藏好内容,要大家一起看,分享完了回来,评论区告诉我,你转了几个群,让我也替你高兴高兴。
+模板2:老友们快转发给朋友吧,给朋友们都看看,这么好的视频,千万不能在咱们手里终止了。愿每一个收到这个视频的朋友都能收获长命百岁,赠人玫瑰,手有余香,转发传递,让快乐继续,多多转发,也将收获福气,不许不发,因为我要你幸福。
+模板3:老友们,这个故事您要是觉得感动,就点一下转发。让所有群里的老伙计们都看看这人间真情,特别是家里的小辈们,让他们知道多行善事必有后福,您多转发一次,说不定就多一个人相信善有善报。老天爷在看着呢,您每转一次群,就相当于给自己存了一份功德,您每转一个好友,就相当于给子孙后代种了一颗善果,您要是已经转完了,我替所有人谢谢您,您要是还没转,现在就点一下转发,福气自然来。转完的老友在评论区敲个善字,让咱们一起把这份感动传递下去。
+---
+第三步:生成引导语(表达增强要求)
+请结合以下**表达技巧**进行加工:
+1. 加入画面感:鼓励添加用户“身边人”“微信群”“同学”“家里老人”等细节,让用户能联想到具体转发对象  
+2. 代入感和共情力:用“你这一发,就帮到了谁谁谁”“他们看到可能会...”这类话术,激发用户行动  
+3. 生活化语言:需要适配老年人的阅读习惯和理解能力,多用通俗易懂、接地气、有感情、有呼吁力的表达,禁止出现太燃了、佛系、躺平、内卷、炸裂、冲鸭、学霸等非老年人偏好的词,同时避免官话、空话、劝学式语言
+4. 利他但不生硬:强调“信息重要性 + 传播的意义”,但不要机械重复“转发很重要”,而是给出“为什么分享能帮到别人”的理由
+5. 不要只是陈述,要带情绪感染:“太实用了”“我真的震撼了”“群里都在传”“你看看是不是这个理”等带感叹语气的话术可加入
+6. 引导语中,引导分享到群的内容必须包含“点击下方黄色按钮”的指示,引导分享到好友的内容,必须包含“点击下方绿色按钮”的指示。同时引导分享的内容必须在引导语的前5句话内出现(以1个句号为1句话)
+7.若内容中包含用于引起共鸣的群体名称,不要用“咱们中老年人”这类太泛的词,可以根据视频内容进行概括适合引起共鸣的群体描述如“咱们这一代人”、“咱们做父母的”,或是通过对某些群体共性的描述,如“咱们平时就是太老实了”
+---
+输出规范
+- 最终仅输出引导语本体内容,不输出中间过程、选项、模板内容等
+- 输出的引导语应包含1个明确的转发理由 + 明确的转发动员行为(如“快转发到群里”、“发给身边老人”、“发到同学群”)
+- 输出长度绝对不能低于250字,表达完整、有节奏、饱含情绪感染力
+---
+【示例】
+❌ Bad Case(不要这样写):  
+这个视频内容很有意义,讲了很多知识,建议大家转发给朋友们一起学习。
+✅ Good Case(目标风格):  
+这个视频太有用了,原来除了养老金,还有这么多隐藏补贴可以领,今天在群里大家都在讨论这个视频,你快转发到你所有的群里,说不定你的一发就让谁领到了钱,尤其是咱们老年朋友,很多人根本不知道这些政策,你这一发就等于给别人送去了实打实的福利,这才是真正的帮忙,这才是我们这代人该做的大事!
+---
+请开始任务,最终输出“引导语”文本。
+
+# 视频转音频文本
+{{PLACEHOLDER_ASR_RESULT}}
+
+# 视频分段描述文本
+{{PLACEHOLDER_VIDEO_CLIP_RESULT}}
+'''
+title_prompt = '''
+你是一位深谙人性、精通流量密码的短视频爆款标题专家。你的任务是根据我提供的「红色旅游」视频核心内容,严格遵循以下经过验证的“爆款标题策略框架与合规红线”,为我创作一个能够引发用户强烈点击欲且绝对安全的标题。
+最终目标: 输出1个最具爆款潜质且严格遵守平台规范的标题。
+第一部分:三大核心原则
+强烈的情绪锚点: 标题必须注入高浓度情绪(如:悲伤、崇高、赞叹),瞬间与用户建立情感连接。
+巨大的信息差与悬念: 采用“说一半藏一半”的策略,只透露部分信息,制造强烈的好奇心,诱导用户点击探究。
+宏大叙事与身份认同: 将个体故事与“国家”、“民族”、“人民”等宏大概念绑定,触动用户的集体身份认同和民族自豪感。
+第二部分:四种标题模型
+你在创作时,必须从以下四种模型中选择最匹配视频内容的一种来进行构思:
+模型一:英雄落幕,精神永存
+核心逻辑: 利用“死亡/离去”事件,激发惋惜与敬仰,强调其不朽贡献。
+句式特征: “逝去事实 + 伟大贡献/深远影响”的陈述句或感叹句。
+关键词库: 走了、去世、悼念、忘记、活在我们心中、永远怀念。
+爆款案例:
+🔴悼念,他走了,因为有他,我们不用看外国脸色!
+🔴他走了55年,火车为他改道,百姓万人敬仰!
+🔴他走了以后,留下了九个世界之最!
+模型二:悬念揭秘,引人入胜
+核心逻辑: 制造明确的“谜题”,勾起用户的“求知欲”和“解密欲”。
+句式特征: 疑问句或带有强烈悬念的陈述句。
+关键词库: 是谁、为何、多神、真相、你绝对想不到、谁还记得。
+爆款案例:
+🔴1959年的照片,你绝对想不到是谁!
+🔴一张残缺的全家福照片,为何看哭了许多人?
+🔴泪目!一张毕业照,477人仅剩2人,他们是谁?
+模型三:理想化身,价值共鸣
+核心逻辑: 将人物塑造为某种身份的“完美典范”,与社会现象形成对比,激发用户的价值认同。
+句式特征: 带有定义和评判口吻的强力陈述句、感叹句。
+关键词库: 这才是、真正的、这种、好官、好领导、老百姓、中国人。
+爆款案例:
+🔴这种好官,现在真少见!
+🔴你瞧瞧,这才是咱老百姓心里头盼着的好领导呢!
+🔴这才是真正的中国人,忍辱负重只为中华而崛起!
+模型四:巨大反差,戏剧冲突
+核心逻辑: 构建身份、境遇、时间或认知上的巨大反差,制造强烈的戏剧性冲突。
+句式特征: 呈现“A,却B”的转折关系。
+关键词库: 被贬...还、首富...却、扬名中外...如今、竟是这样。
+爆款案例:
+🔴老祖宗太牛了!被贬新疆,还干成这事!
+🔴太令人感动了!华侨首富,晚年却一贫-如洗。
+🔴20年前他扬名中外,如今这位英雄,谁还记得?
+第三部分:创作流程与合规红线
+你必须严格按照以下流程执行,并将合规红线作为创作的绝对边界:
+解析视频内容: 快速提炼我输入的视频核心要素(人物、结局、贡献、情绪、悬念等)。
+匹配最佳模型: 从第二部分的四种模型中,选定一个最贴切、最具爆发力的模型。
+生成初稿标题: 套用选定模型的句式、关键词和爆款案例的“感觉”,进行初步创作。
+执行最终审查(关键步骤): 在输出最终标题前,必须用以下【合规红线】对初稿进行逐条审查和修改,确保100%合规。
+红线一:严禁使用违禁词
+标题中绝对不允许出现以下列表中的任何词语,此为硬性规定,无任何例外:
+"紧急","速看","速转","刚刚","事关","赶紧","一定要","千万不要","震惊","惊人","亿万","无数","百分之","自杀","致死","全体国民","全体国人","央视","中央","国务院","人民日报","卫生部","官方","气象局","世卫","联合国","布林肯","新闻","内部","内幕","最新","医生提醒","自来水厂","爆炸性消息","九胞胎","天大的","连看三遍","务必看","终于曝光","神药","危害太大","不要吃了","大事发生","无数国人","再忙也要","出大事","关系你我","正式确认","好消息","突然传出","新规出台","重要的消息","重要消息","即将失传","打死都","惊天","不要再吃","格外留心","太危险","可怕一幕","身亡","后果很严重","寿命长短","错过别后悔","必看","早点知道就好了","不得不信","看一次少一次","无数人","老美","新华社","新规","最新骗局","新型骗局","吃的是这些","新冠","空气造粮","大老虎","激素鸡蛋","人造鸡蛋","官员财产","快速退烧","老中医","预言","致命","救命","保命","非常难得","太震撼了","快来看","一定要看","来看看","所有人都","头一次见","属相","泪目","泪崩","看完泪","看哭了","看哭无数人"
+红线二:严禁过度夸大与挑衅
+在构思时,必须主动规避以下被平台视为“过度夸大”的表达方式:
+禁止制造对立: 避免使用如“老外”、“汉奸”等词语,刻意制造“我们 vs 他们”的二元对立、民族对立或引战内容。
+禁止绝对断言: 避免使用“超越”、“不敢”、“震惊”等无法被客观事实完全支撑的、过于绝对化的结论性词语。
+禁止攻击性描述: 避免使用如“枪毙”、“嚣张”等带有强烈攻击性、审判性或挑衅意味的词语。
+# 输出指令与限制 (Output Instruction & Constraints)
+你的最终交付物只能是那一个生成的标题,并且必须严格遵守以下所有限制:
+唯一输出: 仅输出1个最终标题。禁止输出任何解释、分析过程、前缀、后缀或任何多余的文字。
+强制前缀: 标题的开头必须是 emoji “🔴”。
+字数限制: 标题的总字数(不含前缀🔴)绝对不能超过15个字。
+【任务开始】
+请根据下方我提供的视频核心内容,开始你的创作。"
+
+# 视频转音频文本
+{{PLACEHOLDER_ASR_RESULT}}
+
+# 视频分段描述文本
+{{PLACEHOLDER_VIDEO_CLIP_RESULT}}
+'''
+
+video_id_and_vid_map = {
+    58994728: "v02d5bg10068d7tepoqljht4mfqvjc80",
+    52049032: "v02d5bg10068d7tg5jaljht75snp9qag",
+    49300889: "v03d5bg10068d7tg5nqljht75rtf1pfg",
+    58391826: "v0dd5bg10068d7tg5p2ljht5lnrkq4v0",
+    45666412: "v02d5bg10068d7tg5piljht5sj9f25c0",
+    52376623: "v03d5bg10068d7tg5qaljhtba78mi630",
+    64607206: "v0dd5bg10068d7tg5riljht3ilaibl3g",
+    58638486: "v02d5bg10068d7tg5s2ljhtfhh7bpl80",
+    52480544: "v03d5bg10068d7tg5tqljht5n6dp8psg",
+    44740363: "v0dd5bg10068d7tg5uiljht7l97u5asg",
+    64607178: "v02d5bg10068d7tg5vqljht8fm23p9gg",
+    59051053: "v03d5bg10068d7tg60aljht75rtf1q90",
+    59392240: "v0dd5bg10068d7tg61qljhtdds0683mg"
+}
+
+pq_client = PQClient()
+vod_service = VodService()
+vod_service.set_ak(ak)
+vod_service.set_sk(sk)
+
+configuration = volcenginesdkcore.Configuration()
+configuration.ak = ak
+configuration.sk = sk
+configuration.region = 'cn-north-1'
+volcenginesdkcore.Configuration.set_default(configuration)
+api_instance = volcenginesdkvod20250101.VOD20250101Api()
+
+
+def key_is_empty_in_map(key: str, map: Dict[str, Any]):
+    return key not in map or map[key] == ""
+
+
+def load_task_map(csv_path: Path) -> Dict[int, Dict[str, Any]]:
+    """从 CSV 加载已有的任务信息,返回 video_id -> 行字典 的映射"""
+    if csv_path.exists():
+        df = pd.read_csv(csv_path)
+
+        for col in df.columns:
+            if col == 'video_id':
+                continue
+            df[col] = df[col].fillna('').astype(str)
+
+        # 将 DataFrame 转换为 records 列表,然后构建映射
+        records = df.to_dict(orient='records')
+        return {item['video_id']: item for item in records}
+    else:
+        return {}
+
+
+def save_task_map(csv_path: Path, task_map: Dict[int, Dict[str, Any]]) -> None:
+    """将任务映射保存为 CSV 文件"""
+    df = pd.DataFrame(list(task_map.values()))
+    df.to_csv(csv_path, index=False, encoding='utf-8-sig')
+
+
+def ensure_task_for_video(video_id: int, task_map: Dict[int, Dict[str, Any]]) -> Dict[int, Dict[str, Any]]:
+    """如果 video_id 不在映射中,则执行任务并更新映射"""
+    if video_id not in task_map:
+        task_map[video_id] = {
+            "video_id": video_id,
+            "vid": "",
+            "end_task_id": "",
+            "end_task_result": "",
+            "title_task_id": "",
+            "title_task_result": ""
+        }
+    return task_map
+
+
+def vod_upload_media(video_id: int, local_file_path: str) -> str:
+    # 查询缓存
+    if video_id in video_id_and_vid_map:
+        return video_id_and_vid_map[video_id]
+
+    apply_function = Function.get_add_option_info_func(os.path.basename(local_file_path), "", "", 0, False)
+
+    try:
+        req = VodUploadMediaRequest()
+        req.SpaceName = space_name
+        req.FilePath = local_file_path
+        req.Functions = json.dumps([apply_function])
+        req.CallbackArgs = ''
+        req.FileName = os.path.basename(local_file_path)
+        req.FileExtension = os.path.splitext(local_file_path)[1]
+        req.StorageClass = 1
+        req.UploadHostPrefer = ''
+        resp = vod_service.upload_media(req)
+        vid = resp.Result.Data.Vid
+        print(f"视频 {os.path.basename(local_file_path)} 的vid {vid}")
+        return vid
+    except Exception:
+        raise
+
+
+def start_execution(vid: str, prompt: str) -> str:
+    print(f"提交媒体处理任务: {vid}")
+    req_input = volcenginesdkvod20250101.InputForStartExecutionInput(
+        type="Vid",
+        vid=vid,
+    )
+    req_model = volcenginesdkvod20250101.ConvertModelForStartExecutionInput(
+        asr_app_id="3860818013",
+        asr_app_type="volc.bigasr.sauc.duration",
+        doubao_text_endpoint="ep-20260506151915-jqvw7",
+        doubao_vision_endpoint="ep-20260506151915-jqvw7",
+    )
+    req_vision = volcenginesdkvod20250101.VisionForStartExecutionInput(
+        model=req_model,
+        prompt=prompt
+    )
+    req_task = volcenginesdkvod20250101.TaskForStartExecutionInput(
+        type="Vision",
+        vision=req_vision,
+    )
+    req_operation = volcenginesdkvod20250101.OperationForStartExecutionInput(
+        task=req_task,
+        type="Task",
+    )
+    start_execution_request = volcenginesdkvod20250101.StartExecutionRequest(
+        input=req_input,
+        operation=req_operation,
+    )
+
+    response = api_instance.start_execution(start_execution_request)
+    run_id = response.run_id
+    print(f"媒体 {vid} 的任务ID: {run_id}")
+    return run_id
+
+
+def start_video_understanding_execution(vid: str, prompt: str) -> str:
+    print(f"提交媒体处理任务 VideoUnderstanding: {vid}")
+    req_input = volcenginesdkvod20250101.InputForStartExecutionInput(
+        type="Vid",
+        vid=vid,
+    )
+    req_model = volcenginesdkvod20250101.ConvertModelForStartExecutionInput(
+        asr_app_id="3860818013",
+        asr_app_type="volc.bigasr.sauc.duration",
+        doubao_text_endpoint="ep-20260506151915-jqvw7",
+        doubao_vision_endpoint="ep-20260506151915-jqvw7",
+    )
+    req_video_understanding = volcenginesdkvod20250101.VideoUnderstandingForStartExecutionInput(
+        prompt=prompt
+    )
+    req_task = volcenginesdkvod20250101.TaskForStartExecutionInput(
+        type="VideoUnderstanding",
+        video_understanding=req_video_understanding,
+    )
+    req_operation = volcenginesdkvod20250101.OperationForStartExecutionInput(
+        task=req_task,
+        type="Task",
+    )
+    start_execution_request = volcenginesdkvod20250101.StartExecutionRequest(
+        input=req_input,
+        operation=req_operation,
+    )
+
+    response = api_instance.start_execution(start_execution_request)
+    run_id = response.run_id
+    print(f"媒体 {vid} 的VideoUnderstanding任务ID: {run_id}")
+    return run_id
+
+
+def get_execution(run_id: str) -> str:
+    print(f"查询媒体处理任务结果: {run_id}")
+    try:
+        get_execution_request = volcenginesdkvod20250101.GetExecutionRequest(
+            run_id=run_id,
+        )
+        response = api_instance.get_execution(get_execution_request)
+        return response.output.task.vision.content
+    except Exception as e:
+        return ""
+
+
+def get_vide_understanding(run_id: str) -> str:
+    print(f"查询媒体处理任务结果: {run_id}")
+    try:
+        get_execution_request = volcenginesdkvod20250101.GetExecutionRequest(
+            run_id=run_id,
+        )
+        response = api_instance.get_execution(get_execution_request)
+        return response.output.task.video_understanding.content
+    except Exception as e:
+        return ""
+
+
+def main(video_ids: List[int]):
+    task_info_map = load_task_map(result_csv)
+    for video_id in video_ids:
+        task_info_map = ensure_task_for_video(video_id, task_info_map)
+        task_info = task_info_map[video_id]
+        # m3u8格式的暂不处理
+        if video_id == 58391826:
+            continue
+
+        if key_is_empty_in_map('vid', task_info):
+            response = pq_client.get_video_info(video_id)
+            video_url = response['videoPath']
+            local_file_path = f"{base_dir}/{video_id}{os.path.splitext(video_url)[1]}"
+            file_util.download_file(video_url, local_file_path)
+            vid = vod_upload_media(video_id, local_file_path)
+            task_info['vid'] = vid
+
+        # # Vision模式
+        if key_is_empty_in_map('end_task_id', task_info):
+            task_info['end_task_id'] = start_execution(task_info['vid'], end_prompt)
+
+        if key_is_empty_in_map('end_task_result', task_info) and not key_is_empty_in_map('end_task_id', task_info):
+            task_info['end_task_result'] = get_execution(task_info['end_task_id'])
+
+        if key_is_empty_in_map('title_task_id', task_info):
+            task_info['title_task_id'] = start_execution(task_info['vid'], title_prompt)
+
+        if key_is_empty_in_map('title_task_result', task_info) and not key_is_empty_in_map('title_task_id', task_info):
+            task_info['title_task_result'] = get_execution(task_info['title_task_id'])
+
+        # # VideoUnderstanding模式
+        # if key_is_empty_in_map('understanding_end_task_id', task_info):
+        #     task_info['understanding_end_task_id'] = start_video_understanding_execution(task_info['vid'], end_prompt)
+        #
+        # if key_is_empty_in_map('understanding_end_task_result', task_info) and not key_is_empty_in_map('understanding_end_task_id', task_info):
+        #     task_info['understanding_end_task_result'] = get_vide_understanding(task_info['understanding_end_task_id'])
+        #
+        # if key_is_empty_in_map('understanding_title_task_id', task_info):
+        #     task_info['understanding_title_task_id'] = start_video_understanding_execution(task_info['vid'], title_prompt)
+        #
+        # if key_is_empty_in_map('understanding_title_task_result', task_info) and not key_is_empty_in_map('understanding_title_task_id', task_info):
+        #     task_info['understanding_title_task_result'] = get_vide_understanding(task_info['understanding_title_task_id'])
+    save_task_map(result_csv, task_info_map)
+
+
+if __name__ == '__main__':
+    main([58994728, 52049032, 49300889, 58391826, 45666412, 52376623, 64607206, 58638486, 52480544, 44740363, 64607178, 59051053, 59392240])