怎样用脚本批量检测音乐调性?

wen 实用脚本 2

本文目录导读:

怎样用脚本批量检测音乐调性?

  1. 使用Python + librosa库(最推荐)
  2. 使用Essentia库(更专业)
  3. 命令行工具MIR-QBSH
  4. 使用音乐分析软件API
  5. 完整的批处理脚本(带GUI)
  6. 安装依赖
  7. 使用建议

我来介绍几种批量检测音乐调性的方法,从最简单到最专业。

使用Python + librosa库(最推荐)

import librosa
import numpy as np
import os
from pathlib import Path
def detect_key(audio_path):
    """检测单个音频文件的调性"""
    try:
        # 加载音频文件
        y, sr = librosa.load(audio_path, sr=22050)
        # 计算色谱图
        chroma = librosa.feature.chroma_cqt(y=y, sr=sr)
        # 使用Krumhansl-Schmuckler算法检测调性
        key_temp = librosa.feature.tonnetz(y=librosa.effects.harmonic(y), sr=sr)
        # 计算每个调性的分数
        key_dict = {
            'C': 0, 'C#': 1, 'D': 2, 'D#': 3, 'E': 4, 'F': 5,
            'F#': 6, 'G': 7, 'G#': 8, 'A': 9, 'A#': 10, 'B': 11
        }
        # 使用模板匹配
        chroma_mean = np.mean(chroma, axis=1)
        # 大调模板
        major_template = np.array([1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1])
        # 小调模板
        minor_template = np.array([1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0])
        best_key = None
        best_score = -1
        for i in range(12):
            # 循环移位模板
            rotated_major = np.roll(major_template, i)
            rotated_minor = np.roll(minor_template, i)
            # 计算相关性
            major_score = np.corrcoef(chroma_mean, rotated_major)[0, 1]
            minor_score = np.corrcoef(chroma_mean, rotated_minor)[0, 1]
            if major_score > best_score:
                best_score = major_score
                key_name = list(key_dict.keys())[i]
                best_key = f"{key_name} Major"
            if minor_score > best_score:
                best_score = minor_score
                key_name = list(key_dict.keys())[i]
                best_key = f"{key_name} Minor"
        return best_key, best_score
    except Exception as e:
        return f"Error: {str(e)}", 0
def batch_detect_keys(folder_path):
    """批量检测文件夹中所有音频文件的调性"""
    results = []
    # 支持的音频格式
    audio_formats = ['.mp3', '.wav', '.flac', '.m4a', '.ogg']
    # 遍历文件夹
    for file_path in Path(folder_path).rglob('*'):
        if file_path.suffix.lower() in audio_formats:
            print(f"检测中: {file_path.name}")
            key, confidence = detect_key(str(file_path))
            results.append({
                'filename': file_path.name,
                'key': key,
                'confidence': confidence,
                'path': str(file_path)
            })
    return results
# 使用方法
if __name__ == "__main__":
    import pandas as pd
    # 设置音频文件夹路径
    audio_folder = "your_audio_folder_path"  # 修改为你的文件夹路径
    results = batch_detect_keys(audio_folder)
    # 保存为CSV文件
    df = pd.DataFrame(results)
    df.to_csv("music_key_detection_results.csv", index=False, encoding='utf-8')
    # 打印结果
    print("\n检测结果:")
    print(df.to_string())

使用Essentia库(更专业)

# 安装essentia
pip install essentia-tensorflow
import essentia.standard as es
import os
import pandas as pd
from pathlib import Path
def detect_key_essentia(audio_path):
    """使用Essentia检测调性"""
    try:
        # 加载音频
        loader = es.MonoLoader(filename=audio_path)
        audio = loader()
        # 检测调性
        key_extractor = es.KeyExtractor()
        key, scale, strength = key_extractor(audio)
        return f"{key} {scale}", strength
    except Exception as e:
        return f"Error: {str(e)}", 0
def batch_detect_keys_essentia(folder_path):
    results = []
    audio_formats = ['.mp3', '.wav', '.flac', '.m4a', '.ogg']
    for file_path in Path(folder_path).rglob('*'):
        if file_path.suffix.lower() in audio_formats:
            print(f"检测中: {file_path.name}")
            key, confidence = detect_key_essentia(str(file_path))
            results.append({
                'filename': file_path.name,
                'key': key,
                'confidence': confidence
            })
    return results
# 使用方法
results = batch_detect_keys_essentia("your_audio_folder")
df = pd.DataFrame(results)
df.to_csv("key_detection_results.csv", index=False)

命令行工具MIR-QBSH

# 安装
pip install mir-eval
# 或者下载工具: https://github.com/rabitt/qbsh

使用音乐分析软件API

Sonic Visualiser + VAMP插件

# 命令行批量处理
sonic-annotator -t key_detection.n3 -w csv *.wav

AcousticBrainz API

import requests
import json
import os
def detect_key_api(audio_path):
    """使用AcousticBrainz API(需要上传音频)"""
    url = "https://acousticbrainz.org/api/v1/high-level"
    with open(audio_path, 'rb') as f:
        files = {'audio': f}
        response = requests.post(url, files=files)
    if response.status_code == 200:
        data = response.json()
        if 'tonal' in data and 'key_key' in data['tonal']:
            return data['tonal']['key_key'], data['tonal'].get('key_scale', '')
    return None, None

