import tkinter as tk from tkinter import filedialog, messagebox, Listbox import os import subprocess import threading import time class VideoConcatenatorApp: def __init__(self, root): self.root = root self.root.title("Video Concatenator") # Selected files self.selected_files = [] self.output_file_name = "output.mp4" self.delete_original = tk.BooleanVar(value=False) # Section 1: Select files self.label1 = tk.Label(root, text="1. Select Video Files", font=("Arial", 12)) self.label1.pack(pady=5) self.select_btn = tk.Button(root, text="Select Videos", command=self.select_videos) self.select_btn.pack(pady=5) self.files_listbox = Listbox(root, selectmode=tk.SINGLE, width=80, height=10) self.files_listbox.pack(pady=5) # Section 2: Organize sequence self.label2 = tk.Label(root, text="2. Organize Sequence", font=("Arial", 12)) self.label2.pack(pady=5) self.up_btn = tk.Button(root, text="Move Up", command=self.move_up) self.up_btn.pack(pady=5) self.down_btn = tk.Button(root, text="Move Down", command=self.move_down) self.down_btn.pack(pady=5) # Section 3: Name the output file self.label3 = tk.Label(root, text="3. Name the Output File", font=("Arial", 12)) self.label3.pack(pady=5) self.output_name_entry = tk.Entry(root, width=50) self.output_name_entry.insert(0, "output.mp4") self.output_name_entry.pack(pady=5) # Section 4: Choose output directory self.label4 = tk.Label(root, text="4. Select Output Directory", font=("Arial", 12)) self.label4.pack(pady=5) self.output_dir_btn = tk.Button(root, text="Choose Output Directory", command=self.select_output_dir) self.output_dir_btn.pack(pady=5) self.output_dir_label = tk.Label(root, text="", fg="blue") self.output_dir_label.pack(pady=5) # Section 5: Delete original files option self.delete_original_check = tk.Checkbutton(root, text="Delete Original Files After Concatenation", variable=self.delete_original) self.delete_original_check.pack(pady=5) # Concatenate Button self.concat_btn = tk.Button(root, text="Concatenate Videos", command=self.start_concatenation, state=tk.DISABLED) self.concat_btn.pack(pady=10) # Time Left Label self.time_left_label = tk.Label(root, text="", font=("Arial", 10), fg="green") self.time_left_label.pack(pady=5) def select_videos(self): files = filedialog.askopenfilenames(filetypes=[("Video Files", "*.mp4;*.mov;*.mkv;*.avi;*.flv")]) if files: self.selected_files = list(files) self.update_files_listbox() self.concat_btn.config(state=tk.NORMAL) def update_files_listbox(self): self.files_listbox.delete(0, tk.END) for file in self.selected_files: self.files_listbox.insert(tk.END, file) def move_up(self): selected = self.files_listbox.curselection() if selected and selected[0] > 0: index = selected[0] self.selected_files[index], self.selected_files[index - 1] = self.selected_files[index - 1], self.selected_files[index] self.update_files_listbox() self.files_listbox.select_set(index - 1) def move_down(self): selected = self.files_listbox.curselection() if selected and selected[0] < len(self.selected_files) - 1: index = selected[0] self.selected_files[index], self.selected_files[index + 1] = self.selected_files[index + 1], self.selected_files[index] self.update_files_listbox() self.files_listbox.select_set(index + 1) def select_output_dir(self): directory = filedialog.askdirectory() if directory: self.output_dir = directory self.output_dir_label.config(text=directory) def start_concatenation(self): # Disable button to prevent re-clicking self.concat_btn.config(state=tk.DISABLED) self.time_left_label.config(text="Starting concatenation...") # Run the concatenation process in a separate thread threading.Thread(target=self.concatenate_videos).start() def concatenate_videos(self): if not self.selected_files: messagebox.showerror("Error", "No videos selected.") return if not hasattr(self, "output_dir"): messagebox.showerror("Error", "Output directory not selected.") return # Get output file name self.output_file_name = self.output_name_entry.get().strip() if not self.output_file_name: messagebox.showerror("Error", "Output file name cannot be empty.") return # Ensure the output file name ends with .mp4 if not self.output_file_name.endswith(".mp4"): self.output_file_name += ".mp4" # Create file list for FFmpeg temp_file_list = os.path.join(self.output_dir, "file_list.txt") with open(temp_file_list, "w") as f: for file in self.selected_files: f.write(f"file '{file}'\n") # Output file path output_file = os.path.join(self.output_dir, self.output_file_name) # FFmpeg command command = [ "ffmpeg", "-f", "concat", "-safe", "0", "-i", temp_file_list, "-c", "copy", output_file ] try: total_time = len(self.selected_files) * 5 # Estimate: ~5 seconds per video for i in range(total_time): time.sleep(1) self.time_left_label.config(text=f"Estimated time left: {total_time - i}s") self.root.update_idletasks() subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) self.time_left_label.config(text="Concatenation completed!") messagebox.showinfo("Success", f"Videos concatenated successfully! Output: {output_file}") # Optionally delete original files if self.delete_original.get(): for file in self.selected_files: os.remove(file) messagebox.showinfo("Info", "Original files have been deleted.") except subprocess.CalledProcessError: self.time_left_label.config(text="Error during concatenation.") messagebox.showerror("Error", "Failed to concatenate videos.") finally: os.remove(temp_file_list) self.concat_btn.config(state=tk.NORMAL) if __name__ == "__main__": root = tk.Tk() app = VideoConcatenatorApp(root) root.mainloop()