文章目錄
- 1、前言
- 2、需求說明
- 2.1 需求說明
- 2.2 數據準備
- 3、功能實現
- 3.1 使用視頻理解大模型能力
- 3.1.1 三方平臺視頻在線鏈接解析
- 3.1.2 三方平臺視頻內網鏈接解析
- 3.1.3 三方平臺視頻轉存本地服務
- 3.2 使用音頻識別大模型能力
- 3.2.1 三方平臺視頻在線鏈接解析
- 3.2.2 三方平臺視頻詳情接口說明
- 3.2.3 通過三方平臺視頻詳情獲取音頻鏈接
- 3.2.4 通過音頻鏈接獲取視頻文案
1、前言
????近期要做個智能文案工具,來幫助電商公司的運營同學提效。包括AI創作、風格改寫、文案續寫、文案提取、智能問答等功能的運營文案創作助手。
????其中AI創作、風格改寫啊,實現過程比較簡單,基本上就是把用戶的想法,包括主題、觀點傳給文本大模型即可,通過限定大模型系統提示詞,加上外掛電商營銷文案知識庫,一般大模型會給出符合主題和觀點的文案。
????在文案提取功能上遇到了卡點,我們希望能提取視頻中的營銷文案內容。在實現前簡單的理解為文本大模型可以直接識別在線的視頻鏈接,然后給出視頻文案。實際上不是的。問了下DeepSeek、元寶、Kimi等都是給出"無法直接訪問XX或其他外部鏈接的內容"。其中百度網頁版也能分析出點內容,但是和我們想要的視頻文案差距太大。
-
DeepSeek(網頁版)
-
Baidu(DeepSeek-R1網頁版)
2、需求說明
2.1 需求說明
????我們希望能提取出視頻鏈接的原始文案,而不是對視頻的內容分析,視頻的理解,視頻的標題等無關內容。
????因為有了原始文案,我們可以對文案進行二創、潤色、風格改寫等動作。
????同樣的我們也能夠獲取視頻分析、視頻標題、視頻標簽等信息。
2.2 數據準備
- 三方平臺視頻鏈接
// 精選鏈接:
https://www.douyin.com/jingxuan?modal_id=7359544025726143771
// 詳情頁鏈接
https://www.douyin.com/video/7359544025726143771
- 視頻文案如下
貧窮真的跟懶惰有關系嗎?江秦這些年一直在思考這個問題。他覺得自己已經足夠勤奮了,完全對得起自己的名字。可錢呢?錢到底是被誰給賺走了?小時候,爸媽曾語重心長地告訴他,只要你肯吃苦,就一定會出人頭地。但他長大后發現的事實卻是,只要你肯吃苦,就一定有吃不完的苦。現在,他的相親對象要彩禮三十萬。江秦,你有沒有聽我說話?嗯,我一直聽著呢。那你怎么一聲也不吭?我都說了半天,嗓子都啞了,你也不管。江秦放下水杯,沉默半晌后開口:這婚要不還是別結了吧。女人愣了一下,隨即勃然大怒:你這話是什么意思?沒什么,就是覺得好累,想回家睡一覺。江秦,你個孬種,怪不得你都三十八了,也沒有女人想跟你。江秦不顧女人的咆哮,邁步走出了西餐廳,沿著馬路漫無目的地往前走去。走到一個建筑工地的時候,他看到圍墻上掛著一條橫幅,寫著:打工人是人上人。于是,他點上根煙,吧嗒抽了兩口后,在在上面燙了個洞。他對那個女人其實沒有太多的怨言,甚至覺得她的要求很正常。人家都三十五了,現實一點有什么毛病?他只是在思考一個問題,這樣的日子哪一天是個盡頭。沒打過工的人拼命鼓吹著打工人是人上人,一直在打工的人卻什么都不敢說,只能點頭承認:啊,對對對。可自己到底哪里像個人上人?這輩子就混了兩雙愛意還是莆田的,你管這叫人上人?至于愛情,江秦甚至都不知道這東西存不存在。他相過幾次親,見過幾個朋友介紹的女孩,無論哪個都可以湊合過,但最悲哀的也是僅限于湊合過。回顧一生,這輩子的遺憾真的太多了。江秦嘆了口氣,從口袋里摸出電話,想找個朋友陪自己喝點酒,但點菜后卻看到了四條短信:一條信用卡催款通知,一條話費欠費預警,一條哥哥我在附近,今天家里沒有人,最后一條來自他的直屬領導,用語重心長的文字跟他說:最近公司效益不好,希望員工可以自愿降薪,與公司一起共渡難關。江秦瞬間失去了喝酒的心情,繼續在施工樓下抽著煙。在這個時代,你想要有錢就絕對不能打工,因為這個社會的資源分配本來就是不公平的。可是一想到自己的年紀,江秦忍不住笑了。三十八了,再去創業有點不現實吧?他這兩年腰都累斷了,頸椎也出問題了,交叉神經痛比尿頻還勤快。拖著這殘破的身軀去創業,就算成功也得五十歲了。這人生還有什么可享受的?要是能重來就好了,打什么都別打工,能傍富婆就傍富婆,實在不行就創業,堅信錢沒了可以再賺,可良心沒了賺的更多。
3、功能實現
3.1 使用視頻理解大模型能力
-
思路
????把視頻鏈接傳給大模型,讓大模型識別視頻文案并輸出。 -
視頻理解大模型原理
????對視頻文件每隔0.5秒抽取一幀,采用圖像理解技術識別每幀圖像信息,通過圖像分析間接實現視頻內容分析的。
????允許設置fps參數控制抽幀頻率,高速運動場景比如體育賽事、動作電影適合較高的fps;長視頻或內容偏靜態視頻適合較低的fps。 -
以通義千問VL模型為例
- 文件形式
- 在線視頻鏈接要求:視頻鏈接是公網訪問且沒有權限攔截的,文件是常見的視頻文件格式。否則無法獲取視頻內容,就無法做視頻理解。
- 支持使用本地文件:通過SDK允許使用本地文件。需要將本地文件編碼為Base64格式,或者直接傳入本地路徑。
- 文件限制
- 視頻文件大小:Qwen2.5-VL系列模型支持傳入的視頻大小不超過1 GB,其他模型不超過150MB
- 視頻文件格式: MP4、AVI、MKV、MOV、FLV、WMV 等。
- 視頻時長:Qwen2.5-VL系列模型支持的視頻時長為2秒至10分鐘,其他模型為2秒至40秒。
- 文件形式
-
使用示例
curl -X POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model": "qwen-vl-max-latest","messages": [{"role": "system", "content": [{"type": "text","text": "You are a helpful assistant."}]},{"role": "user","content": [{"type": "video_url","video_url": {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4"}},{"type": "text","text": "這段視頻的內容是什么?"}]}]
}'
3.1.1 三方平臺視頻在線鏈接解析
????直接拿著三方平臺視頻鏈接,因三方平臺官方安全策略,大模型無法識別,提示傳入的視頻文件無效。
// 請求信息
{"enable_thinking": false,"max_tokens": 4096,"messages": [{"content": [{"type": "video_url", "video_url": {"url": "https://www.douyin.com/video/7359544025726143771"}},{"type": "text","text": "請提取視頻文案"}],"role": "user"}],"model": "qwen-vl-max","stream": false,"temperature": 0.7
}
// 響應結果
{"error": {"code": "invalid_parameter_error","param": null,"message": "<400> InternalError.Algo.InvalidParameter: Invalid video file.","type": "invalid_request_error"},"id": "chatcmpl-94d36a39-3322-9fbd-9b8f-2dedc1850d21","request_id": "94d36a39-3322-9fbd-9b8f-2dedc1850d21"
}
3.1.2 三方平臺視頻內網鏈接解析
????三方平臺視頻內網鏈接是通過三方平臺視頻詳情接口 /aweme/v1/web/aweme/detail 獲取的,如下所示,調用用法在下面音頻識別模塊會介紹。
????獲取到三方平臺視頻內網鏈接,大模型無法識別,提示鏈接資源無法下載或下載超時。
// 請求信息
{"enable_thinking": false,"max_tokens": 4096,"messages": [{"content": [{"type": "video_url", "video_url": {"url": "https://v3-web.douyinvod.com/bf9cca29836d3612cc035943bc6e220c/685a7e6e/video/tos/cn/tos-cn-ve-15/ocQjQeefGBgQLKqB3QIodOIZGCEEbNMAPm7anA/?a=6383&ch=26&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C3&cv=1&br=5029&bt=5029&cs=0&ds=4&ft=AJkeU_TLRR0sTlC42Dv2Nc.xBiGNbLMY4jdU_45JCAxJNv7TGW&mime_type=video_mp4&qs=0&rc=NzM4OTVkaDc4N2gzZjtnOkBpanhuaW05cnhoMzMzNGkzM0BjL2IyLTViXi8xLV5hYWA0YSMzYHJvMmRzbTNhLS1kLS9zcw%3D%3D&btag=80000e00010000&cquery=100B_100x_100z_100o_100w&dy_q=1750750244&feature_id=59cb2766d89ae6284516c6a254e9fb61&l=20250624153044F89E8D93AC3D0D87B3C8"}},{"type": "text","text": "請提取視頻文案"}],"role": "user"}],"model": "qwen-vl-max","stream": false,"temperature": 0.7
}
// 響應結果
{"error": {"code": "invalid_parameter_error","param": null,"message": "<400> InternalError.Algo.InvalidParameter: Failed to download multimodal content","type": "invalid_request_error"},"id": "chatcmpl-84b97fed-33ad-9d98-a40a-b893fb64c969","request_id": "84b97fed-33ad-9d98-a40a-b893fb64c969"
}
具體錯誤信息如下:https://help.aliyun.com/zh/model-studio/error-code
- 網絡原因,請檢查您的網絡連接是否正常。
- 該文件的URL為OSS的內網URL。由于OSS內網與阿里云百煉服務不互通,請勿使用OSS內網URL。
- 提供的圖片資源所在的IP地址不在中國內地。
- 由于網絡環境的差異,跨境資源訪問可能會受到一定的限制或不穩定因素影響。建議您盡量使用中國內地的資源存儲服務,以確保網絡連接的穩定性和訪問速度。
3.1.3 三方平臺視頻轉存本地服務
????通過以上兩種方式,看出三方平臺視頻鏈接因三方平臺官方的安全策略,無法直接識別。
????可以下載視頻到自己服務器上或者下載并轉存到cos、oos云服務器上,獲取到oss鏈接再交給大模型進行解析。
????此處就不再演示。
3.2 使用音頻識別大模型能力
-
思路
????獲取視頻的音頻文件,把音頻文件鏈接傳給大模型,讓大模型識別音頻文案并輸出。
????支持多種音頻(包括說話人語音、自然聲音、音樂、歌聲)和文本作為輸入,并輸出文本。不僅能對輸入的音頻進行轉錄,還具備更深層次的語義理解、情感分析、音頻事件檢測、語音聊天等能力。 -
以通義千問Audio模型為例
- 文件形式
- 在線音頻鏈接要求:音頻鏈接是公網訪問且沒有權限攔截的,文件是常見的音頻文件格式。
- 支持使用本地文件:通過SDK允許使用本地文件。需要傳入本地音頻的絕對路徑。
- 文件限制
- 音頻文件大小:建議不超過10 MB,超出也是可以解析的
- 音頻文件格式: AMR、WAV(CodecID: GSM_MS)、WAV(PCM)、3GP、3GPP、AAC、MP3等。
- 音頻時長:音頻的時長建議不超過30秒,如果超過30秒,模型會自動截取前30秒的音頻。實際上解析2min時長的音頻也是可以的
- 音頻語言:中文、英語、粵語、法語、意大利語、西班牙語、德語和日語
- 文件形式
-
使用示例
curl -X POST https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model": "qwen-audio-turbo-latest","input":{"messages":[{"role": "system","content": [{"text": "You are a helpful assistant."}]},{"role": "user","content": [{"audio": "https://dashscope.oss-cn-beijing.aliyuncs.com/audios/welcome.mp3"},{"text": "這段音頻在說什么?"}]}]}
}'
3.2.1 三方平臺視頻在線鏈接解析
????通過瀏覽器訪問三方平臺視頻詳情頁鏈接,打開瀏覽器控制臺,可以看到有一個視頻詳情接口 /aweme/v1/web/aweme/detail 的調用,返回了視頻的明細,包括視頻鏈接、音頻鏈接、視頻標題、視頻標簽、ocr識別文案、分享信息等。
????下面我找了一寫關鍵信息貼出來。
????其中我們發現有ocr識別結果 aweme_detail.seo_info.ocr_content,但是文案質量不佳,封面或者視頻背景中的特殊字符文案也會識別出來。
????說明走ocr識別方案可能也不是我們想要的視頻文案結果。
{"aweme_detail": {"caption": "#重生文 #AIGC #錯哪兒了 都重生了誰談戀愛啊","desc": "#重生文 #AIGC #錯哪兒了 都重生了誰談戀愛啊","music": {"play_url": {"height": 720,"uri": "https://sf3-cdn-tos.douyinstatic.com/obj/ies-music/7359544061390326578.mp3","url_key": "7359544062350822195","url_list": ["https://sf3-cdn-tos.douyinstatic.com/obj/ies-music/7359544061390326578.mp3","https://sf6-cdn-tos.douyinstatic.com/obj/ies-music/7359544061390326578.mp3"],"width": 720}},"preview_title": "#重生文 #AIGC #錯哪兒了 都重生了誰談戀愛啊","seo_info": {"ocr_content": "起點讀書 誰談戀愛啊 嗎 錢到底是被誰給賺走了 地告訴他 出人頭地 卻是 只要你肯吃苦就一定有 吃不完的苦 現在 十萬 ”“嗯 我都說了半天 嗓子都啞了你也不管 沉默半晌后開口:“這 婚 aui 話是什么意思”“ E R4C 想回家睡一覺”“江勤 你個孬種 前走去 1001010 沿著馬路漫無目的地往 候 橫幅 吧嗒抽了兩口后在上面 燙了個洞 太多的怨言 常 人家都三十五了 盡頭 著 寫著打工人是人上人 打工人是人上人 啊對對對 上人 還是莆田的 至于愛情 西存不存在 見過幾個朋友介紹的女 孩 湊合過 回顧一生 這輩子的遺憾真的太多 奮了 酒 但點開后卻看到了四條 短信 領導 說 有 心情 i 配 可是一想到自己的年紀 可是一想到自己的年紀 江勤瞬間失去了喝酒的 江勤忍不住笑了 不現實吧 樂 他只是在思考一個問題 交叉神經痛比尿頻還勤 快 業 的 打工 打什么都不打工 都不敢說 該內容引用AI能力生產 可良心沒了賺的更多"},"share_info": {"share_desc": "在XX,記錄美好生活","share_desc_info": "#在XX,記錄美好生活##重生文 #AIGC #錯哪兒了 都重生了誰談戀愛啊","share_link_desc": "3.02 Q@X.zT 03/05 mqE:/ # 重生文 # AIGC # 錯哪兒了 都重生了誰談戀愛啊 %s 復制此鏈接,打開Dou音搜索,直接觀看視頻!","share_url": "https://www.iesdouyin.com/share/video/7359544025726143771/?region=CN\u0026mid=7359544062350822195\u0026u_code=353j2f7d77mc\u0026did=MS4wLjABAAAAqHqcov8rpj4LVZ8iF07s0MNfkPzs3ytKRY7R1ioqIPBXNmjExJVCCn98dkv4GFyX\u0026iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ\u0026with_sec_did=1\u0026video_share_track_ver=\u0026titleType=title\u0026share_sign=IbKCVSSsLU6Xp_9LStddXTQcO8hqW30VeXjQ_PnB16A-\u0026share_version=190500\u0026ts=1750821377\u0026from_aid=6383\u0026from_ssr=1"},"text_extra": [{"caption_end": 4,"caption_start": 0,"end": 4,"hashtag_id": "1608221011432477","hashtag_name": "重生文","is_commerce": false,"start": 0,"type": 1},{"caption_end": 10,"caption_start": 5,"end": 10,"hashtag_id": "1722895911293971","hashtag_name": "aigc","is_commerce": false,"start": 5,"type": 1},{"caption_end": 16,"caption_start": 11,"end": 16,"hashtag_id": "1631690698606605","hashtag_name": "錯哪兒了","is_commerce": false,"start": 11,"type": 1}],"video": {"play_addr": {"data_size": 10329912,"file_cs": "c:0-172367-aa4c","file_hash": "39d7feda1c7822ee2a3b4f3b3ce18240","height": 960,"uri": "v0200fg10000coh5k7rc77u15ncs6cag","url_key": "v0200fg10000coh5k7rc77u15ncs6cag_h264_540p_403786","url_list": ["https://v26-web.douyinvod.com/7bd3b24c1e6ca49a04eb7661c65ff0bd/685b94fd/video/tos/cn/tos-cn-ve-0015c800/oczi2CcPEBDEnIhCmteWA1BAb07VSgMdQLAJfz/?a=6383\u0026ch=26\u0026cr=3\u0026dr=0\u0026lr=all\u0026cd=0%7C0%7C0%7C3\u0026cv=1\u0026br=394\u0026bt=394\u0026cs=0\u0026ds=6\u0026ft=AJkeU_TLRR0sTlC42Dv2Nc.xBiGNbLjf~jdU_45JCAxJNv7TGW\u0026mime_type=video_mp4\u0026qs=0\u0026rc=ZDs2Mzk7Zmg1PGc0Njo1OUBpamZ2cTQ6Zjo4cjMzNGkzM0BhMDEzLS9eNTYxYmExXl8wYSNeM2AycjRvaGVgLS1kLS9zcw%3D%3D\u0026btag=80000e00028000\u0026dy_q=1750821377\u0026feature_id=f0150a16a324336cda5d6dd0b69ed299\u0026l=202506251116174010AE3B891530B28EB0","https://v3-web.douyinvod.com/ae0626098d54eda9e992e48653624ea3/685b94fd/video/tos/cn/tos-cn-ve-0015c800/oczi2CcPEBDEnIhCmteWA1BAb07VSgMdQLAJfz/?a=6383\u0026ch=26\u0026cr=3\u0026dr=0\u0026lr=all\u0026cd=0%7C0%7C0%7C3\u0026cv=1\u0026br=394\u0026bt=394\u0026cs=0\u0026ds=6\u0026ft=AJkeU_TLRR0sTlC42Dv2Nc.xBiGNbLjf~jdU_45JCAxJNv7TGW\u0026mime_type=video_mp4\u0026qs=0\u0026rc=ZDs2Mzk7Zmg1PGc0Njo1OUBpamZ2cTQ6Zjo4cjMzNGkzM0BhMDEzLS9eNTYxYmExXl8wYSNeM2AycjRvaGVgLS1kLS9zcw%3D%3D\u0026btag=80000e00028000\u0026dy_q=1750821377\u0026feature_id=f0150a16a324336cda5d6dd0b69ed299\u0026l=202506251116174010AE3B891530B28EB0","https://www.douyin.com/aweme/v1/play/?video_id=v0200fg10000coh5k7rc77u15ncs6cag\u0026line=0\u0026file_id=36b2a743d1ed49928eac2806604338da\u0026sign=39d7feda1c7822ee2a3b4f3b3ce18240\u0026is_play_url=1\u0026source=PackSourceEnum_AWEME_DETAIL"],"width": 544},"ratio": "540p","video_model": "","width": 544},"video_tag": [{"level": 1,"tag_id": 2014,"tag_name": "二次元"},{"level": 2,"tag_id": 2014002,"tag_name": "二次元內容"},{"level": 3,"tag_id": 2014002001,"tag_name": "動漫IP"}]
}
3.2.2 三方平臺視頻詳情接口說明
????三方平臺視頻詳情接口 /aweme/v1/web/aweme/detail,該接口目前可通過以下兩個域名訪問。
- 第一個域名是我們訪問三方平臺視頻詳情頁鏈接,在瀏覽器控制臺獲取到的
https://www-hj.douyin.com/aweme/v1/web/aweme/detail/
- 第二個域名是我們通過訪問iframe嵌套的
https://www.douyin.com/aweme/v1/web/aweme/detail/
- 通過VideoID獲取IFrame代碼
https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/video-management/douyin/iframe-player/get-iframe-by-video
請求示例:
curl --location --request GET 'https://open.douyin.com/api/douyin/v1/video/get_iframe_by_video?video_id=7359544025726143771'
響應結果:
{"log_id" : "202506250953238DD93A552BB7E8653C75","err_msg" : "","err_no" : 0,"data" : {"iframe_code" : "<iframe width=\"544\" height=\"960\" frameborder=\"0\" src=\"https://open.douyin.com/player/video?vid=7359544025726143771&autoplay=0\" referrerpolicy=\"unsafe-url\" allowfullscreen></iframe>","video_height" : 960,"video_title" : "#重生文 #AIGC #錯哪兒了 都重生了誰談戀愛啊","video_width" : 544}
}
3.2.3 通過三方平臺視頻詳情獲取音頻鏈接
- 請求頭配置
????此處使用https://www.douyin.com/aweme/v1/web/aweme/detail/域名接口,注意該接口的請求頭需要配置
Origin: https://open.douyin.com
Referer: https://open.douyin.com
????如果使用https://www-hj.douyin.com/aweme/v1/web/aweme/detail/域名接口,注意該接口的請求頭需要配置
Origin: https://www.douyin.com
Referer: https://www.douyin.com
- 請求參數說明
????此處使用https://www.douyin.com/aweme/v1/web/aweme/detail/
????核心參數aweme_id傳視頻ID
????核心參數aid傳固定值,用瀏覽器控制臺抓取到的那個值就可以
????其他參數msToken、X-Bogus、_signature是三方平臺官方安全策略參數,用官方的js文件可以找到加密方法,此處固定用瀏覽器控制臺抓取到的值也可以。 - DyDetailVO.java
????請求結果VO封裝
package com.adtool.platform.controller.vo.wenan;import lombok.Data;import java.util.List;/*** @className: DyDetailVO * @description: 三方平臺視頻明細* @author: author* @date: 2025/6/24 18:24**/
@Data
public class DyDetailVO {private AwemeDetail aweme_detail;private LogPb log_pb;private Integer status_code;@Datapublic static class LogPb {private String impr_id;}@Datapublic static class AwemeDetail {/** 視頻描述 */private String desc;private Double duration;/** 視頻標題 */private String item_title;/** 音樂信息 */private Music music;/** 預覽標題 */private String preview_title;/** seo信息 */private SeoInfo seo_info;/** 標題標簽 */private List<TextExtra> text_extra;private Video video;/** 視頻標簽 */private List<VideoTag> video_tag;@Datapublic static class Music {/** 音樂名稱 */private PlayUrl play_url;@Datapublic static class PlayUrl {/** 音頻地址 */private String uri;}}@Datapublic static class SeoInfo {/** ocr_content */private String ocr_content;}@Datapublic static class TextExtra {/** 標簽名稱 */private String hashtag_name;}@Datapublic static class Video {/** 格式 */private String format;/** 分辨率 */private String ratio;private PlayAddr play_addr;@Datapublic static class PlayAddr {/*** 播放地址,取list.get(0)* 對幀率有要求可以取其他值* play_addr* play_addr_265* play_addr_h264*/private List<String> url_list;}}@Datapublic static class VideoTag {/** 標簽名稱 */private String tag_name;}}}
- 獲取三方平臺視頻明細
????請求過程是crul調用,這里就不寫了。
@Value("${dy.video.detail:https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id={{videoId}}&aid=6383&msToken=KbscjTT6O_4LM5GZSm6ulplDk6kFy5lvsIznwgVdhWUng75b2NqTLC4lnKwENN1uiW52Ub2Q1P3yUS6GL9EUSNIDSqgiH7k5uGiDGYvrjt9YgpYRJKvqQw==&X-Bogus=DFSzswVOW7iANrWECCcE6QTQh4SC&_signature=_02B4Z6wo00001qQHOyAAAIDDe1zIbmL9oZKkBz-AAMFn8sgvQzXK4sUcPGou9UVpawJQumggtRNzYPluyC5lYZv1kLaElZP-aNIeKLcgp7x7moMOtAYAC6ggXbhscNqWEePdp0-JKSpODhxd6e}")
private String getDyAwemeDetail;public void videoLinkAnalysis(String videoId) {String url = getDyAwemeDetail.replace(Constants.VIDEO_ID, videoId);Map<String, String> headers = new HashMap<>(2);headers.put("Origin".intern(), "https://open.douyin.com".intern());headers.put("Referer".intern(), "https://open.douyin.com".intern());Response response = httpClientService.buildResponseGet(url, videoId, headers);if (response.isSuccessful()) {try {dyDetailVO = JSON.parseObject(response.body().string(), DyDetailVO.class);} catch (IOException e) {log.error("獲取視頻明細失敗,請稍后重試!", e);throw new RuntimeException("獲取視頻明細失敗,請稍后重試!");}}String musicUrl = dyDetailVO.getAweme_detail().getMusic().getPlay_url().getUri();// TODO 獲取到音頻鏈接 ...
}
3.2.4 通過音頻鏈接獲取視頻文案
????此處以Crul形式調用通義千問Audio模型。
????實現效果如下:
????下面是部分實現代碼。
- OpenAIApiService.java
大模型請求實現類核心方法,流式解析大模型識別結果。
private static final String STREAM_MESSAGE_PREFIX_NO_EMPTY = "data:";
private static final String STOP = "stop";@Resource
private ExecutorService chatRequestExecutor;
@Resource
private ChatEngineRetryService chatEngineRetryService;
/*** @param: chatInput* @param: emitter* @param: openaiUrl* @param: key* data:{"output":{"choices":[{"message":{"content":[{"text":"音頻"}],"role":"assistant"},"finish_reason":"null"}]},"usage":{"audio_tokens":754,"input_tokens":785,"output_tokens":1},"request_id":"972092ff-3184-9aad-bfc0-8aa2a34a4f25"}* data:{"output":{"choices":[{"message":{"content":[],"role":"assistant"},"finish_reason":"stop"}]},"usage":{"audio_tokens":754,"input_tokens":785,"output_tokens":236},"request_id":"2537f174-7767-9680-91fd-73230ff14cd4"}* @return: void* @author: 音頻大模型流式接口* @date: 2025/6/24*/
@Override
public void streamIncrementalApi(ChatInput<String> chatInput, SseEmitter emitter, String openaiUrl, String key) {chatRequestExecutor.execute(() -> {long start = System.currentTimeMillis();try (Response response = buildResponse(chatInput, openaiUrl, key)) {log.info("API耗時響應: {}s", (System.currentTimeMillis()-start)/1000);try (InputStream is = response.body().byteStream();InputStreamReader isr = new InputStreamReader(is);BufferedReader bufferedReader = new BufferedReader(isr)) {String line;while ((line = bufferedReader.readLine()) != null) {log.info("res stream:{}", line);if (!line.contains(STREAM_MESSAGE_PREFIX_NO_EMPTY)){continue;}String messageJsonStr = line.substring(line.indexOf(STREAM_MESSAGE_PREFIX_NO_EMPTY) + STREAM_MESSAGE_PREFIX_NO_EMPTY.length());OpenAIRes res = JSONUtil.toBean(messageJsonStr, OpenAIRes.class);Choice choice = res.getOutput().getChoices().get(0);if (STOP.equals(choice.getFinish_reason())) {emitter.send(new GptStreamDto(1, StringUtils.EMPTY, false, true));continue;}List<MessageContent> content = JSONUtil.toList((JSONArray) choice.getMessage().getContent(), MessageContent.class);if (!CollectionUtils.isEmpty(content)) {emitter.send(new GptStreamDto(1, content.get(0).getText(), false, false));}}}} catch (Exception e) {log.error("流處理異常", e);} finally {log.info("API耗時流式對話: {}s", (System.currentTimeMillis()-start)/1000);emitter.complete();}});
}
/*** @param: chatInput* @param: openaiUrl* @param: key* @description: 構建OkClient請求結果* @return: okhttp3.Response* @author: niaonao* @date: 2025/6/25*/
@Override
public Response buildResponse(Object chatInput, String openaiUrl, String key) {String body = com.alibaba.fastjson.JSON.toJSONString(chatInput);RequestBody requestBody = RequestBody.create(body, JSON);/*** 如果服務端接口本身不支持流式響應(SSE),即使客戶端設置 X-DashScope-SSE: enable,服務端會忽略該頭部并按默認非流式模式返回數據。* 主流API設計規范中,非流式接口會直接丟棄無關的流式控制頭部,不會引發錯誤。*/Request requestOpenai = new Request.Builder().url(openaiUrl).post(requestBody).addHeader("Authorization", "Bearer " + key).addHeader("api-key", key).addHeader("X-DashScope-SSE", "enable").build();log.info("url:{}", openaiUrl);log.info("requestBody:{}", body);Response response = chatEngineRetryService.execute(okClient, requestOpenai);return response;
}
- ExecutorServiceConfig.java
線程池配置類
import com.alibaba.ttl.threadpool.TtlExecutors;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** 業務配置。*/
@Configuration
@Data
public class ExecutorServiceConfig {/** 業務線程池核心線程數 */@Value("${chat.remote.request.pool.corePoolSize:25}")private int businessCorePoolSize;/** 業務線程池最大線程數 */@Value("${chat.remote.request.pool.maxPoolSize:50}")private int businessMaxPoolSize;/** 業務線程池最大空閑秒數 */@Value("${chat.remote.request.pool.keepAliveSeconds:60}")private int businessKeepAliveSeconds;/** 業務線程池任務隊列長度 */@Value("${chat.remote.request.pool.taskQueueSize:1000}")private int businessTaskQueueSize;@Bean(name = "chatRequestExecutor", destroyMethod = "shutdown")public ExecutorService chatRequestExecutor() {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(businessCorePoolSize, businessMaxPoolSize,businessKeepAliveSeconds, TimeUnit.SECONDS,new LinkedBlockingQueue<>(businessTaskQueueSize), new ThreadPoolExecutor.CallerRunsPolicy());return TtlExecutors.getTtlExecutorService(threadPoolExecutor);}}
- ChatEngineRetryService.java、ChatEngineRetryServiceImpl.java
OkClient調用封裝接口
// 接口
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;public interface ChatEngineRetryService {Response execute(OkHttpClient client, Request requestOpenai) throws RuntimeException;
}
OkClient調用封裝實現類
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.stereotype.Service;/*** 可增加重試機制*/
@Slf4j
@Service
public class ChatEngineRetryServiceImpl implements ChatEngineRetryService {/*** @param: client* @param: requestOpenai* @description: 封裝okClient* @return: okhttp3.Response* @author: niaonao* @date: 2025/6/25*/@Overridepublic Response execute(OkHttpClient client, Request requestOpenai) throws RuntimeException{try {Response response = client.newCall(requestOpenai).execute();log.info("engine res:{}", response.toString());if (null != response && response.code() != 200) {throw new RuntimeException("大模型引擎失敗,錯誤碼不是200");}return response;}catch (Exception e){log.error("調用引擎異常", e);throw new RuntimeException("大模型引擎失敗");}}
}
- ChatInput.java
大模型請求體封裝類
import lombok.Data;import java.io.Serializable;
import java.util.List;@Data
public class ChatInput<T> implements Serializable {private List<ChatMessage<T>> messages;private Boolean stream;private String system;private Double temperature;private String model;private String stop;private Integer max_tokens;private ChatResponseFormat response_format;/** 聯網搜索 */private Boolean enable_search;/** 深度思考-阿里云百煉 */private Boolean enable_thinking;/** 音頻大模型-阿里云百煉 */private ChatInputMessage input;/** 音頻大模型-流式輸出-阿里云百煉 */private ChatParameters parameters;
}
- ChatMessage.java
大模型請求體message封裝類
import lombok.Data;import java.io.Serializable;@Data
public class ChatMessage<T> implements Serializable {private String role;private T content;
}
- ChatInputMessage.java
音頻大模型請求體messages封裝
import lombok.Data;import java.io.Serializable;
import java.util.List;@Data
public class ChatInputMessage<T> implements Serializable {private List<ChatMessage<T>> messages;
}
- ChatParameters.java
音頻大模型開啟流式接口的屬性
import lombok.Data;import java.io.Serializable;@Data
public class ChatParameters implements Serializable {private Boolean incremental_output;
}
- 調用音頻大模型方法
@Value("${aliyuncs.audioModel:qwen-audio-turbo-latest}")
private String audioModel;@Value("${aliyuncs.audioUrl:https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation}")
private String apiAudioUrl;@Value("${aliyuncs.key:sk-xxx}")
private String apiKey;/*** @param: videoContentDTO* @param: emitter* @description: 視頻文案提取,傳入的是三方平臺視頻鏈接,提取視頻文案* @return: void 流式接口,通過SSE和前端交互* @author: niaonao* @date: 2025/6/24*/
@Override
public void videoLinkAnalysis(VideoContentDTO videoContentDTO, SseEmitter emitter) {// ...// 此處獲取到音頻鏈接String musicUrl = dyDetailVO.getAweme_detail().getMusic().getPlay_url().getUri();String userPrompt = "請提取音頻文案";Map<String, String> contentAudio = new HashMap<>(1);contentAudio.put("audio".intern(), musicUrl);Map<String, String> contentText = new HashMap<>(1);contentText.put("text".intern(), userPrompt);List<Map<String, String>> contentList = new ArrayList<>(2);contentList.add(contentAudio);contentList.add(contentText);ChatMessage<List<Map<String, String>>> chatMessage = new ChatMessage<List<Map<String, String>>>();chatMessage.setRole(PromptConstants.ROLE_USER);chatMessage.setContent(contentList);List<ChatMessage<List<Map<String, String>>>> messages = new ArrayList<>(1);messages.add(chatMessage);ChatInputMessage chatInputMessage = new ChatInputMessage();chatInputMessage.setMessages(messages);ChatInput<List<Map<String, String>>> chatInput = new ChatInput<List<Map<String, String>>>();chatInput.setInput(chatInputMessage);chatInput.setModel(audioModel);ChatParameters parameters = new ChatParameters();parameters.setIncremental_output(true);chatInput.setParameters(parameters);openAIApiService.streamIncrementalApi(chatInput, emitter, apiAudioUrl, apiKey);
}
參考文檔
大模型服務平臺百煉-視覺理解
大模型服務平臺百煉-音頻理解
三方平臺開放平臺-通過VideoID獲取IFrame代碼
Powered By niaonao