24 Commits
v0.2.0 ... main

Author SHA1 Message Date
Ramforth
dffc6a2bbc Docs: Add user thoughts on bundling challenges to DEVELOPMENT_PLAN.md 2025-11-03 01:57:33 +01:00
Ramforth
5ad7eb2ea1 Docs: Add assessment report on bundling FFmpeg to DEVELOPMENT_PLAN.md 2025-11-03 01:50:56 +01:00
Ramforth
5f1363f335 Docs: Add images/ folder with GUI screenshot 2025-11-03 01:48:27 +01:00
Ramforth
0b1816a310 Docs: Center GUI screenshot in README.md 2025-11-03 01:45:30 +01:00
Ramforth
d62742ef00 Docs: Add GUI screenshot to README.md 2025-11-03 01:44:29 +01:00
Ramforth
841bbac9d4 Fix: Explicitly set empty text for CTkSwitch to suppress default label 2025-11-03 01:34:32 +01:00
Ramforth
b4815a4a19 Fix: Correct mode switch text and remove main frame padding 2025-11-03 01:28:29 +01:00
Ramforth
7283966b2e Feat: Implement main content frame and dynamic mode switch text 2025-11-03 01:18:38 +01:00
Ramforth
5ee4b3e8f3 Revert: Revert gui.py and custom theme JSON files to previous state for debugging segmentation fault 2025-11-03 01:00:27 +01:00
Ramforth
603ba2f679 Fix: Add missing border_width to CTkFrame in custom themes 2025-11-03 00:55:06 +01:00
Ramforth
46bb80338a Fix: Add missing border_width to CTkFrame in custom themes 2025-11-03 00:52:17 +01:00
Ramforth
2f773657a1 Fix: Add missing border_width to CTkFrame in custom themes 2025-11-03 00:49:59 +01:00
Ramforth
dd37b38498 Feat: Implement user's proposed gui.py changes, add footer, and refine theme JSON files 2025-11-03 00:48:03 +01:00
Ramforth
3b4890c8e8 Feat: Implement user's proposed gui.py changes, add footer, and refine theme JSON files 2025-11-03 00:45:18 +01:00
Ramforth
55a3838be7 Feat: Implement user's proposed gui.py changes, add footer, and refine theme JSON files 2025-11-03 00:37:54 +01:00
Ramforth
a791f4d8ca Fix: Implement user's proposed gui.py changes and correct theme JSON files 2025-11-03 00:29:05 +01:00
Ramforth
4f61d86e2a Feat: Implement user's proposed gui.py changes for improved UI/UX and functionality 2025-11-03 00:24:53 +01:00
Ramforth
710f77f403 Fix: Include custom theme JSON files in PyInstaller bundle 2025-11-03 00:16:14 +01:00
Ramforth
b4ffdfa977 Fix: Resolve CTkSwitch AttributeError and implement custom themes 2025-11-02 22:40:24 +01:00
Ramforth
1250afc9e5 Fix: Resolve AttributeError in CTkSwitch and ensure initial dark mode 2025-11-02 22:27:28 +01:00
Ramforth
e0fb81197c Feat: Implement proposed gui.py changes for improved UI/UX 2025-11-02 22:24:37 +01:00
Ramforth
0a49866662 Fix: Re-add tkinter import to gui.py to resolve NameError 2025-11-02 22:06:24 +01:00
Ramforth
e3238d7091 Feat: Migrate GUI to customtkinter and update README.md 2025-11-02 22:01:43 +01:00
Ramforth
3235ed5e6f Docs: Add in-document link to HOW-TO.md 2025-11-02 21:34:23 +01:00
10 changed files with 353 additions and 68 deletions

View File

