make this script faster
import tkinter as tk
from tkinter import filedialog, messagebox
import subprocess
import os
import librosa
import numpy as np
import shutil
import soundfile as sf
import glob

def select_file():
    file_path = filedialog.askopenfilename(filetypes=[("Video files", "*.mp4 *.avi *.mov")])
    video_path.set(file_path)

def get_frame_rate(video_file):
    cmd = [
        'ffprobe',
        '-v', '0',
        '-select_streams', 'v:0',
        '-show_entries', 'stream=avg_frame_rate',
        '-of', 'default=noprint_wrappers=1:nokey=1',
        video_file
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    rate_str = result.stdout.strip()
    if '/' in rate_str:
        num, denom = rate_str.split('/')
        frame_rate = float(num) / float(denom) if float(denom) != 0 else 0
    else:
        frame_rate = float(rate_str)
    return frame_rate

def get_total_frames(video_file):
    cmd = [
        'ffprobe',
        '-v', '0',
        '-count_frames',
        '-select_streams', 'v:0',
        '-show_entries', 'stream=nb_read_frames',
        '-of', 'default=noprint_wrappers=1:nokey=1',
        video_file
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    frame_count_str = result.stdout.strip()
    if frame_count_str.isdigit():
        return int(frame_count_str)
    else:
        # If nb_read_frames is not available, fall back to duration * fps
        frame_rate = get_frame_rate(video_file)
        cmd_duration = [
            'ffprobe',
            '-v', '0',
            '-select_streams', 'v:0',
            '-show_entries', 'format=duration',
            '-of', 'default=noprint_wrappers=1:nokey=1',
            video_file
        ]
        result_duration = subprocess.run(cmd_duration, capture_output=True, text=True)
        duration_str = result_duration.stdout.strip()
        try:
            duration = float(duration_str)
            return int(duration * frame_rate)
        except ValueError:
            return 0

def start_processing():
    if video_path.get():
        try:
            process_video(video_path.get())
        except Exception as e:
            messagebox.showerror("Error", f"An unexpected error occurred:\n{e}")
    else:
        messagebox.showerror("Error", "Please select a video file.")

def process_video(input_video):
    status_label.config(text="Extracting audio...")
    root.update_idletasks()
    
    # Extract audio from the video
    audio_file = "temp_audio.wav"
    cmd = [
        'ffmpeg',
        '-i', input_video,
        '-ar', '44100',  # Sample rate
        '-ac', '1',      # Mono audio
        '-vn',           # No video
        '-y',            # Overwrite output file
        audio_file
    ]
    subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    status_label.config(text="Analyzing audio...")
    root.update_idletasks()

    # Get the frame rate and total number of frames
    frame_rate = get_frame_rate(input_video)
    total_frames = get_total_frames(input_video)
    
    if frame_rate == 0 or total_frames == 0:
        messagebox.showerror("Error", "Could not determine frame rate or total frames.")
        cleanup([audio_file])
        return
    
    # Load the audio file
    y, sr = librosa.load(audio_file, sr=None)
    frame_duration = 1.0 / frame_rate
    frame_length = int(sr * frame_duration)
    
    # Compute loudness (RMS) for each frame
    loudness_list = []
    for i in range(total_frames):
        start = i * frame_length
        end = start + frame_length
        frame_audio = y[start:end]
        if len(frame_audio) == 0:
            rms = 0
        else:
            rms = np.sqrt(np.mean(frame_audio ** 2))
        loudness_list.append((i, rms))
    
    # Sort the frames by loudness (from quietest to loudest)
    loudness_list.sort(key=lambda x: x[1])
    
    status_label.config(text="Extracting frames from video...")
    root.update_idletasks()

    # Extract frames from the video
    frame_folder = "frames"
    if os.path.exists(frame_folder):
        shutil.rmtree(frame_folder)
    os.makedirs(frame_folder, exist_ok=True)
    cmd = [
        'ffmpeg',
        '-i', input_video,
        '-vf', f'fps={frame_rate}',
        os.path.join(frame_folder, 'frame%06d.png')  # Use 6 digits for frame numbering
    ]
    subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    # Verify extracted frames
    extracted_frames = sorted(glob.glob(os.path.join(frame_folder, 'frame*.png')))
    if len(extracted_frames) < total_frames:
        messagebox.showerror("Error", f"Expected {total_frames} frames, but extracted {len(extracted_frames)} frames.")
        cleanup([audio_file], frame_folder)
        return
    
    status_label.config(text="Rearranging audio segments...")
    root.update_idletasks()

    # Pair frames with their loudness
    frames_with_loudness = []
    for idx, (frame_index, rms) in enumerate(loudness_list):
        frame_filename = os.path.join(frame_folder, f'frame{frame_index+1:06d}.png')  # ffmpeg starts at 1
        if not os.path.exists(frame_filename):
            messagebox.showerror("Error", f"Frame file {frame_filename} does not exist.")
            cleanup([audio_file], frame_folder)
            return
        frames_with_loudness.append((frame_filename, rms, frame_index))
    
    status_label.config(text="Rearranging audio segments...")
    root.update_idletasks()

    # Rearrange audio segments as per the sorted frames
    sorted_audio_segments = []
    for (frame_file, rms, original_index) in frames_with_loudness:
        start = original_index * frame_length
        end = start + frame_length
        frame_audio = y[start:end]
        sorted_audio_segments.append(frame_audio)
    
    # Concatenate sorted audio segments
    if sorted_audio_segments:
        sorted_audio = np.concatenate(sorted_audio_segments)
    else:
        sorted_audio = y  # Fallback in case of empty segments
    
    # Save the sorted audio to a file
    sorted_audio_file = "sorted_audio.wav"
    sf.write(sorted_audio_file, sorted_audio, sr)
    
    status_label.config(text="Rearranging frames...")
    root.update_idletasks()

    # Create a temporary directory for sorted frames
    sorted_frame_folder = 'sorted_frames'
    if os.path.exists(sorted_frame_folder):
        shutil.rmtree(sorted_frame_folder)
    os.makedirs(sorted_frame_folder, exist_ok=True)
    for idx, (frame_file, rms, original_index) in enumerate(frames_with_loudness):
        new_filename = os.path.join(sorted_frame_folder, f'frame{idx+1:06d}.png')
        shutil.copy(frame_file, new_filename)
    
    status_label.config(text="Creating video from frames...")
    root.update_idletasks()

    # Create a video from sorted frames
    sorted_video = 'sorted_video.mp4'
    cmd = [
        'ffmpeg',
        '-framerate', f"{frame_rate}",
        '-i', os.path.join(sorted_frame_folder, 'frame%06d.png'),
        '-c:v', 'libx264',
        '-pix_fmt', 'yuv420p',
        '-y',
        sorted_video
    ]
    subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    status_label.config(text="Merging audio and video...")
    root.update_idletasks()

    # Merge the sorted audio with the video
    final_output = 'output_with_audio.mp4'
    cmd = [
        'ffmpeg',
        '-i', sorted_video,
        '-i', sorted_audio_file,
        '-c:v', 'copy',
        '-c:a', 'aac',
        '-strict', 'experimental',
        '-y',
        final_output
    ]
    subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    status_label.config(text="Cleaning up temporary files...")
    root.update_idletasks()

    # Clean up temporary files and directories
    cleanup(
        [audio_file, sorted_audio_file, sorted_video],
        [frame_folder, sorted_frame_folder]
    )

    status_label.config(text=f"Processing completed.\nOutput saved as '{final_output}'")
    root.update_idletasks()

def cleanup(files_to_remove=None, dirs_to_remove=None):
    if files_to_remove:
        for file in files_to_remove:
            if os.path.exists(file):
                os.remove(file)
    if dirs_to_remove:
        for dir in dirs_to_remove:
            if os.path.exists(dir):
                shutil.rmtree(dir)

# Set up the main application window
root = tk.Tk()
root.title("Video Frame Sorter")

# Create the UI components
frame = tk.Frame(root)
frame.pack(padx=10, pady=10)

video_path = tk.StringVar()
select_button = tk.Button(frame, text="Select Video File", command=select_file)
select_button.pack(pady=5)

video_label = tk.Label(frame, textvariable=video_path, wraplength=400, justify="left")
video_label.pack(pady=5)

start_button = tk.Button(frame, text="Start Processing", command=start_processing)
start_button.pack(pady=5)

status_label = tk.Label(frame, text="", wraplength=400, justify="left")
status_label.pack(pady=5)

# Run the application
root.mainloop()