Python实现音频去广告和字幕提取!
Python实现音频去广告和字幕提取!
之前下了一些音频课,但是存在一些音频中间插入广告,更万恶的是,它根本不分是不是整句,只要时间差不多了就插入。
要去掉广告我们分为以下步骤依次执行:
- 分析规律(就是前面找规律)
- 广告提取
- 识别广告
- 重新拼接
对于字幕提取,之前其实我们在 AI 相关的文章中也介绍过对应模型,直接转换并处理就可以了,后面再介绍。
分析规律
和写爬虫一样,第一点就是要找规律:用一张草稿纸记录每个广告的起始时间和结束时间,再分析它和整段音频的关系。
遗憾的是,在插入时或许是为了避免裁剪,逐秒计算(也叫做)后,我得出了一个结论:它是在固定时间(end_time - 3min) + random_offset 值,因为了 offset 值的介入,整个就变的玄学了起来。
还好很快我又有了一些新的想法:利用一些识别的手段把广告词裁掉就可以了。
还好广告词是固定的,而要处理的音频却多,这样计算下来 ROI 还是划算的。
广告提取
这一步是所有步骤里最耗费时间的,对于整句来说,切割分离是一个高敏感性的操作,稍微多留白几百毫秒,你听起来可能就很难受。只有原始数据切割的恰到好处,才能达到完美还原。
因此我们需要更精细化,精细到毫秒的裁剪手段。
Windows 下也不知道用啥,搜了下就选了 Audacity:
以毫秒控制选区,然后切割后如果听感是无缝的,那么就相当于抽离了,如果觉得怪怪的就再调整毫秒重新裁,如此反复直到无缝衔接。
依赖列表
下文完整的import
依赖(因为懒得在文末贴完整代码了):
1
2
3
4
5
6
7
8
9
10
11
12
|
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 |
识别广告
接下来我们得到了两个片段,一个是完整版的音频,另一个是纯广告音频,将对应波形的相似度进行比对,找到相似的段,再进行切割。
当然,由于整段二三十分钟,相对的来说计算量会很大,由于我们知道了总是在一个音频快结束了插入广告,因此可以先裁剪缩小对比规模,然后再进行比对,减少计算量。
其中有一些非常抽象的音频和数学知识,只能说谢谢 GPT 老师(我也没学会)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
# 已知的广告片段文件 AD_SNIPPET_FILE = "./testcase/test2.wav" # 待处理的音频文件目录 audio_dir = "./testcase" TAIL_SECONDS = 300 # 只截取最后5分钟处理 SIMILARITY_THRESHOLD = 0.8 # 相似度阈值(0~1之间, 需根据实际情况调整) 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 音频片段的出现位置。 snippet_audio 为事先加载好的 numpy 数组,snippet_norm 为 snippet 的二范数,用于相似度计算。 返回 (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 # 转换到 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 翻转) 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 |
重新拼接
找到广告后我们将广告段落减去,然后再重新拼接生成新的音频文件即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
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的范数,用于相似度归一化 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" )] # 使用多线程加速处理 # 线程数可根据您的机器资源调整 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}" ) |
字幕提取
下一个问题是,音频是提取好了,但是音频的字幕和总结能力其实也是一个亮点,这个也是我们想要有的能力,而好多都是付费的,百度网盘虽然会员免费,但是实际听音频的过程中遇到了 Bug,让我不得不另谋高就。
要使用这个能力,核心还是使用whisper这个模型的能力。
我考虑用 Emby 来当音频播放器,字幕可以和歌词字幕一样,因此就需要生成 lrc 格式的标准文件。
也就是:
- 提取字幕
- 给每段字幕和时间轴进行格式转换,转为 lrc 标准格式
而跑 AI 模型的时候,务必保证 GPU 加速(否则你会卡的痛不欲生)。
模型请根据自己的内存和实际情况决定,不一定是越大越好的,可以先跑一段音频试试效果。
如果本地没有找到对应的模型,whisper 先尝试下载,也可以使用本地准备好的模型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
def format_lrc_timestamp(seconds: float ) - > str : """将秒数转换为 LRC 格式时间戳 [mm:ss.xx]""" total_seconds = int (seconds) m = total_seconds / / 60 s = total_seconds % 60 # 毫秒取两位小数 ms = (seconds - total_seconds) * 100 return f "[{m:02d}:{s:02d}.{int(ms):02d}]" def trans_files(): # 请将此路径替换为你的音频目录路径 audio_directory = "./audio_files" # 可根据需要选择模型大小,如 "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 进行转录, 并将结果保存为 lrc_path 文件。 """ result = model.transcribe(audio_path, language = language) segments = result.get( "segments" , []) with open (lrc_path, "w" , encoding = "utf-8" ) as f: # 可根据需要添加标签信息,如标题、歌手、专辑 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 "使用设备: {device}" ) # 加载模型到指定设备 # 模型大小可根据资源调整,如: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" # 可根据需要选择模型大小,如 "small"、"medium"、"large" trans_text(audio_directory, model_name = "medium" , language = "zh" ) |
目前我还没有做总结功能(主要是普通播放器也没地方显示总结),但是有了全量文本,相信对于各位来说并不是难事。
总结
本文的所有代码均由 AI 编写,可以说过去让它写的代码更多的是提效用,我姑且还算一知半解,但是涉及到音频和数学知识的本功能我是真的一无所知,但它却能帮我做出一个非常完美的效果,真的是科技改变生活了。
以上就是Python实现音频去广告和字幕提取的详细内容。
学习资料见知识星球。
以上就是今天要分享的技巧,你学会了吗?若有什么问题,欢迎在下方留言。
快来试试吧,小琥 my21ke007。获取 1000个免费 Excel模板福利!
更多技巧, www.excelbook.cn
欢迎 加入 零售创新 知识星球,知识星球主要以数据分析、报告分享、数据工具讨论为主;
1、价值上万元的专业的PPT报告模板。
2、专业案例分析和解读笔记。
3、实用的Excel、Word、PPT技巧。
4、VIP讨论群,共享资源。
5、优惠的会员商品。
6、一次付费只需129元,即可下载本站文章涉及的文件和软件。
共有 0 条评论