|
| 1 | +# PRODUCT OF TRAKEXCEL AGENCY 2024. COPYRIGHT TERMS APPLIES |
| 2 | +import cv2 |
| 3 | +import os |
| 4 | +import tkinter as tk |
| 5 | +from tkinter import ttk, filedialog, messagebox |
| 6 | +import subprocess |
| 7 | +import sys |
| 8 | + |
| 9 | + |
| 10 | +class VideoToFramesConverter: |
| 11 | + def __init__(self, root): |
| 12 | + self.root = root |
| 13 | + self.root.title("Video to Frames Pictures Converter") |
| 14 | + |
| 15 | + # Set custom icon |
| 16 | + icon_path = 'vid.ico' # Replace with the path to your .ico file |
| 17 | + if getattr(sys, 'frozen', False): # Check if running as a PyInstaller executable |
| 18 | + icon_path = os.path.join(sys._MEIPASS, "vid.ico") |
| 19 | + |
| 20 | + if os.path.exists(icon_path): |
| 21 | + self.root.iconbitmap(icon_path) |
| 22 | + |
| 23 | + # Icon definitions |
| 24 | + self.browse_icon = tk.PhotoImage(file="folder.png" if not getattr( |
| 25 | + sys, 'frozen', False) else os.path.join(sys._MEIPASS, "folder.png")) |
| 26 | + self.cancel_icon = tk.PhotoImage(file="cancel.png" if not getattr( |
| 27 | + sys, 'frozen', False) else os.path.join(sys._MEIPASS, "cancel.png")) |
| 28 | + self.convert_icon = tk.PhotoImage(file="convert.png" if not getattr( |
| 29 | + sys, 'frozen', False) else os.path.join(sys._MEIPASS, "convert.png")) |
| 30 | + self.open_folder_icon = tk.PhotoImage(file="open-folder.png" if not getattr( |
| 31 | + sys, 'frozen', False) else os.path.join(sys._MEIPASS, "open-folder.png")) |
| 32 | + |
| 33 | + self.create_widgets() |
| 34 | + self.cancel_conversion = False |
| 35 | + |
| 36 | + def create_widgets(self): |
| 37 | + self.notebook = ttk.Notebook(self.root) |
| 38 | + self.notebook.grid(row=0, column=0, sticky="nsew", padx=10, pady=10) |
| 39 | + |
| 40 | + # Page 1: Conversion functionality |
| 41 | + conversion_page = ttk.Frame(self.notebook) |
| 42 | + self.notebook.add(conversion_page, text="Convert Video") |
| 43 | + |
| 44 | + self.create_conversion_widgets(conversion_page) |
| 45 | + |
| 46 | + # Page 2: About the program |
| 47 | + about_page = ttk.Frame(self.notebook) |
| 48 | + self.notebook.add(about_page, text="About") |
| 49 | + |
| 50 | + about_label = ttk.Label( |
| 51 | + about_page, text="Video to Frames Converter\nVersion 1.0\n© 2024 Trakexcel Agency-@Uzitrake") |
| 52 | + about_label.pack(padx=20, pady=14) |
| 53 | + |
| 54 | + explanation_text = ( |
| 55 | + "This program allows you to convert a video file into a sequence of frames.\n\n" |
| 56 | + "Usage:\n" |
| 57 | + "1. Select a video file using the 'Browse' button.\n" |
| 58 | + "2. Choose an output directory where the frames will be saved.\n" |
| 59 | + "3. Select the output format (WebP, JPEG, PNG) from the dropdown menu.\n" |
| 60 | + "4. Set the quality of the images (0-100).\n" |
| 61 | + "5. Click 'Convert' to start the conversion process.\n" |
| 62 | + "6. Optionally, you can cancel the conversion using the 'Cancel' button.\n" |
| 63 | + "7. Once the conversion is done, use 'Open in Explorer'." |
| 64 | + ) |
| 65 | + |
| 66 | + explanation_label = ttk.Label( |
| 67 | + about_page, text=explanation_text, anchor="w", justify="left") |
| 68 | + explanation_label.pack(padx=20, pady=20, fill="both") |
| 69 | + |
| 70 | + # Allow window expansion |
| 71 | + self.root.grid_rowconfigure(0, weight=1) |
| 72 | + self.root.grid_columnconfigure(0, weight=1) |
| 73 | + |
| 74 | + def create_conversion_widgets(self, parent): |
| 75 | + # Video file selection |
| 76 | + self.file_frame = ttk.Frame(parent, padding="10 5 10 5") |
| 77 | + self.file_frame.grid(row=0, column=0, sticky="ew") |
| 78 | + |
| 79 | + self.file_path_label = ttk.Label( |
| 80 | + self.file_frame, text="Video To Convert:") |
| 81 | + self.file_path_label.grid(row=0, column=0, sticky="w") |
| 82 | + |
| 83 | + self.file_path_entry = ttk.Entry( |
| 84 | + self.file_frame, state="disabled", width=40, font=("Helvetica", 10)) |
| 85 | + self.file_path_entry.grid(row=0, column=1, padx=(0, 10), sticky="ew") |
| 86 | + |
| 87 | + self.browse_button = ttk.Button( |
| 88 | + self.file_frame, text="Browse", command=self.browse_file) |
| 89 | + self.browse_button.grid(row=0, column=2) |
| 90 | + |
| 91 | + # Output directory selection |
| 92 | + self.output_frame = ttk.Frame(parent, padding="10 5 10 5") |
| 93 | + self.output_frame.grid(row=1, column=0, sticky="ew") |
| 94 | + |
| 95 | + self.output_path_label = ttk.Label( |
| 96 | + self.output_frame, text="Output Location:", font=("Helvetica", 10)) |
| 97 | + self.output_path_label.grid(row=0, column=0, sticky="w") |
| 98 | + |
| 99 | + self.output_path_entry = ttk.Entry( |
| 100 | + self.output_frame, state="disabled", width=40, font=("Helvetica", 10)) |
| 101 | + self.output_path_entry.grid(row=0, column=1, padx=(0, 10), sticky="ew") |
| 102 | + |
| 103 | + self.output_browse_button = ttk.Button( |
| 104 | + self.output_frame, text="Browse", command=self.browse_output_directory) |
| 105 | + self.output_browse_button.grid(row=0, column=2) |
| 106 | + |
| 107 | + # Output format selection |
| 108 | + self.format_frame = ttk.Frame(parent, padding="10 5 10 5") |
| 109 | + self.format_frame.grid(row=2, column=0, sticky="ew") |
| 110 | + |
| 111 | + self.format_label = ttk.Label( |
| 112 | + self.format_frame, text="Output Format:", font=("Helvetica", 10)) |
| 113 | + self.format_label.grid(row=0, column=0, sticky="w") |
| 114 | + |
| 115 | + self.format_var = tk.StringVar() |
| 116 | + self.format_var.set("WebP") # Default format |
| 117 | + self.format_dropdown = ttk.Combobox(self.format_frame, textvariable=self.format_var, |
| 118 | + values=["WebP", "JPEG", "PNG"]) |
| 119 | + self.format_dropdown.grid(row=0, column=1, padx=(0, 10), sticky="ew") |
| 120 | + |
| 121 | + # Quality selection |
| 122 | + self.quality_frame = ttk.Frame(parent, padding="10 5 10 5") |
| 123 | + self.quality_frame.grid(row=3, column=0, sticky="ew") |
| 124 | + |
| 125 | + self.quality_label = ttk.Label( |
| 126 | + self.quality_frame, text="Quality (0-100):", font=("Helvetica", 10)) |
| 127 | + self.quality_label.grid(row=0, column=0, sticky="w") |
| 128 | + |
| 129 | + self.quality_var = tk.StringVar() |
| 130 | + self.quality_var.set("100") |
| 131 | + self.quality_spinbox = ttk.Spinbox( |
| 132 | + self.quality_frame, from_=0, to=100, textvariable=self.quality_var, width=7) |
| 133 | + self.quality_spinbox.grid(row=0, column=1, padx=(0, 10), sticky="ew") |
| 134 | + |
| 135 | + # Button frame |
| 136 | + self.button_frame = ttk.Frame(parent, padding="10 5 10 5") |
| 137 | + self.button_frame.grid(row=4, column=0, sticky="ew") |
| 138 | + |
| 139 | + # Convert button |
| 140 | + self.convert_button = ttk.Button( |
| 141 | + self.button_frame, text="Convert", command=self.start_conversion, image=self.convert_icon, compound="left") |
| 142 | + self.convert_button.grid(row=0, column=0, padx=5) |
| 143 | + |
| 144 | + # Cancel button |
| 145 | + self.cancel_button = ttk.Button( |
| 146 | + self.button_frame, text="Cancel", command=self.cancel_conversion_process, image=self.cancel_icon, compound="left") |
| 147 | + self.cancel_button.grid(row=0, column=1, padx=5) |
| 148 | + |
| 149 | + # Open in Explorer button |
| 150 | + self.open_folder_button = ttk.Button(self.button_frame, text="Open in Explorer", |
| 151 | + command=self.open_output_folder, image=self.open_folder_icon, compound="left") |
| 152 | + self.open_folder_button.grid(row=0, column=2, padx=5) |
| 153 | + |
| 154 | + # Info label |
| 155 | + info_label = ttk.Label( |
| 156 | + parent, text="ℹ️ Check chosen directory for images", font="Helvetica 8 italic") |
| 157 | + info_label.grid(row=5, column=0, pady=5) |
| 158 | + |
| 159 | + # Conversion status labels |
| 160 | + self.status_label = ttk.Label(parent, text="") |
| 161 | + self.status_label.grid(row=6, column=0, pady=5) |
| 162 | + |
| 163 | + # Progress bar style |
| 164 | + style = ttk.Style(self.root) |
| 165 | + style.theme_use("clam") |
| 166 | + style.configure("green.Horizontal.TProgressbar", |
| 167 | + troughcolor='black', barcolor='green') |
| 168 | + |
| 169 | + # Progress bar |
| 170 | + self.progress_var = tk.DoubleVar() |
| 171 | + self.progress_bar = ttk.Progressbar( |
| 172 | + parent, variable=self.progress_var, mode="determinate", style="green.Horizontal.TProgressbar") |
| 173 | + self.progress_bar.grid(row=7, column=0, sticky="ew", pady=5) |
| 174 | + |
| 175 | + def browse_file(self): |
| 176 | + file_path = filedialog.askopenfilename(title="Select a video file", filetypes=[ |
| 177 | + ("Video Files", "*.mp4;*.avi")]) |
| 178 | + self.file_path_entry.config(state="normal") |
| 179 | + self.file_path_entry.delete(0, tk.END) |
| 180 | + self.file_path_entry.insert(tk.END, file_path) |
| 181 | + self.file_path_entry.config(state="disabled") |
| 182 | + |
| 183 | + def browse_output_directory(self): |
| 184 | + output_directory = filedialog.askdirectory( |
| 185 | + title="Select an output directory") |
| 186 | + self.output_path_entry.config(state="normal") |
| 187 | + self.output_path_entry.delete(0, tk.END) |
| 188 | + self.output_path_entry.insert(tk.END, output_directory) |
| 189 | + self.output_path_entry.config(state="disabled") |
| 190 | + |
| 191 | + # Call open_output_folder to update the output_directory variable |
| 192 | + # self.open_output_folder() |
| 193 | + # CODE BY UZITRAKE |
| 194 | + |
| 195 | + def start_conversion(self): |
| 196 | + self.cancel_conversion = False |
| 197 | + self.status_label.config(text="Converting...") |
| 198 | + self.progress_var.set(0) # Reset progress bar |
| 199 | + # Delayed call to start conversion |
| 200 | + self.root.after(100, self.convert_video_to_frames) |
| 201 | + |
| 202 | + def cancel_conversion_process(self): |
| 203 | + self.cancel_conversion = True |
| 204 | + self.status_label.config(text="Conversion canceled.") |
| 205 | + self.progress_bar.stop() |
| 206 | + |
| 207 | + def convert_video_to_frames(self): |
| 208 | + video_path = self.file_path_entry.get() |
| 209 | + output_directory = self.output_path_entry.get() |
| 210 | + quality = int(self.quality_var.get()) |
| 211 | + output_format = self.format_var.get() |
| 212 | + |
| 213 | + if not video_path: |
| 214 | + messagebox.showerror("Error", "Please select a video file.") |
| 215 | + self.reset_conversion_status() |
| 216 | + return |
| 217 | + |
| 218 | + if not output_directory: |
| 219 | + messagebox.showerror("Error", "Please select an output directory.") |
| 220 | + self.reset_conversion_status() |
| 221 | + return |
| 222 | + |
| 223 | + if not os.path.exists(output_directory): |
| 224 | + os.makedirs(output_directory) |
| 225 | + |
| 226 | + video_capture = cv2.VideoCapture(video_path) |
| 227 | + total_frames = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT)) |
| 228 | + |
| 229 | + frame_count = 0 |
| 230 | + |
| 231 | + def process_frame(): |
| 232 | + nonlocal frame_count |
| 233 | + |
| 234 | + if not self.cancel_conversion: |
| 235 | + ret, frame = video_capture.read() |
| 236 | + if ret: |
| 237 | + frame_filename = os.path.join(output_directory, f'frame_{ |
| 238 | + frame_count:04d}.{output_format.lower()}') |
| 239 | + |
| 240 | + if output_format == "AVIF": |
| 241 | + imageio.imwrite(frame_filename, frame, |
| 242 | + format='AVIF', codec='av1') |
| 243 | + elif output_format == "PNG": |
| 244 | + cv2.imwrite(frame_filename, frame, [ |
| 245 | + cv2.IMWRITE_PNG_COMPRESSION, quality]) |
| 246 | + else: |
| 247 | + cv2.imwrite(frame_filename, frame, [ |
| 248 | + cv2.IMWRITE_WEBP_QUALITY, quality] if output_format == "WebP" else []) |
| 249 | + |
| 250 | + frame_count += 1 |
| 251 | + |
| 252 | + # Update progress bar |
| 253 | + progress_value = (frame_count + 1) / total_frames * 100 |
| 254 | + self.progress_var.set(progress_value) |
| 255 | + self.root.update_idletasks() # Force update of the GUI |
| 256 | + |
| 257 | + # Schedule the next frame processing |
| 258 | + self.root.after(10, process_frame) |
| 259 | + else: |
| 260 | + # Release resources and finalize |
| 261 | + video_capture.release() |
| 262 | + cv2.destroyAllWindows() |
| 263 | + |
| 264 | + if self.cancel_conversion: |
| 265 | + self.status_label.config(text="Conversion canceled.") |
| 266 | + else: |
| 267 | + self.status_label.config(text="Conversion completed.") |
| 268 | + self.progress_bar.stop() |
| 269 | + self.reset_conversion_status() |
| 270 | + |
| 271 | + process_frame() |
| 272 | + |
| 273 | + def open_output_folder(self): |
| 274 | + output_directory = self.output_path_entry.get() |
| 275 | + |
| 276 | + if output_directory and os.path.exists(output_directory): |
| 277 | + os.startfile(output_directory) |
| 278 | + else: |
| 279 | + messagebox.showwarning( |
| 280 | + "Warning", "Output directory does not exist.") |
| 281 | + |
| 282 | + def reset_conversion_status(self): |
| 283 | + self.cancel_conversion = False |
| 284 | + self.status_label.config(text="") |
| 285 | + self.progress_bar.stop() |
| 286 | + |
| 287 | + |
| 288 | +if __name__ == "__main__": |
| 289 | + root = tk.Tk() |
| 290 | + style = ttk.Style(root) |
| 291 | + style.theme_use("clam") |
| 292 | + converter = VideoToFramesConverter(root) |
| 293 | + root.mainloop() |
0 commit comments