testqkl 发表于 2024-9-5 18:19

AI辅助编写的cue和wav音乐文件的歌曲分割提取

本帖最后由 testqkl 于 2024-9-13 14:31 编辑

工具说明
cue和wav音乐文件的歌曲分割提取
ai辅助快速开发
通过百度的文小言辅助,自己修修改改的cue和wav音乐文件,需要先本地安装ffmpeg,如果不在path环境,可以自己写ffmpeg的全路径地址。

tikinter版本截图


Tkinter版本代码
# mac brew install tk-devel
$ brew install tk-devel


#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import (
    Tk,
    filedialog,
    messagebox,
    Label,
    Text,
    Button,
    END,
    Scrollbar,
    RIGHT,
    Y,
)
from functools import partial
import time
import os
import re
import subprocess
import json


class MUSIC_CUE_CUTE_GUI:
    def __init__(self, init_window_name):
      self.init_window_name = init_window_name
      self.cue_file_path = ""
      self.wav_file_path = ""
      self.wav_result_path = ""
      self.tracks = []
      self.ffmpeg = "ffmpeg"

    def reset_params(self):
      self.cue_file_path = ""
      self.wav_file_path = ""
      self.wav_result_path = ""
      self.tracks = []

    # 设置窗口
    def set_init_window(self):
      self.init_window_name.title("CUE音频提取器_v1.0.0")# 窗口名
      # self.init_window_name.geometry('320x160+10+10')                         #290 160为窗口大小,+10 +10 定义窗口弹出时的默认展示位置
      self.init_window_name.geometry("681x681+10+10")
      # self.init_window_name["bg"] = "pink"                                    #窗口背景色,其他背景色见:blog.csdn.net/chl0000/article/details/7657887
      # self.init_window_name.attributes("-alpha",0.9)                        #虚化,值越小虚化程度越高
      # 标签

      # 按钮
      self.cue_file_button = Button(
            self.init_window_name,
            text="打开CUE文件",
            bg="lightblue",
            width=16,
            command=lambda: self.open_file("cue"),
      )# 调用内部方法加()为直接调用
      self.cue_file_button.grid(row=0, column=0)

      self.wav_file_button = Button(
            self.init_window_name,
            text="打开WAV文件",
            bg="lightblue",
            width=16,
            command=lambda: self.open_file("wav"),
      )# 调用内部方法加()为直接调用
      self.wav_file_button.grid(row=0, column=1)

      self.detect_ffmpeg_button = Button(
            self.init_window_name,
            text="检测ffmpeg环境",
            bg="lightblue",
            width=16,
            command=lambda: self.detect_ffmpeg(),
      )# 调用内部方法加()为直接调用
      self.detect_ffmpeg_button.grid(row=0, column=2)

      self.parse_cue_button = Button(
            self.init_window_name,
            text="读取cue歌曲信息",
            bg="lightblue",
            width=16,
            command=lambda: self.parse_cue(),
      )# 调用内部方法加()为直接调用
      self.parse_cue_button.grid(row=1, column=0)

      self.execute_button = Button(
            self.init_window_name,
            text=">> 提取音乐",
            bg="lightblue",
            width=16,
            command=lambda: self.split_audio(),
      )# 调用内部方法加()为直接调用
      self.execute_button.grid(row=1, column=1)

      self.result_data_label = Label(self.init_window_name, text="")
      self.result_data_label.grid(
            row=2, column=0, rowspan=1, columnspan=5, sticky="W"
      )

      self.result_data_Text = Text(self.init_window_name)# 处理结果展示
      self.result_data_Text.grid(row=3, column=0, rowspan=5, columnspan=5)

      self.readme_text = Text(self.init_window_name, height=10)
      self.readme_text.grid(row=9, column=0, rowspan=2, columnspan=5, sticky="W")
      self.readme_text.insert(END, "使用说明:\n")
      self.readme_text.insert(END, "1. 请先检测ffmpeg环境\n")
      self.readme_text.insert(
            END, "2. 选择cue文件,如果wav和cue在同目录且同名,可不用操作wav文件\n"
      )
      self.readme_text.insert(END, "3. 尝试先读取cue文件的歌曲清单,做确认提取音乐\n")
      self.readme_text.insert(END, "4. 执行提取音乐\n")
      # self.readme_text.config(state="disable")

      # # 创建滚动条
      # scrollbar = Scrollbar(self.init_window_name)
      # scrollbar.pack(side=RIGHT, fill=Y)

      # # 将滚动条与Text组件关联
      # self.readme_text.config(yscrollcommand=scrollbar.set)
      # scrollbar.config(command=self.readme_text.yview)

    def detect_ffmpeg(self):
      try:
            subprocess.run()
            messagebox.showinfo("ffmpeg检测", "你已正确安装")
      except FileNotFoundError:
            messagebox.showwarning(
                "ffmpeg检测", "你未正确安装,请先安装ffmpeg到系统环境"
            )

    def open_file(self, t="cue"):
      filetypes = [("All files", "*.*")]
      if t == "cue":
            filetypes = [("Cue files", "*.cue")]
      elif t == "wav":
            filetypes = [("Wav files", "*.wav")]
      print(filetypes, t, self)
      file_path = filedialog.askopenfilename(
            title="选择" + t + "文件", filetypes=filetypes
      )
      if t == "cue":
            self.cue_file_path = file_path
            if self.wav_file_path == "":
                self.wav_file_path = self.replace_file_suffix(file_path, ".wav")
                self.wav_result_path = os.path.dirname(self.wav_file_path)
      elif t == "wav":
            if self.cue_file_path == "":
                self.cue_file_path = self.replace_file_suffix(file_path, ".cue")
            self.wav_file_path = file_path
            self.wav_result_path = os.path.dirname(self.wav_file_path)

      self.print_log("你选择了" + t + "文件: " + self.cue_file_path + "\n")
      self.print_log(
            "系统默认给你选择了"
            + ("wav" if t == "cue" else "cue")
            + "文件: "
            + self.wav_file_path
            + "\n",
      )

      print(self.cue_file_path, self.wav_file_path)

    def replace_file_suffix(self, file_path, new_suffix):
      # 使用os.path.splitext()分割文件路径为基本部分和扩展名
      base_name, ext = os.path.splitext(file_path)
      # 确保替换的是扩展名(去掉点号)
      if ext and ext == ".":
            # 使用新的后缀替换旧的后缀,确保新后缀前有点号
            new_file_path = base_name + new_suffix
      else:
            # 如果没有找到扩展名,直接添加新后缀
            new_file_path = file_path + new_suffix
      return new_file_path

    def parse_cue(self):
      tracks = []
      current_track = None

      if self.cue_file_path == "":
            messagebox.showwarning("警告", "请先选择cue文件再来操作")

      with open(self.cue_file_path, "r", encoding="GB2312") as file:
            for line in file:
                line = line.strip()

                # 检查是否为新的音轨开始
                if line.startswith("TRACK"):
                  if current_track:
                        tracks.append(current_track)
                  match = re.match(r"TRACK (\d+) AUDIO", line)
                  if match:
                        track_number = int(match.group(1))
                        current_track = {
                            "number": track_number,
                            "title": "",
                            "start": None,
                        }

                # 提取歌曲名
                elif line.startswith("TITLE"):
                  title = line.split('TITLE "').split('"')
                  if current_track:
                        current_track["title"] = title

                # 提取起始时间
                elif line.startswith("INDEX 01"):
                  match = re.match(r"INDEX 01 ((\d+):(\d+):(\d+))", line)
                  if match:
                        # minutes, seconds, frames = map(int, match.groups())
                        # 将时间转换为毫秒
                        # total_seconds = minutes * 60 + seconds + frames / 75.0
                        # start_time = int(total_seconds * 1000)
                        start_time, minutes, seconds, ms = match.groups()
                        if current_track:
                            current_track["start"] = minutes + ":" + seconds + "." + ms

                # 注意:.cue文件通常不直接包含结束时间,但可以通过下一个音轨的起始时间推断

      # 添加最后一个音轨(如果有)
      if current_track:
            tracks.append(current_track)

      self.tracks = tracks

      self.print_log("提取到{}首音乐".format(len(self.tracks)))
      self.print_log(
            "\n".join(
                list(map(lambda x: json.dumps(x, ensure_ascii=False), self.tracks))
            )
      )

    def split_audio(self):
      if len(self.tracks) == 0:
            self.parse_cue()

      if len(self.tracks) == 0:
            self.print_log("cue音频提起失败")

      """根据 CUE 文件中的时间信息分割 WAV 文件"""
      for i, track in enumerate(self.tracks):
            # print(i, track)
            start_time = track["start"]

            # 使用 ffmpeg 剪切音频
            output_file = (
                self.wav_result_path + os.sep + track["title"].strip() + ".wav"
            )

            # 最后一个
            if len(self.tracks) == i + 1:
                # 如果是到文件末尾,则不需要指定结束时间
                cuteCmd = [
                  self.ffmpeg,
                  "-i",
                  self.wav_file_path,
                  "-y",
                  "-ss",
                  start_time,
                  "-vn",
                  "-acodec",
                  "copy",
                  output_file,
                ]
            else:
                # 如果是第一个音轨,没有前一个音轨的结束时间,所以从头开始
                next_track = self.tracks# 获取前一个音轨的信息(注意索引)
                cuteCmd = [
                  self.ffmpeg,
                  "-i",
                  self.wav_file_path,
                  "-y",
                  "-ss",
                  start_time,
                  "-to",
                  next_track["start"],
                  "-vn",
                  "-acodec",
                  "copy",
                  output_file,
                ]

            self.print_log(i + 1 + ": " + " ".join(cuteCmd))
            subprocess.run(cuteCmd)

      self.print_log("cue音频提起完成")
      self.reset_params()

    # 获取当前时间
    def get_current_time(self):
      current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
      return current_time

    def print_log(self, text):
      self.result_data_Text.insert(END, self.get_current_time() + ": " + text + "\n")


