Python實現(xiàn)音頻去廣告和字幕提取
之前下了一些音頻課,但是存在一些音頻中間插入廣告,更萬惡的是,它根本不分是不是整句,只要時間差不多了就插入。
要去掉廣告我們分為以下步驟依次執(zhí)行:
- 分析規(guī)律(就是前面找規(guī)律)
- 廣告提取
- 識別廣告
- 重新拼接
對于字幕提取,之前其實我們在 AI 相關(guān)的文章中也介紹過對應模型,直接轉(zhuǎn)換并處理就可以了,后面再介紹。
分析規(guī)律
和寫爬蟲一樣,第一點就是要找規(guī)律:用一張草稿紙記錄每個廣告的起始時間和結(jié)束時間,再分析它和整段音頻的關(guān)系。
遺憾的是,在插入時或許是為了避免裁剪,逐秒計算(也叫做)后,我得出了一個結(jié)論:它是在固定時間(end_time - 3min) + random_offset 值,因為了 offset 值的介入,整個就變的玄學了起來。
還好很快我又有了一些新的想法:利用一些識別的手段把廣告詞裁掉就可以了。
還好廣告詞是固定的,而要處理的音頻卻多,這樣計算下來 ROI 還是劃算的。
廣告提取
這一步是所有步驟里最耗費時間的,對于整句來說,切割分離是一個高敏感性的操作,稍微多留白幾百毫秒,你聽起來可能就很難受。只有原始數(shù)據(jù)切割的恰到好處,才能達到完美還原。
因此我們需要更精細化,精細到毫秒的裁剪手段。
Windows 下也不知道用啥,搜了下就選了 Audacity:
以毫秒控制選區(qū),然后切割后如果聽感是無縫的,那么就相當于抽離了,如果覺得怪怪的就再調(diào)整毫秒重新裁,如此反復直到無縫銜接。
依賴列表
下文完整的 import
依賴(因為懶得在文末貼完整代碼了):
import glob import os from concurrent.futures import ThreadPoolExecutor, as_completed import numpy as np import librosa import torch import whisper from pydub import AudioSegment import soundfile as sf import torch.nn.functional as F import shutil
識別廣告
接下來我們得到了兩個片段,一個是完整版的音頻,另一個是純廣告音頻,將對應波形的相似度進行比對,找到相似的段,再進行切割。
當然,由于整段二三十分鐘,相對的來說計算量會很大,由于我們知道了總是在一個音頻快結(jié)束了插入廣告,因此可以先裁剪縮小對比規(guī)模,然后再進行比對,減少計算量。
其中有一些非常抽象的音頻和數(shù)學知識,只能說謝謝 GPT 老師(我也沒學會)
# 已知的廣告片段文件 AD_SNIPPET_FILE = "./testcase/test2.wav" # 待處理的音頻文件目錄 audio_dir = "./testcase" TAIL_SECONDS = 300 # 只截取最后5分鐘處理 SIMILARITY_THRESHOLD = 0.8 # 相似度閾值(0~1之間, 需根據(jù)實際情況調(diào)整) SUCCESS_DIR = "./success" device = torch.device("cuda" if torch.cuda.is_available() else "cpu") def load_audio_segment(file_path, sr=16000, tail_seconds=None): info = sf.info(file_path) total_duration = info.duration if tail_seconds is not None and tail_seconds < total_duration: start_time = total_duration - tail_seconds audio, _ = librosa.load(file_path, sr=sr, mono=True, offset=start_time, duration=tail_seconds) return audio, start_time else: audio, _ = librosa.load(file_path, sr=sr, mono=True) return audio, 0.0 def find_audio_snippet(main_audio_path, snippet_audio, snippet_norm, sr=16000, tail_seconds=300): """ 在 main_audio_path 中尋找 snippet_audio 音頻片段的出現(xiàn)位置。 snippet_audio 為事先加載好的 numpy 數(shù)組,snippet_norm 為 snippet 的二范數(shù),用于相似度計算。 返回 (ad_start_time, ad_end_time, similarity) 若未找到則返回 (None, None, None) """ main_audio, main_start = load_audio_segment(main_audio_path, sr=sr, tail_seconds=tail_seconds) if len(snippet_audio) > len(main_audio): return None, None, None # 轉(zhuǎn)換到 GPU 張量 main_audio_t = torch.from_numpy(main_audio).float().to(device).unsqueeze(0).unsqueeze(0) # [1,1,M] snippet_audio_t = torch.from_numpy(snippet_audio).float().to(device).unsqueeze(0).unsqueeze(0) # [1,1,S] # 使用 conv1d 來進行類似相似度搜索 (無 snippet 翻轉(zhuǎn)) correlation = F.conv1d(main_audio_t, snippet_audio_t) correlation = correlation[0, 0].cpu().numpy() best_index = np.argmax(correlation) best_value = correlation[best_index] # 相似度計算:歸一化 similarity = best_value / snippet_norm if snippet_norm > 0 else 0 ad_start_time = main_start + best_index / sr ad_end_time = ad_start_time + len(snippet_audio) / s return ad_start_time, ad_end_time, similarity
重新拼接
找到廣告后我們將廣告段落減去,然后再重新拼接生成新的音頻文件即可。
def process_file(filename, snippet_audio, snippet_norm, sr=16000, tail_seconds=300, similarity_threshold=0.8): """ 處理單個文件,找到廣告并移除。 """ ad_start, ad_end, similarity = find_audio_snippet(filename, snippet_audio, snippet_norm, sr=sr, tail_seconds=tail_seconds) if ad_start is not None and similarity > similarity_threshold: # 去除廣告段落 audio = AudioSegment.from_file(filename) part1 = audio[:ad_start * 1000] part2 = audio[ad_end * 1000:] cleaned = part1 + part2 cleaned_file = f"output/{os.path.basename(filename)}" cleaned.export(cleaned_file, format="mp3") shutil.move(filename, SUCCESS_DIR) return f"{filename} 已移除廣告,生成 {cleaned_file},相似度:{similarity}" else: return f"{filename} 中未高相似度檢測到廣告或相似度過低({similarity})" def remove_ads(): sr = 16000 # 預先加載廣告片段 snippet_audio, _ = librosa.load(AD_SNIPPET_FILE , sr=sr, mono=True) # 計算snippet的范數(shù),用于相似度歸一化 snippet_norm = np.dot(snippet_audio, snippet_audio) file_list = [os.path.join(audio_dir, f) for f in os.listdir(audio_dir) if f.endswith(".mp3")] # 使用多線程加速處理 # 線程數(shù)可根據(jù)您的機器資源調(diào)整 max_workers = 20 results = [] with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = { executor.submit(process_file, file, snippet_audio, snippet_norm, sr, TAIL_SECONDS, SIMILARITY_THRESHOLD): file for file in file_list } for future in as_completed(futures): file = futures[future] try: res = future.result() print(res) except Exception as e: print(f"{file} 處理時出錯: {e}")
字幕提取
下一個問題是,音頻是提取好了,但是音頻的字幕和總結(jié)能力其實也是一個亮點,這個也是我們想要有的能力,而好多都是付費的,百度網(wǎng)盤雖然會員免費,但是實際聽音頻的過程中遇到了 Bug,讓我不得不另謀高就。
要使用這個能力,核心還是使用 whisper這個模型的能力。
我考慮用 Emby 來當音頻播放器,字幕可以和歌詞字幕一樣,因此就需要生成 lrc 格式的標準文件。
也就是:
- 提取字幕
- 給每段字幕和時間軸進行格式轉(zhuǎn)換,轉(zhuǎn)為 lrc 標準格式
而跑 AI 模型的時候,務必保證 GPU 加速(否則你會卡的痛不欲生)。
模型請根據(jù)自己的內(nèi)存和實際情況決定,不一定是越大越好的,可以先跑一段音頻試試效果。
如果本地沒有找到對應的模型,whisper 先嘗試下載,也可以使用本地準備好的模型。
def format_lrc_timestamp(seconds: float) -> str: """將秒數(shù)轉(zhuǎn)換為 LRC 格式時間戳 [mm:ss.xx]""" total_seconds = int(seconds) m = total_seconds // 60 s = total_seconds % 60 # 毫秒取兩位小數(shù) ms = (seconds - total_seconds) * 100 return f"[{m:02d}:{s:02d}.{int(ms):02d}]" def trans_files(): # 請將此路徑替換為你的音頻目錄路徑 audio_directory = "./audio_files" # 可根據(jù)需要選擇模型大小,如 "small"、"medium"、"large" trans_text(audio_directory, model_name="medium", language="zh") def transcribe_to_lrc(audio_path: str, lrc_path: str, model, language: str = "zh"): """ 使用已加載的 whisper model 對 audio_path 進行轉(zhuǎn)錄, 并將結(jié)果保存為 lrc_path 文件。 """ result = model.transcribe(audio_path, language=language) segments = result.get("segments", []) with open(lrc_path, "w", encoding="utf-8") as f: # 可根據(jù)需要添加標簽信息,如標題、歌手、專輯 f.write("[ti:未知標題]\n") f.write("[ar:未知作者]\n") f.write("[al:未知專輯]\n\n") for seg in segments: start_time = format_lrc_timestamp(seg['start']) text = seg['text'].strip() f.write(f"{start_time}{text}\n") def trans_text(audio_dir: str, model_name: str = "medium", language: str = "zh"): # 嘗試使用 GPU device = "cuda:0" if torch.cuda.is_available() else "cpu" print(f"使用設(shè)備: {device}") # 加載模型到指定設(shè)備 # 模型大小可根據(jù)資源調(diào)整,如:tiny, base, small, medium, large print(f"加載 Whisper 模型 ({model_name}),請稍候...") model = whisper.load_model(model_name, device=device) print("模型加載完成。") # 遍歷指定目錄下所有 mp3 audio_files = glob.glob(os.path.join(audio_dir, "*.mp3")) if not audio_files: print("指定目錄中未找到 MP3 文件。") return for audio_path in audio_files: base_name = os.path.splitext(audio_path)[0] lrc_path = base_name + ".lrc" print(f"處理文件: {audio_path} -> {lrc_path}") transcribe_to_lrc(audio_path, lrc_path, model=model, language=language) print(f"完成: {lrc_path}") print("所有文件處理完成!") def trans_files(): # 請將此路徑替換為你的音頻目錄路徑 audio_directory = "./audio_files" # 可根據(jù)需要選擇模型大小,如 "small"、"medium"、"large" trans_text(audio_directory, model_name="medium", language="zh")
目前我還沒有做總結(jié)功能(主要是普通播放器也沒地方顯示總結(jié)),但是有了全量文本,相信對于各位來說并不是難事。
總結(jié)
本文的所有代碼均由 AI 編寫,可以說過去讓它寫的代碼更多的是提效用,我姑且還算一知半解,但是涉及到音頻和數(shù)學知識的本功能我是真的一無所知,但它卻能幫我做出一個非常完美的效果,真的是科技改變生活了。
以上就是Python實現(xiàn)音頻去廣告和字幕提取的詳細內(nèi)容,更多關(guān)于Python音頻去廣告和字幕提取的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
gethostbyaddr在Python3中引發(fā)UnicodeDecodeError
本文介紹了gethostbyaddr()在Python?3中引發(fā)UnicodeDecodeError的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧2022-05-05Python實現(xiàn)批量合并多個txt文件并生成Excel文件
在數(shù)據(jù)處理中,有時會面臨合并多個文本文件的任務,本文將詳細介紹如何使用Python批量合并多個txt文件,并將其生成為一個Excel文件,需要的可以參考下2023-12-12Python?Pipeline處理數(shù)據(jù)工作原理探究
如果你是一個Python開發(fā)者,你可能聽過"pipeline"這個術(shù)語,但?pipeline?到底是什么,它又有什么用呢?在這篇文章中,我們將探討?Python?中的?pipeline?概念,它們是如何工作的,以及它們?nèi)绾螏椭憔帉懜逦?、更高效的代碼2024-01-01Django values()和value_list()的使用
這篇文章主要介紹了Django values()和value_list()的使用,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03python?open函數(shù)中newline參數(shù)實例詳解
newLine()方法可用于輸出一個換行字符"/n",下面這篇文章主要給大家介紹了關(guān)于python?open函數(shù)中newline參數(shù)的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-06-06