Mp3 & srt splitter

cmd:pip install tkinterdnd2 pysrt

image

#pip install tkinterdnd2 pysrt

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinterdnd2 import DND_FILES, TkinterDnD
import os
import subprocess
import threading
from concurrent.futures import ThreadPoolExecutor
import pysrt
from datetime import timedelta


class Mp3SrtSplitterApp:
    def __init__(self, root):
        self.root = root
        self.root.title("mp3 & srt splitter")
        self.root.geometry("600x700")
        self.files = {"mp3": [], "srt": []}
        self.progress = 0
        self.lock = threading.Lock()
        self.create_widgets()

    def create_widgets(self):
        dnd_frame = ttk.LabelFrame(self.root, text="drop test1.mp3&test1.srt test2.mp3&test2.srt hint here")

        dnd_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.listbox = tk.Listbox(dnd_frame, selectmode=tk.MULTIPLE)
        self.listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.listbox.drop_target_register(DND_FILES)
        self.listbox.dnd_bind('<<Drop>>', self.drop)

        self.progress_bar = ttk.Progressbar(self.root, orient="horizontal", length=400, mode="determinate")
        self.progress_bar.pack(pady=10)

        self.console_output = tk.Text(self.root, height=8, state='disabled')
        self.console_output.pack(fill=tk.BOTH, padx=10, pady=5)

        button_frame = ttk.Frame(self.root)
        button_frame.pack(fill=tk.X, padx=10, pady=5)

        add_button = ttk.Button(button_frame, text="add files", command=self.add_files)
        add_button.pack(side=tk.LEFT, padx=5)

        split_button = ttk.Button(button_frame, text="split ", command=self.start_splitting)
        split_button.pack(side=tk.LEFT, padx=5)

        clear_button = ttk.Button(button_frame, text="clear", command=self.clear_list)
        clear_button.pack(side=tk.LEFT, padx=5)

    def add_files(self):
        file_paths = filedialog.askopenfilenames(
            title="choice .mp3  .srt  files",
            filetypes=[("MP3 and SRT files", "*.mp3 *.srt")])
        self.process_files(file_paths)

    def drop(self, event):
        files = self.root.splitlist(event.data)
        self.process_files(files)

    def process_files(self, file_paths):
        for file_path in file_paths:
            file_path = file_path.strip('{}')
            if os.path.isfile(file_path):
                if file_path.lower().endswith('.mp3'):
                    if file_path not in self.files["mp3"]:
                        self.files["mp3"].append(file_path)
                        self.listbox.insert(tk.END, file_path)
                elif file_path.lower().endswith('.srt'):
                    if file_path not in self.files["srt"]:
                        self.files["srt"].append(file_path)
                        self.listbox.insert(tk.END, file_path)

    def clear_list(self):
        self.files = {"mp3": [], "srt": []}
        self.listbox.delete(0, tk.END)
        self.progress_bar["value"] = 0
        self.update_console("")

    def start_splitting(self):
        if not self.files["mp3"] or not self.files["srt"]:
            messagebox.showwarning("No files found", "Please drag-and-drop or add .mp3 and .srt files to split.")
            return

        matched_pairs = self.match_files()
        if not matched_pairs:
            messagebox.showwarning("No matching files found", "No matching .mp3 and .srt files were found.")
            return

        self.progress_bar["maximum"] = len(matched_pairs)
        self.progress = 0

        threading.Thread(target=self.split_files, args=(matched_pairs,), daemon=True).start()

    def match_files(self):
        pairs = []
        for mp3_file in self.files["mp3"]:
            base_name = os.path.splitext(os.path.basename(mp3_file))[0]
            for srt_file in self.files["srt"]:
                if os.path.splitext(os.path.basename(srt_file))[0] == base_name:
                    pairs.append((mp3_file, srt_file))
                    break
        return pairs

    def split_files(self, pairs):
        with ThreadPoolExecutor(max_workers=4) as executor:
            futures = []
            for mp3_file, srt_file in pairs:
                futures.append(executor.submit(self.split_mp3_and_srt, mp3_file, srt_file))

            for future in futures:
                future.result()

    def split_mp3_and_srt(self, mp3_file, srt_file):
        try:
            subs = pysrt.open(srt_file)
            total_duration = self.get_mp3_duration(mp3_file)
            target_seconds = total_duration / 2

            nearest_time = self.find_nearest_split_time(subs, target_seconds)

            # Split the .srt file and the .mp3 file at the nearest time
            self.split_srt(srt_file, nearest_time)
            self.split_mp3(mp3_file, nearest_time)


            self.update_console_threadsafe(f"Done: {mp3_file} and {srt_file}")
        except Exception as e:
            self.update_console_threadsafe(f"Error: {mp3_file} and {srt_file}\nError Message: {e}")

    def get_mp3_duration(self, mp3_file):
        result = subprocess.run(
            ["ffprobe", "-i", mp3_file, "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        return float(result.stdout.strip())

    def find_nearest_split_time(self, subs, target_seconds):
        min_diff = float('inf')
        nearest_time = None

        for sub in subs:
            # Calculating the total end time in seconds without using timedelta
            end_seconds = (sub.end.hours * 3600) + (sub.end.minutes * 60) + sub.end.seconds + (sub.end.milliseconds / 1000.0)
            diff = abs(end_seconds - target_seconds)

            if diff < min_diff:
                min_diff = diff
                nearest_time = sub.end

        return nearest_time

 
    def split_srt(self, srt_file, split_time):
        subs = pysrt.open(srt_file)
        part1 = subs.slice(ends_before=split_time)
        part2 = subs.slice(starts_after=split_time)

        # Calculate the offset as seconds to avoid using timedelta
        offset_seconds = (split_time.hours * 3600) + (split_time.minutes * 60) + split_time.seconds + (split_time.milliseconds / 1000.0)
        part2.shift(seconds=-offset_seconds)  # Use seconds directly for shifting

        base_name = os.path.splitext(srt_file)[0]
        part1.save(f"{base_name}_part1.srt")
        part2.save(f"{base_name}_part2.srt")

    def split_mp3(self, mp3_file, split_time):
        hours = split_time.hours
        minutes = split_time.minutes
        seconds = split_time.seconds
        milliseconds = split_time.milliseconds

        # Convert milliseconds to fractional seconds
        frac_seconds = seconds + milliseconds / 1000.0

        split_time_str = f"{hours:02}:{minutes:02}:{frac_seconds:06.3f}"

        base_name = os.path.splitext(mp3_file)[0]
        part1_path = f"{base_name}_part1.mp3"
        part2_path = f"{base_name}_part2.mp3"

        subprocess.run(["ffmpeg", "-i", mp3_file, "-to", split_time_str, "-c", "copy", part1_path])
        subprocess.run(["ffmpeg", "-i", mp3_file, "-ss", split_time_str, "-c", "copy", part2_path])

    def update_console_threadsafe(self, message):
        self.root.after(0, self.update_console, message)

    def update_console(self, message):
        self.console_output.config(state='normal')
        self.console_output.insert(tk.END, message + "\n")
        self.console_output.see(tk.END)
        self.console_output.config(state='disabled')

def main():
    root = TkinterDnD.Tk()
    app = Mp3SrtSplitterApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()

2 Likes