@@ -23,6 +23,10 @@ Develop a user-friendly, standalone Python tool to convert video files into form
### Future Considerations
* **Twitch Integration Strategy:**
* **URL Support:** Extend the URL handling to explicitly support Twitch video, single clip, and clip collection URLs.
* **Single Video/Clip Downloads:** Leverage `yt-dlp`'s existing capabilities to download single Twitch videos and clips. This should work with the current implementation.
* **Clip Collection Downloads (Batch Processing):** For Twitch clip collection URLs, `yt-dlp` will download all clips in the collection. This will require implementing batch processing capabilities (which is already a future consideration) to handle multiple downloaded files. The application will need to iterate through each downloaded clip and convert it individually.
* **Batch Processing:** Allow conversion of multiple files or URLs in a single run.
* **Configuration File:** Implement a configuration file (e.g., YAML, JSON) for persistent settings.
* **Progress Bar:** Integrate a progress bar for both download and conversion processes.
@@ -34,3 +38,30 @@ Develop a user-friendly, standalone Python tool to convert video files into form
* DaVinci Resolve Free on Linux has limitations with certain audio codecs (e.g., AAC) and 4K output/encoding (requires Studio version).
* `ffmpeg-full` AUR package is required for `dnxhd` support on Arch-based systems.
### Large scale implementations
Since one of the limitations of this project is the requirement of ffmpeg-full for encoding videos for Davinci Resolve use we should investigate the proposition of implementing the necessary ffmpeg-full into the project release file.
I do not know, currently if this is viable or even possible. But I think that a fully precompiled program with built in encoders and tools would be more attractive to most users.
We should also see if there are other tools that would be considered natural for this project.
**Assessment Report: Bundling FFmpeg and Other Tools**
**1. Bundling `ffmpeg-full`:**
* **Viability:** Technically viable and possible. PyInstaller (and similar tools) can include external binaries like `ffmpeg` in the final executable. The Python script would then call this bundled binary.
* **User Attractiveness:** Highly attractive. A self-contained executable eliminates the need for users to manually install `ffmpeg`, especially specific versions like `ffmpeg-full` with custom codecs, significantly improving user experience and simplifying setup.
* **Challenges:**
* **Cross-Platform Complexity:** Requires separate `ffmpeg` binaries for each target OS (Linux, Windows, macOS), increasing build complexity.
* **Licensing:** Must comply with `ffmpeg`'s LGPLv2.1/GPLv2 licensing terms (e.g., providing source code).
* **Maintenance:** Keeping bundled `ffmpeg` updated for security and features requires re-bundling.
* **Executable Size:** Bundling `ffmpeg` will increase the overall size of the application executable.
**2. Other Natural Tools for Consideration:**
* **`ffprobe`:** (Already implicitly used) Part of the `ffmpeg` suite, essential for media analysis (e.g., video duration). Would be bundled alongside `ffmpeg`.
* **`mediainfo`:** A powerful command-line tool for detailed media file information. Could be integrated for advanced diagnostics or user-facing metadata display.
* **`exiftool`:** For comprehensive metadata handling. Relevant if "Metadata Preservation" becomes a core feature.
### Questions for Challenges with bundling
* Cross platform development is currently delayed until after final stages of Linux project.
Reason is, among other things, that Windows do not see the same restrictions as Linux users meet in this actual field. So converting video files in this manner is mostly a Linux theme.
* Licensing: Will the entire source code have to be provided along side the downloadable package - or can it remain available as a Gitea repository for reference?
* Size of executable: We will have to test numerous iterations to lower the size of the finished product. However, I am also confident that Linux users that face the challenge of editing videos and recoding videos at a semi-regular rate will have enough storage space to handle a sizeable application if it solves the actual problem.

View File