def gui_start():
    init_window = Tk()# 实例化出一个父窗口
    MUSIC_CUE_CUTE_GUI(init_window).set_init_window()
    init_window.mainloop()# 父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示

if __name__ == "__main__":
    gui_start()



旧代码:
import re
import subprocess

def parse_cue(cue_file_path):
    tracks = []
    current_track = None
      
    with open(cue_file_path, 'r', encoding='GB2312') as file:
      for line in file:
            line = line.strip()
            
            # 检查是否为新的音轨开始
            if line.startswith('TRACK'):
                if current_track:
                  tracks.append(current_track)
                match = re.match(r'TRACK (\d+) AUDIO', line)
                if match:
                  track_number = int(match.group(1))
                  current_track = {'number': track_number, 'title': '', 'start': None, 'end': None}
            
            # 提取歌曲名
            elif line.startswith('TITLE'):
                title = line.split('TITLE "').split('"')
                if current_track:
                  current_track['title'] = title
            
            # 提取起始时间
            elif line.startswith('INDEX 01'):
                match = re.match(r'INDEX 01 ((\d+):(\d+):(\d+))', line)
                if match:
                  # minutes, seconds, frames = map(int, match.groups())
                  # 将时间转换为毫秒
                  # total_seconds = minutes * 60 + seconds + frames / 75.0
                  # start_time = int(total_seconds * 1000)
                  start_time,minutes,seconds,ms = match.groups()
                  if current_track:
                        current_track['start'] = minutes+":"+ seconds+"."+ms
            
            # 注意:.cue文件通常不直接包含结束时间,但可以通过下一个音轨的起始时间推断

    # 添加最后一个音轨(如果有)
    if current_track:
      tracks.append(current_track)
      
    return tracks

