Python實(shí)現(xiàn)音頻去廣告和字幕提取
之前下了一些音頻課,但是存在一些音頻中間插入廣告,更萬惡的是,它根本不分是不是整句,只要時(shí)間差不多了就插入。
要去掉廣告我們分為以下步驟依次執(zhí)行:
- 分析規(guī)律(就是前面找規(guī)律)
- 廣告提取
- 識別廣告
- 重新拼接
對于字幕提取,之前其實(shí)我們在 AI 相關(guān)的文章中也介紹過對應(yīng)模型,直接轉(zhuǎn)換并處理就可以了,后面再介紹。
分析規(guī)律
和寫爬蟲一樣,第一點(diǎn)就是要找規(guī)律:用一張草稿紙記錄每個(gè)廣告的起始時(shí)間和結(jié)束時(shí)間,再分析它和整段音頻的關(guān)系。
遺憾的是,在插入時(shí)或許是為了避免裁剪,逐秒計(jì)算(也叫做)后,我得出了一個(gè)結(jié)論:它是在固定時(shí)間(end_time - 3min) + random_offset 值,因?yàn)榱?offset 值的介入,整個(gè)就變的玄學(xué)了起來。
還好很快我又有了一些新的想法:利用一些識別的手段把廣告詞裁掉就可以了。
還好廣告詞是固定的,而要處理的音頻卻多,這樣計(jì)算下來 ROI 還是劃算的。
廣告提取
這一步是所有步驟里最耗費(fèi)時(shí)間的,對于整句來說,切割分離是一個(gè)高敏感性的操作,稍微多留白幾百毫秒,你聽起來可能就很難受。只有原始數(shù)據(jù)切割的恰到好處,才能達(dá)到完美還原。
因此我們需要更精細(xì)化,精細(xì)到毫秒的裁剪手段。
Windows 下也不知道用啥,搜了下就選了 Audacity:

