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_())