def split_audio(wav_file, tracks):
    """ 根据 CUE 文件中的时间信息分割 WAV 文件 """
    for i, track in enumerate(tracks):
      print(i, track)
      start_time = track['start']
      if (len(tracks) ==i+1):
            # 如果是到文件末尾,则不需要指定结束时间
            end_cmd = ''
            end_time = ''
      else:
            # 如果是第一个音轨,没有前一个音轨的结束时间,所以从头开始
            next_track = tracks# 获取前一个音轨的信息(注意索引)
            end_cmd = '-to'
            end_time = next_track['start']
         
      # 使用 ffmpeg 剪切音频
      output_file = track['title'].strip()+".wav"
      print(' '.join(['ffmpeg', '-ss', start_time, end_cmd, end_time, '-i', wav_file, '-y', '-vn', '-acodec', 'copy', output_file]))
      subprocess.run([
            'ffmpeg', '-ss', start_time, end_cmd, end_time, '-i', wav_file, '-y', '-vn', '-acodec', 'copy', output_file
      ])


# 使用示例
cue_file_path = './1.cue'
wav_file = './1.wav'
tracks = parse_cue(cue_file_path)
for track in tracks:
    print(f"Track {track['number']}: {track['title']}, Start: {track['start']}")
    # 注意:这里没有打印结束时间,因为它通常需要从下一个音轨的起始时间推断

confirm = input('确认分割音频:')
if confirm.lower() == 'y' or confirm.lower() == 'yes':
    split_audio(wav_file, tracks)
else:
    print("你已取消cue音频提取执行")

ot1686 发表于 2024-9-11 16:27

本帖最后由 ot1686 于 2024-9-11 16:30 编辑

大佬您的代码是不是有错误....因为假如是10首歌...但分割9首出来最后一首都有问题无法分割...小弟测试了几个cue.....都是少一首歌曲的....但....还是谢谢您..{:1_921:}

都出现了以下问题

Unable to choose an output format for ''; use a standard extension for the filename or specify the format manually.
Error initializing the muxer for : Invalid argument
Error opening output file .
Error opening output files: Invalid argument

wkdxz 发表于 2024-9-6 09:29

本帖最后由 wkdxz 于 2024-9-6 09:30 编辑

我是这么做的:把CUE文件拖入foobar2000,即可自动提取出播放列表(包含歌曲名,演唱者等),然后选中歌曲右键转换为其他格式或者分割。

foobar2000 v2.2 preview 播放器下载
https://www.52pojie.cn/thread-1895502-1-1.html
(出处: 吾爱破解论坛)

无痕978 发表于 2024-9-5 21:43

厉害,建议写出对话过程

miraak 发表于 2024-9-5 22:43

。。。。
这都要用ai?

amn2007 发表于 2024-9-6 05:09

参考一下

masteryun 发表于 2024-9-6 08:18

ffmpeg还能无损吗?

FishDreamer 发表于 2024-9-6 10:27

用foobar就可以

testqkl 发表于 2024-9-6 11:46

wkdxz 发表于 2024-9-6 09:29
我是这么做的:把CUE文件拖入foobar2000,即可自动提取出播放列表(包含歌曲名,演唱者等),然后选中歌曲 ...

这个挺好,好像只有win,没有mac版本

testqkl 发表于 2024-9-6 11:48

masteryun 发表于 2024-9-6 08:18
ffmpeg还能无损吗?

ffmpeg copy,应该是无损
页: [1] 2
查看完整版本: AI辅助编写的cue和wav音乐文件的歌曲分割提取