@@ -16,6 +16,7 @@ Before running the application, ensure you have the following installed on your
1. **Download the application:**
You can either clone the repository and build the application yourself, or download the latest release from the project's Gitea page.
For instructions on running the application, see [Running the Application](#3-running-the-application).
2. **Building from source (optional):**
If you have cloned the repository, you can build the executable by running the following commands:

View File

@@ -19,6 +19,14 @@ For a detailed breakdown of the project's goals, technical specifications, and d
For instructions on how to use the application, please refer to the [HOW-TO.md](HOW-TO.md) file.
## GUI
The application's graphical user interface has been updated to use `customtkinter` as of November 2, 2025. This provides a modern and customizable look and feel.
<p align="center">
<img src="images/video_converter.png" alt="Video Converter GUI" style="display: block; margin: 0 auto;">
</p>
## Setup Notes for Arch Linux / CachyOS
When installing `ffmpeg-full` (required for DNxHR/HD codec support), users might encounter a dependency conflict with `obs-studio-browser`. `obs-studio-browser` typically requires `ffmpeg-obs`, which is incompatible with `ffmpeg-full`.

56
custom_theme_dark.json Normal file
View File

@@ -0,0 +1,56 @@
{
"CTk": {
"fg_color": ["#121212", "#121212"]
},
"CTkFrame": {
"fg_color": ["#121212", "#121212"],
"border_color": ["#333333", "#333333"],
"corner_radius": 8,
"border_width": 0
},
"CTkLabel": {
"text_color": ["#ecd7b2", "#ecd7b2"],
"fg_color": "transparent",
"corner_radius": 0
},
"CTkButton": {
"fg_color": ["#21498a", "#21498a"],
"hover_color": ["#2a5c9e", "#2a5c9e"],
"corner_radius": 8,
"border_color": ["#333333", "#333333"]
},
"CTkEntry": {
"fg_color": ["#1e1e1e", "#1e1e1e"],
"text_color": ["#ecd7b2", "#ecd7b2"],
"placeholder_text_color": ["#888888", "#888888"],
"corner_radius": 8,
"border_width": 2,
"border_color": ["#333333", "#333333"]
},
"CTkOptionMenu": {
"fg_color": ["#21498a", "#21498a"],
"button_color": ["#21498a", "#21498a"],
"button_hover_color": ["#2a5c9e", "#2a5c9e"],
"text_color": ["#ecd7b2", "#ecd7b2"],
"corner_radius": 8,
"border_color": ["#333333", "#333333"]
},
"CTkProgressBar": {
"fg_color": ["#1e1e1e", "#1e1e1e"],
"progress_color": ["#21498a", "#21498a"],
"corner_radius": 8,
"border_color": ["#333333", "#333333"]
},
"CTkSwitch": {
"fg_color": ["#1e1e1e", "#1e1e1e"],
"progress_color": ["#21498a", "#21498a"],
"text_color": ["#ecd7b2", "#ecd7b2"],
"corner_radius": 8,
"border_color": ["#333333", "#333333"]
},
"CTkFont": {
"family": ["Roboto", "Roboto"],
"size": [13, 13],
"weight": ["normal", "normal"]
}
}

56
custom_theme_light.json Normal file
View File

@@ -0,0 +1,56 @@
{
"CTk": {
"fg_color": ["#ecd7b2", "#ecd7b2"]
},
"CTkFrame": {
"fg_color": ["#ecd7b2", "#ecd7b2"],
"border_color": ["#cccccc", "#cccccc"],
"corner_radius": 8,
"border_width": 0
},
"CTkLabel": {
"text_color": ["#000000", "#000000"],
"fg_color": "transparent",
"corner_radius": 0
},
"CTkButton": {
"fg_color": ["#4077d1", "#4077d1"],
"hover_color": ["#5a8ee6", "#5a8ee6"],
"corner_radius": 8,
"border_color": ["#cccccc", "#cccccc"]
},
"CTkEntry": {
"fg_color": ["#ffffff", "#ffffff"],
"text_color": ["#000000", "#000000"],
"placeholder_text_color": ["#888888", "#888888"],
"corner_radius": 8,
"border_width": 2,
"border_color": ["#cccccc", "#cccccc"]
},
"CTkOptionMenu": {
"fg_color": ["#4077d1", "#4077d1"],
"button_color": ["#4077d1", "#4077d1"],
"button_hover_color": ["#5a8ee6", "#5a8ee6"],
"text_color": ["#000000", "#000000"],
"corner_radius": 8,
"border_color": ["#cccccc", "#cccccc"]
},
"CTkProgressBar": {
"fg_color": ["#ffffff", "#ffffff"],
"progress_color": ["#4077d1", "#4077d1"],
"corner_radius": 8,
"border_color": ["#cccccc", "#cccccc"]
},
"CTkSwitch": {
"fg_color": ["#ffffff", "#ffffff"],
"progress_color": ["#4077d1", "#4077d1"],
"text_color": ["#000000", "#000000"],
"corner_radius": 8,
"border_color": ["#cccccc", "#cccccc"]
},
"CTkFont": {
"family": ["Roboto", "Roboto"],
"size": [13, 13],
"weight": ["normal", "normal"]
}
}

202
gui.py
View File

@@ -1,11 +1,12 @@
import customtkinter
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from tkinter import filedialog, messagebox
import os
import sys
import threading
import time
import converter
import webbrowser
import downloader
import utils
import config
@@ -14,68 +15,129 @@ class VideoConverterGUI:
def __init__(self, master):
self.master = master
master.title("Video Converter")
master.geometry("600x750+100+100")
self.label = tk.Label(master, text="Select video file or enter URL:")
self.label.pack()
# --- Set Dark Mode by Default ---
customtkinter.set_appearance_mode("Dark")
customtkinter.set_default_color_theme("blue") # Temporarily using built-in theme for build stability
self.filepath_label = tk.Label(master, text="")
self.filepath_label.pack()
# Create the main content frame
self.main_frame = customtkinter.CTkFrame(master, fg_color="transparent") # Make it transparent to root
self.main_frame.pack(fill="both", expand=True) # Add some padding to the main frame
self.browse_button = tk.Button(master, text="Browse", command=self.browse_file)
self.browse_button.pack()
# --- Footer Frame (packed into main_frame) ---
self.footer_frame = customtkinter.CTkFrame(self.main_frame, fg_color="transparent")
self.footer_frame.pack(side="bottom", fill="x", pady=(5, 10))
self.url_label = tk.Label(master, text="URL:")
self.url_label.pack()
self.nickname_label = customtkinter.CTkLabel(self.footer_frame, text="by ramforth")
self.nickname_label.pack(side="left", padx=(10, 0))
self.url_entry = tk.Entry(master, width=50)
self.url_entry.pack()
self.gitea_link = customtkinter.CTkLabel(self.footer_frame, text="https://gitea.ramforth.net/ramforth", text_color="#4077d1", cursor="hand2")
self.gitea_link.pack(side="right", padx=(0, 10))
self.gitea_link.bind("<Button-1>", lambda e: self.open_link("https://gitea.ramforth.net/ramforth"))
underline_font = customtkinter.CTkFont(family="sans-serif", size=12, underline=True)
self.gitea_link.configure(font=underline_font)
self.quality_label = tk.Label(master, text="Quality:")
self.quality_label.pack()
# --- Status and Progress (packed into main_frame) ---
self.status_var = customtkinter.StringVar(value="Ready")
self.status_label = customtkinter.CTkLabel(self.main_frame, textvariable=self.status_var)
self.status_label.pack(side="bottom", fill="x", padx=10, pady=(5, 0))
self.quality_var = tk.StringVar(master)
self.quality_var.set("medium") # default value
self.quality_menu = tk.OptionMenu(master, self.quality_var, "low", "medium", "high", "archive")
self.quality_menu.pack()
self.progress = customtkinter.CTkProgressBar(self.main_frame, orientation="horizontal")
self.progress.pack(side="bottom", fill="x", padx=10, pady=(0, 10))
self.progress.set(0)
self.cookies_label = tk.Label(master, text="Cookies from Browser:")
self.cookies_label.pack()
# --- Mode Switch (packed into main_frame) ---
self.mode_switch = customtkinter.CTkSwitch(self.main_frame, text="", command=self.toggle_mode)
self.mode_switch.pack(pady=10, padx=20, anchor="w")
self.mode_switch.select()
self.cookies_var = tk.StringVar(master)
self.cookies_var.set("none") # default value
self.cookies_menu = tk.OptionMenu(master, self.cookies_var, "none", "brave", "chrome", "chromium", "edge", "firefox", "opera", "safari", "vivaldi", "whale")
self.cookies_menu.pack()
self.mode_label = customtkinter.CTkLabel(self.main_frame)
self.mode_label.pack(pady=0, padx=20, anchor="w")
self.toggle_mode() # Ensure initial mode is set based on switch state
self.output_dir_label = tk.Label(master, text="Output Directory:")
self.output_dir_label.pack()
# --- Main Widgets (packed into main_frame) ---
self.input_label_frame = customtkinter.CTkFrame(self.main_frame, fg_color="transparent")
self.input_label_frame.pack(pady=(10, 5))
self.output_dir_button = tk.Button(master, text="Select Directory", command=self.select_output_dir)
self.output_dir_button.pack()
self.label_part1 = customtkinter.CTkLabel(self.input_label_frame, text="Select video file ")
self.label_part1.pack(side="left")
self.output_dir_path_label = tk.Label(master, text="")
self.output_dir_path_label.pack()
or_font = customtkinter.CTkFont(family="sans-serif", weight="bold", underline=True)
self.label_or = customtkinter.CTkLabel(self.input_label_frame, text="or", font=or_font)
self.label_or.pack(side="left")
self.convert_button = tk.Button(master, text="Convert", command=self.convert)
self.convert_button.pack()
self.label_part2 = customtkinter.CTkLabel(self.input_label_frame, text=" enter URL:")
self.label_part2.pack(side="left")
self.cancel_button = tk.Button(master, text="Cancel", command=self.cancel_conversion, state=tk.DISABLED)
self.cancel_button.pack()
self.filepath_label = customtkinter.CTkLabel(self.main_frame, text="No file selected", fg_color=("gray75", "gray25"))
self.filepath_label.pack(pady=5, padx=20, fill="x")
self.status_var = tk.StringVar()
self.status_label = tk.Label(master, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
self.status_label.pack(side=tk.BOTTOM, fill=tk.X)
self.browse_button = customtkinter.CTkButton(self.main_frame, text="Browse", command=self.browse_file)
self.browse_button.pack(pady=5)
self.progress = ttk.Progressbar(master, orient=tk.HORIZONTAL, length=100, mode='determinate')
self.progress.pack(side=tk.BOTTOM, fill=tk.X)
self.url_label = customtkinter.CTkLabel(self.main_frame, text="URL:")
self.url_label.pack(pady=(15, 5))
self.url_entry = customtkinter.CTkEntry(self.main_frame, placeholder_text="https://...")
self.url_entry.pack(pady=5, padx=20, fill="x")
self.quality_label = customtkinter.CTkLabel(self.main_frame, text="Quality:", fg_color="transparent")
self.quality_label.pack(pady=(15, 5))
self.quality_var = customtkinter.StringVar(master, value="medium")
self.quality_menu = customtkinter.CTkOptionMenu(self.main_frame, variable=self.quality_var, values=["low", "medium", "high", "archive"])
self.quality_menu.pack(pady=5)
self.cookies_label = customtkinter.CTkLabel(self.main_frame, text="Cookies from Browser:", fg_color="transparent")
self.cookies_label.pack(pady=(15, 5))
self.cookies_var = customtkinter.StringVar(master, value="none")
self.cookies_menu = customtkinter.CTkOptionMenu(self.main_frame, variable=self.cookies_var, values=["none", "brave", "chrome", "chromium", "edge", "firefox", "opera", "safari", "vivaldi", "whale"])
self.cookies_menu.pack(pady=5)
self.output_dir_label = customtkinter.CTkLabel(self.main_frame, text="Output Directory:", fg_color="transparent")
self.output_dir_label.pack(pady=(15, 5))
self.output_dir_button = customtkinter.CTkButton(self.main_frame, text="Select Directory", command=self.select_output_dir)
self.output_dir_button.pack(pady=5)
self.output_dir_path_label = customtkinter.CTkLabel(self.main_frame, text="No directory selected", fg_color=("gray75", "gray25"))
self.output_dir_path_label.pack(pady=5, padx=20, fill="x")
self.convert_button = customtkinter.CTkButton(self.main_frame, text="Convert", command=self.convert)
self.convert_button.pack(pady=(20, 5))
self.cancel_button = customtkinter.CTkButton(self.main_frame, text="Cancel", command=self.cancel_conversion, state="disabled")
self.cancel_button.pack(pady=5)
def open_link(self, url):
"""Opens the given URL in a web browser."""
webbrowser.open_new(url)
def toggle_mode(self):
# Check if the switch is on (1) or off (0)
if self.mode_switch.get() == 1:
customtkinter.set_appearance_mode("Dark")
customtkinter.set_default_color_theme("blue") # Temporarily using built-in theme
self.main_frame.configure(fg_color="#121212") # Set main frame background
self.mode_label.configure(text="Dark Mode")
else:
customtkinter.set_appearance_mode("Light")
customtkinter.set_default_color_theme("blue") # Temporarily using built-in theme
self.main_frame.configure(fg_color="#ecd7b2") # Set main frame background
self.mode_label.configure(text="Light Mode")
def browse_file(self):
filepath = filedialog.askopenfilename()
self.filepath_label.config(text=filepath)
if filepath:
self.filepath_label.configure(text=filepath)
def select_output_dir(self):
output_dir = filedialog.askdirectory()
self.output_dir_path_label.config(text=output_dir)
if output_dir:
self.output_dir_path_label.configure(text=output_dir)
def convert(self):
input_path = self.filepath_label.cget("text")
@@ -83,17 +145,23 @@ class VideoConverterGUI:
output_dir = self.output_dir_path_label.cget("text")
quality = self.quality_var.get()
if url:
input_path = ""
self.filepath_label.configure(text="Using URL")
elif input_path == "No file selected":
input_path = ""
if not (input_path or url):
messagebox.showerror("Error", "Please select a file or enter a URL.")
return
if not output_dir:
if output_dir == "No directory selected":
messagebox.showerror("Error", "Please select an output directory.")
return
self.convert_button.config(state=tk.DISABLED)
self.cancel_button.config(state=tk.NORMAL)
self.status_var.set("Starting conversion...")
self.convert_button.configure(state="disabled")
self.cancel_button.configure(state="normal")
self.status_var.set("Starting...")
self.conversion_thread = threading.Thread(
target=self._run_conversion,
@@ -106,31 +174,32 @@ class VideoConverterGUI:
try:
if url:
self.status_var.set("Downloading video...")
# Since we don't have progress for downloads, we'll use indeterminate mode
self.progress['mode'] = 'indeterminate'
self.progress.configure(mode='indeterminate')
self.progress.start()
input_path = downloader.download_video(url, output_dir, cookies_from_browser)
self.progress.stop()
self.progress['mode'] = 'determinate'
self.progress.configure(mode='determinate')
if not input_path:
raise Exception("Download failed.")
self.status_var.set("Converting video...")
duration = utils.get_video_duration(input_path)
if not duration:
# If we can't get the duration, use indeterminate mode
self.progress['mode'] = 'indeterminate'
self.progress.configure(mode='indeterminate')
self.progress.start()
else:
self.progress['maximum'] = duration
self.progress.configure(mode='determinate')
self.progress.set(0)
self.progress_thread = threading.Thread(target=self._update_progress, daemon=True)
self.progress_thread = threading.Thread(target=self._update_progress, args=(duration,), daemon=True)
self.progress_thread.start()
success = converter.convert_video(input_path, output_dir, quality)
if success:
self.status_var.set("Conversion successful!")
self.progress.set(1)
messagebox.showinfo("Success", "Conversion completed successfully!")
else:
raise Exception("Conversion failed. Check logs for details.")
@@ -148,12 +217,17 @@ class VideoConverterGUI:
messagebox.showerror("Error", f"An error occurred: {e}\n\nCould not read log file: {log_e}")
finally:
self.convert_button.config(state=tk.NORMAL)
self.cancel_button.config(state=tk.DISABLED)
self.convert_button.configure(state="normal")
self.cancel_button.configure(state="disabled")
self.progress.stop()
self.progress['value'] = 0
if "Error" not in self.status_var.get():
self.progress.set(1)
else:
self.progress.set(0)
if self.status_var.get() == "Starting...":
self.status_var.set("Ready")
def _update_progress(self):
def _update_progress(self, duration):
progress_file = "ffmpeg_progress.log"
while self.conversion_thread.is_alive():
try:
@@ -163,20 +237,22 @@ class VideoConverterGUI:
last_line = lines[-1]
if "out_time_ms" in last_line:
time_ms = int(last_line.split("=")[1])
self.progress['value'] = time_ms / 1000000
current_time_sec = time_ms / 1000000
if duration and duration > 0:
progress_value = max(0, min(1, current_time_sec / duration))
self.progress.set(progress_value)
except (FileNotFoundError, IndexError, ValueError):
pass
time.sleep(0.1)
def cancel_conversion(self):
# This is a bit tricky, as we can't easily kill the thread.
# For now, we'll just show a message.
# A more robust solution would involve a more complex mechanism
# to signal the thread to stop.
messagebox.showinfo("Cancel", "Cancellation requested. The current operation will continue until it is finished.")
self.status_var.set("Cancellation requested...")
messagebox.showinfo("Cancel", "Cancellation requested. The current operation will try to stop.")
self.convert_button.configure(state="normal")
self.cancel_button.configure(state="disabled")
if __name__ == '__main__':
root = tk.Tk()
root = customtkinter.CTk()
gui = VideoConverterGUI(root)
root.mainloop()

BIN
images/video_converter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -1 +1,2 @@
yt-dlp
customtkinter

56
test.css Normal file
View File

@@ -0,0 +1,56 @@
:root {
/** Base colors */
--clr-dark-a0: #000000;
--clr-light-a0: #ffffff;
/** Theme primary colors */
--clr-primary-a0: #759542;
--clr-primary-a10: #84a057;
--clr-primary-a20: #94ac6b;
--clr-primary-a30: #a3b77f;
--clr-primary-a40: #b2c394;
--clr-primary-a50: #c1cfa9;
/** Theme surface colors */
--clr-surface-a0: #121212;
--clr-surface-a10: #282828;
--clr-surface-a20: #3f3f3f;
--clr-surface-a30: #575757;
--clr-surface-a40: #717171;
--clr-surface-a50: #8b8b8b;
/** Theme tonal surface colors */
--clr-surface-tonal-a0: #1c1e17;
--clr-surface-tonal-a10: #31332c;
--clr-surface-tonal-a20: #474943;
--clr-surface-tonal-a30: #5f605b;
--clr-surface-tonal-a40: #777974;
--clr-surface-tonal-a50: #91928e;
/** Success colors */
--clr-success-a0: #22946e;
--clr-success-a10: #47d5a6;
--clr-success-a20: #9ae8ce;
/** Warning colors */
--clr-warning-a0: #a87a2a;
--clr-warning-a10: #d7ac61;
--clr-warning-a20: #ecd7b2;
/** Danger colors */
--clr-danger-a0: #9c2121;
--clr-danger-a10: #d94a4a;
--clr-danger-a20: #eb9e9e;
/** Info colors */
--clr-info-a0: #21498a;
--clr-info-a10: #4077d1;
--clr-info-a20: #92b2e5;
}
/** Examples */
.bg-primary {
color: var(--clr-primary-a50);
background-color: var(--clr-surface-a0);
}

View File

@@ -5,7 +5,7 @@ block_cipher = None
a = Analysis(['main.py'],
pathex=['/home/joe/Cloud9/Documents/Obisdian/projects/Video Converter'],
binaries=[],
datas=[('gui.py', '.')],
datas=[('gui.py', '.'), ('custom_theme_dark.json', '.'), ('custom_theme_light.json', '.')],
hiddenimports=[],
hookspath=[],
hooksconfig={},