音频分类标注工具

发布于:2025-08-18 ⋅ 阅读:(9) ⋅ 点赞:(0)

pyqt 分类标注工具:

import glob
import sys
import json
import os
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QTableWidget, QTableWidgetItem,
    QSplitter, QVBoxLayout, QWidget, QPushButton, QRadioButton,
    QButtonGroup, QLabel, QHBoxLayout, QMessageBox
)
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QColor
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent

class AudioAnnotator(QMainWindow):
    def __init__(self,base_dir):
        super().__init__()
        self.setWindowTitle("MP3标注工具 (单选标注)")
        self.setGeometry(100, 100, 1000, 600)  # 增大窗口尺寸
        
        # 初始化媒体播放器
        self.player = QMediaPlayer()
        
        # 主布局
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        layout = QVBoxLayout()
        main_widget.setLayout(layout)
        
        # 分割左右区域
        splitter = QSplitter(Qt.Horizontal)
        
        # 左侧:音频文件列表(表格)
        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["音频文件", "标注状态"])
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.cellClicked.connect(self.play_audio)
        self.table.setColumnWidth(0, 400)  # 文件名列宽
        self.table.setColumnWidth(1, 150)  # 状态列宽
        
        # 右侧:单选标注区域
        right_panel = QWidget()
        right_layout = QVBoxLayout()
        

        self.label_map = {1: "small", 2: "normal", 3: "多人",4:"纯音乐",5:"人+背景音乐",6:"环境音",7:"歌曲"}
        
        # 单选按钮组
        self.radio_group = QButtonGroup()
        
        right_layout.addWidget(QLabel("标注选项:"))
        

        for key, text in self.label_map.items():
            radio = QRadioButton(f"{text} ({key})")
            # layout.addWidget(radio)
            right_layout.addWidget(radio)
            self.radio_group.addButton(radio, key)  # 设置id为key

        # 绑定选择事件
        self.radio_group.buttonClicked[int].connect(self.on_radio_selected)

        right_layout.addStretch()
        
        right_panel.setLayout(right_layout)
        
        # 底部按钮
        self.btn_save = QPushButton("保存标注")
        self.btn_save.clicked.connect(self.save_annotation)
        
        # 添加到布局
        splitter.addWidget(self.table)
        splitter.addWidget(right_panel)
        layout.addWidget(splitter)
        layout.addWidget(self.btn_save)
        
        self.json_path=os.path.basename(base_dir)+'.json'
        files = glob.glob(base_dir + "/*.mp3")
        self.audio_files = files
        
        # 加载历史标注
        self.annotations = {}
        self.load_annotations()
        self.load_audio_files()

    def on_radio_selected(self, checked_id):
        print(f"选中了按钮 ID: {checked_id}")

        current_row = self.table.currentRow()
        if current_row >= 0:
            audio_file = self.audio_files[current_row]
            checked_id = self.radio_group.checkedId()
            
            if checked_id == -1:
                QMessageBox.warning(self, "警告", "请选择标注选项!")
                return
            
            # 更新标注字典
            self.annotations[audio_file] = checked_id
            
            # 保存到JSON文件
            with open(self.json_path, "w", encoding="utf-8") as f:
                json.dump(self.annotations, f, ensure_ascii=False, indent=4)
            
            # 更新表格显示
            self.load_audio_files()
            
    def load_audio_files(self):
        """加载音频文件到表格并显示标注状态"""
        self.table.setRowCount(len(self.audio_files))
       
        for i, file in enumerate(self.audio_files):
            # 文件名列
            file_item = QTableWidgetItem(file)
            self.table.setItem(i, 0, file_item)
            
            # 状态列
            status_item = QTableWidgetItem()
            if file in self.annotations:
                label_id = self.annotations[file]
                status_item.setText(f"已标: {self.label_map.get(label_id, '未知')}")
                file_item.setBackground(QColor(200, 255, 200))  # 浅绿色背景
                status_item.setBackground(QColor(200, 255, 200))
            else:
                status_item.setText("未标")
                file_item.setBackground(QColor(255, 200, 200))  # 浅红色背景
                status_item.setBackground(QColor(255, 200, 200))
            
            self.table.setItem(i, 1, status_item)
    
    def play_audio(self, row, column):
        if column > 0:  # 只在点击第一列时触发
            return
        file_path = self.audio_files[row]
        print('start play',file_path)
        media_content = QMediaContent(QUrl.fromLocalFile(file_path))
        self.player.setMedia(media_content)
        self.player.play()
        
        # 加载该音频的历史标注
        if file_path in self.annotations:
            checked_id = self.annotations[file_path]
            self.radio_group.button(checked_id).setChecked(True)
        else:
            self.radio_group.setExclusive(False)
            for btn in self.radio_group.buttons():
                btn.setChecked(False)
            self.radio_group.setExclusive(True)
    
    def save_annotation(self):
        """保存标注到JSON文件"""
        current_row = self.table.currentRow()
        if current_row >= 0:
            audio_file = self.audio_files[current_row]
            checked_id = self.radio_group.checkedId()
            
            if checked_id == -1:
                QMessageBox.warning(self, "警告", "请选择标注选项!")
                return
            
            # 更新标注字典
            self.annotations[audio_file] = checked_id
            
            # 保存到JSON文件
            with open(self.json_path, "w", encoding="utf-8") as f:
                json.dump(self.annotations, f, ensure_ascii=False, indent=4)
            
            # 更新表格显示
            self.load_audio_files()
            
            QMessageBox.information(self, "成功", f"标注已保存:{os.path.basename(audio_file)} -> {checked_id}")
        else:
            QMessageBox.warning(self, "警告", "请先选中音频文件!")
    
    def load_annotations(self):
        """加载历史标注文件"""
        if os.path.exists(self.json_path):
            try:
                with open(self.json_path, "r", encoding="utf-8") as f:
                    self.annotations = json.load(f)
                # 转换键为绝对路径(如果保存的是相对路径)
                base_dir = os.path.dirname(os.path.abspath(self.json_path))
                fixed_annotations = {}
                for k, v in self.annotations.items():
                    if not os.path.isabs(k):
                        fixed_path = os.path.join(base_dir, k)
                        if os.path.exists(fixed_path):
                            fixed_annotations[fixed_path] = v
                        else:
                            fixed_annotations[k] = v
                    else:
                        fixed_annotations[k] = v
                self.annotations = fixed_annotations
            except Exception as e:
                QMessageBox.warning(self, "警告", f"加载标注文件出错: {str(e)}")
                self.annotations = {}

if __name__ == "__main__":

    base_dir = r"/Users/lbg/Documents/data/audio_0817_low"
    base_dir = r"/Users/lbg/Documents/audio/audio_0817_normal"
    app = QApplication(sys.argv)
    window = AudioAnnotator(base_dir)
    window.show()
    sys.exit(app.exec_())


网站公告

今日签到

点亮在社区的每一天
去签到