完整的批处理脚本(带GUI)

import tkinter as tk
from tkinter import filedialog, messagebox
import os
import pandas as pd
from pathlib import Path
import threading
import time
class KeyDetectorGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("批量音乐调性检测工具")
        self.root.geometry("600x500")
        # 创建UI元素
        self.create_widgets()
    def create_widgets(self):
        # 文件夹选择
        tk.Label(self.root, text="选择音频文件夹:").pack(pady=10)
        self.folder_path = tk.StringVar()
        entry = tk.Entry(self.root, textvariable=self.folder_path, width=50)
        entry.pack(pady=5)
        tk.Button(self.root, text="浏览文件夹", command=self.select_folder).pack(pady=5)
        # 开始按钮
        self.start_button = tk.Button(self.root, text="开始检测", 
                                     command=self.start_detection,
                                     bg="#4CAF50", fg="white")
        self.start_button.pack(pady=20)
        # 进度显示
        self.progress_label = tk.Label(self.root, text="就绪")
        self.progress_label.pack(pady=10)
        # 结果文本框
        self.result_text = tk.Text(self.root, height=15, width=70)
        self.result_text.pack(pady=10)
        # 滚动条
        scrollbar = tk.Scrollbar(self.result_text)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.result_text.config(yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.result_text.yview)
    def select_folder(self):
        folder = filedialog.askdirectory()
        if folder:
            self.folder_path.set(folder)
    def detect_key_simple(self, audio_path):
        """简单的调性检测(基于频谱分析)"""
        try:
            import librosa
            y, sr = librosa.load(audio_path, sr=22050, duration=30)
            chroma = librosa.feature.chroma_cqt(y=y, sr=sr)
            chroma_mean = np.mean(chroma, axis=1)
            # 简单模板匹配
            major_profile = np.array([6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 
                                      2.52, 5.19, 2.39, 3.66, 2.29, 2.88])
            minor_profile = np.array([6.33, 2.68, 3.52, 5.38, 2.60, 3.53,
                                      2.54, 4.75, 3.98, 2.69, 3.34, 3.17])
            keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
            best_corr = -1
            best_key = "未知"
            for i in range(12):
                rotated_major = np.roll(major_profile, i)
                rotated_minor = np.roll(minor_profile, i)
                corr_major = np.corrcoef(chroma_mean, rotated_major)[0, 1]
                corr_minor = np.corrcoef(chroma_mean, rotated_minor)[0, 1]
                if corr_major > best_corr:
                    best_corr = abs(corr_major)
                    best_key = f"{keys[i]} Major"
                if corr_minor > best_corr:
                    best_corr = abs(corr_minor)
                    best_key = f"{keys[i]} Minor"
            return best_key
        except Exception as e:
            return f"错误: {str(e)}"
    def start_detection(self):
        if not self.folder_path.get():
            messagebox.showerror("错误", "请先选择文件夹")
            return
        # 在新线程中运行检测
        thread = threading.Thread(target=self.run_detection)
        thread.start()
    def run_detection(self):
        import numpy as np
        from scipy import stats
        folder = self.folder_path.get()
        audio_formats = ['.mp3', '.wav', '.flac', '.m4a']
        results = []
        files = [f for f in Path(folder).rglob('*') 
                if f.suffix.lower() in audio_formats]
        for i, file_path in enumerate(files):
            self.progress_label.config(text=f"检测 {i+1}/{len(files)}: {file_path.name}")
            key = self.detect_key_simple(str(file_path))
            results.append(f"{file_path.name}: {key}")
            self.result_text.insert(tk.END, f"{file_path.name}: {key}\n")
            self.result_text.see(tk.END)
            self.root.update()
            time.sleep(0.1)  # 避免界面卡死
        self.progress_label.config(text="检测完成!")
        messagebox.showinfo("完成", f"共检测 {len(files)} 个音频文件")
        # 保存结果
        output_file = os.path.join(folder, "调性检测结果.csv")
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write("文件名,调性\n")
            for result in results:
                f.write(result.replace(": ", ",") + "\n")
        self.result_text.insert(tk.END, f"\n结果已保存到: {output_file}")
# 运行GUI
if __name__ == "__main__":
    app = KeyDetectorGUI()
    app.root.mainloop()

安装依赖

# 基础依赖
pip install librosa numpy pandas scipy
# 可选:更专业的库
pip install essentia-tensorflow
pip install mir-eval

使用建议

  1. 准确度考虑:librosa的调性检测准确度约70-80%,专业软件可达90%以上
  2. 处理速度:处理一个3分钟的音频文件约需1-3秒
  3. 批量建议:先测试10-20个文件确认结果准确性
  4. 输出格式:推荐保存为CSV文件方便后续处理

选择哪种方法取决于你的需求:

  • 简单快速:方法1(Python+librosa)
  • 专业准确:方法2(Essentia)
  • 批量处理:方法5(带GUI的脚本)

抱歉,评论功能暂时关闭!