以毫秒控制選區(qū),然后切割后如果聽感是無縫的,那么就相當(dāng)于抽離了,如果覺得怪怪的就再調(diào)整毫秒重新裁,如此反復(fù)直到無縫銜接。
依賴列表
下文完整的 import依賴(因?yàn)閼械迷谖哪┵N完整代碼了):
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
識別廣告
接下來我們得到了兩個(gè)片段,一個(gè)是完整版的音頻,另一個(gè)是純廣告音頻,將對應(yīng)波形的相似度進(jìn)行比對,找到相似的段,再進(jìn)行切割。
當(dāng)然,由于整段二三十分鐘,相對的來說計(jì)算量會很大,由于我們知道了總是在一個(gè)音頻快結(jié)束了插入廣告,因此可以先裁剪縮小對比規(guī)模,然后再進(jìn)行比對,減少計(jì)算量。
其中有一些非常抽象的音頻和數(shù)學(xué)知識,只能說謝謝 GPT 老師(我也沒學(xué)會)
# 已知的廣告片段文件
AD_SNIPPET_FILE = "./testcase/test2.wav"
# 待處理的音頻文件目錄
audio_dir = "./testcase"
TAIL_SECONDS = 300 # 只截取最后5分鐘處理
SIMILARITY_THRESHOLD = 0.8 # 相似度閾值(0~1之間, 需根據(jù)實(shí)際情況調(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ù),用于相似度計(jì)算。
返回 (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 來進(jìn)行類似相似度搜索 (無 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]
# 相似度計(jì)算:歸一化
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):
"""
處理單個(gè)文件,找到廣告并移除。
"""
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
# 預(yù)先加載廣告片段
snippet_audio, _ = librosa.load(AD_SNIPPET_FILE , sr=sr, mono=True)
# 計(jì)算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ù)您的機(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} 處理時(shí)出錯(cuò): {e}")
字幕提取
下一個(gè)問題是,音頻是提取好了,但是音頻的字幕和總結(jié)能力其實(shí)也是一個(gè)亮點(diǎn),這個(gè)也是我們想要有的能力,而好多都是付費(fèi)的,百度網(wǎng)盤雖然會員免費(fèi),但是實(shí)際聽音頻的過程中遇到了 Bug,讓我不得不另謀高就。
要使用這個(gè)能力,核心還是使用 whisper這個(gè)模型的能力。
我考慮用 Emby 來當(dāng)音頻播放器,字幕可以和歌詞字幕一樣,因此就需要生成 lrc 格式的標(biāo)準(zhǔn)文件。
也就是:
- 提取字幕
- 給每段字幕和時(shí)間軸進(jìn)行格式轉(zhuǎn)換,轉(zhuǎn)為 lrc 標(biāo)準(zhǔn)格式
而跑 AI 模型的時(shí)候,務(wù)必保證 GPU 加速(否則你會卡的痛不欲生)。
模型請根據(jù)自己的內(nèi)存和實(shí)際情況決定,不一定是越大越好的,可以先跑一段音頻試試效果。
如果本地沒有找到對應(yīng)的模型,whisper 先嘗試下載,也可以使用本地準(zhǔn)備好的模型。
def format_lrc_timestamp(seconds: float) -> str:
"""將秒數(shù)轉(zhuǎn)換為 LRC 格式時(shí)間戳 [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 進(jìn)行轉(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ù)需要添加標(biāo)簽信息,如標(biāo)題、歌手、專輯
f.write("[ti:未知標(biāo)題]\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ù)學(xué)知識的本功能我是真的一無所知,但它卻能幫我做出一個(gè)非常完美的效果,真的是科技改變生活了。
以上就是Python實(shí)現(xiàn)音頻去廣告和字幕提取的詳細(xì)內(nèi)容,更多關(guān)于Python音頻去廣告和字幕提取的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
gethostbyaddr在Python3中引發(fā)UnicodeDecodeError
本文介紹了gethostbyaddr()在Python?3中引發(fā)UnicodeDecodeError的處理方法,對大家解決問題具有一定的參考價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-05-05
Python實(shí)現(xiàn)批量合并多個(gè)txt文件并生成Excel文件
在數(shù)據(jù)處理中,有時(shí)會面臨合并多個(gè)文本文件的任務(wù),本文將詳細(xì)介紹如何使用Python批量合并多個(gè)txt文件,并將其生成為一個(gè)Excel文件,需要的可以參考下2023-12-12
Python+PyQT5的子線程更新UI界面的實(shí)例
今天小編就為大家分享一篇Python+PyQT5的子線程更新UI界面的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-06-06
Python?Pipeline處理數(shù)據(jù)工作原理探究
如果你是一個(gè)Python開發(fā)者,你可能聽過"pipeline"這個(gè)術(shù)語,但?pipeline?到底是什么,它又有什么用呢?在這篇文章中,我們將探討?Python?中的?pipeline?概念,它們是如何工作的,以及它們?nèi)绾螏椭憔帉懜逦?、更高效的代碼2024-01-01
Django values()和value_list()的使用
這篇文章主要介紹了Django values()和value_list()的使用,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Tensorflow?2.1完成對MPG回歸預(yù)測詳解
這篇文章主要為大家介紹了Tensorflow?2.1完成對MPG回歸預(yù)測詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
python3使用urllib模塊制作網(wǎng)絡(luò)爬蟲
本文給大家介紹的是利用urllib模塊通過指定的URL抓取網(wǎng)頁內(nèi)容 所謂網(wǎng)頁抓取,就是把URL地址中指定的網(wǎng)絡(luò)資源從網(wǎng)絡(luò)流中讀取出來,保存到本地,有需要的小伙伴可以參考下2016-04-04
python?open函數(shù)中newline參數(shù)實(shí)例詳解
newLine()方法可用于輸出一個(gè)換行字符"/n",下面這篇文章主要給大家介紹了關(guān)于python?open函數(shù)中newline參數(shù)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06

