ue4模组开发中创建预览音频的问题

需求概述
近来为某个神秘游戏寻得一程序,通过ue4 mod的方式进行lua注入从而添加自定义音乐。随后一日,模组更新了特性,支持在bgm选择界面时加入了预览音频的逻辑。其自述文件如此写:
You can provide a separate audio file to be used as the preview (on the BGM select screen) instead of the default behavior of playing the main track from its loop start point.
Place a file named
<audio_file_name>_Previewwith any supported extension (.mp3,.wav,.ogg,.flac) alongside the main audio file. For example:Songs/ MY_AUDIO.wav MY_AUDIO_Preview.mp3 <- this will be used for the BGM select previewThe preview file does not need to match the main track’s format. Preview files are automatically excluded from the song list - they won’t appear as separate tracks.
可以看到,这个逻辑是非常傻瓜式的。然而当我在准备开始制作预览音频的时候发现,我竟然塞了快七十首歌,如果用au/audacity手动一个个处理的话是非常耗费时间的。
自动化实现
那么,如果要寻求脚本批处理的方法来“偷懒”,我第一个想到的是用ffmpeg来进行简单粗暴的一刀切式处理,统一截取音频的某个时间段落来做到省事。但是这种方法无疑是不好的,因为不同的歌曲编排不同,通常预览音频都是截取歌曲的高潮部分,而这种方式容易截取出来一些不知所谓的东西出来。
这时候,我竟然蠢人灵机一动,想起之前在某篇洋人写的文章里提到了python的Librosa库这个东西,
Librosa 是 Python 中专业、轻量、最常用的音频与音乐分析库,可以读取音频、计算节奏、BPM、音量、鼓点等特征,通过数学算法识别音频中能量最强、节奏最密集的片段,从而实现自动检测高潮、剪辑片段等功能,是做音频处理、音乐提取和自动化剪辑的核心工具。用这个库做一个程序实现,好像正好可以满足我既要又要的需求。
代码实现
import os
import numpy as np
import librosa
import ffmpeg这三行是导入库。os是处理文件用的,np是数学计算库,第三个是本尊,最后一个还得用他,因为后面要切分。
# ====================== 你可以改的参数 ======================
PREVIEW_DURATION = x # 预览音频长度(秒)
MIN_START = 10 # 不从最开头截,避免前奏空白
# ===========================================================赋值两个变量
def make_preview(audio_path):
# 定义一个函数,专门处理单个音频文件。
print(f"正在分析:{audio_path}")
# 打印文件名输出 (给自己看的)
# 加载音频
y, sr = librosa.load(audio_path, sr=None)
duration = librosa.get_duration(y=y, sr=sr)
# 读音频,得到:
# y = 音频波形数据
# sr = 采样率
# duration = 音频总时长
# 计算每帧能量
hop_length = 512
rms = librosa.feature.rms(y=y, hop_length=hop_length)[0]
times = librosa.times_like(rms, sr=sr, hop_length=hop_length)
# 只考虑 MIN_START ~ (总时长-预览长度) 之间的片段
valid_mask = times >= MIN_START
valid_times = times[valid_mask]
valid_rms = rms[valid_mask]
if len(valid_times) == 0:
start = 0
# 只看 10 秒以后 的片段,避免前奏。如果有效片段为空,就从 0 秒开始。
else:
# 找到能量最大的帧作为起点
best_idx = np.argmax(valid_rms)
start = valid_times[best_idx]
# 找到能量最大的位置 = 高潮起点。
# 保证不超尾
max_start = duration - PREVIEW_DURATION
start = min(start, max_start)
# 输出文件名(命名规则:原名_Preview.mp3)
base, ext = os.path.splitext(audio_path)
out_path = f"{base}_Preview.mp3"
try:
stream = ffmpeg.input(audio_path, ss=start, t=PREVIEW_DURATION)
stream = ffmpeg.output(stream, out_path, q:a=2)
ffmpeg.run(stream, overwrite_output=True, quiet=True)
# ffmpeg干的事:
'''
ss=start:从高潮开始
t=8:截 8 秒
q:a=2:高质量 MP3
overwrite:覆盖旧文件
quiet:不输出日志
'''
fname_lower = f.lower()
# 遍历文件
# 只处理原始音乐,跳过已生成的预览文件
if fname_lower.endswith(exts) and "_preview" not in fname_lower:
make_preview(f)
#统一转小写避免问题没了,再见。👋