Skip to content

Commit f1d9583

Browse files
committed
Initial commit - YT Converter
0 parents  commit f1d9583

8 files changed

Lines changed: 270 additions & 0 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dist/
2+
build/
3+
appimage-build/
4+
*.spec
5+
__pycache__/
6+
AppDir/

AppImageBuilder.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
version: 1
2+
script:
3+
- rm -rf AppDir || true
4+
5+
AppDir:
6+
path: ./AppDir
7+
app_info:
8+
id: com.khinphunnadet.ytconverter
9+
name: YT Converter
10+
icon: yt-converter
11+
version: 1.0.0
12+
exec: usr/bin/python3
13+
exec_args: "$APPDIR/usr/src/app.py $@"
14+
15+
apt:
16+
arch: amd64
17+
sources:
18+
- sourceline: 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse'
19+
key_url: 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x871920D1991BC93C'
20+
include:
21+
- python3
22+
- python3-pip
23+
- python3-tk
24+
- ffmpeg
25+
- libgtk-3-0
26+
exclude: []
27+
28+
files:
29+
include: []
30+
exclude:
31+
- usr/share/man
32+
- usr/share/doc/*/README.*
33+
- usr/share/doc/*/changelog.*
34+
- usr/share/doc/*/NEWS.*
35+
- usr/share/doc/*/TODO.*
36+
37+
after_bundle:
38+
- pip3 install --target=$APPDIR/usr/lib/python3/dist-packages customtkinter yt-dlp
39+
- mkdir -p $APPDIR/usr/src
40+
- cp app.py $APPDIR/usr/src/
41+
42+
AppImage:
43+
arch: x86_64
44+
update-information: None
45+
sign-key: None

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# YT Converter
2+
3+
A simple YouTube video/audio downloader for Linux, built with Python and CustomTkinter.
4+
5+
## Features
6+
- Download YouTube videos as MP3 (audio) or MP4 (video)
7+
- Quality selector: Best / 720p / 480p
8+
- Real-time download progress bar
9+
- Dark theme UI
10+
- Saves to `~/Downloads` automatically
11+
12+
## Requirements
13+
- Python 3.10+
14+
- ffmpeg (`sudo apt install ffmpeg`)
15+
16+
## Run from source
17+
```bash
18+
git clone https://github.com/thenullastris/yt-converter.git
19+
cd yt-converter
20+
chmod +x run.sh
21+
./run.sh
22+
```
23+
24+
## Build executable
25+
```bash
26+
chmod +x build-appimage.sh
27+
./build-appimage.sh
28+
```
29+
30+
## Download
31+
Grab the latest binary from [Releases](https://github.com/thenullastris/yt-converter/releases).
32+
33+
## Built by
34+
[TheNullAstris](https://github.com/thenullastris)

app.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
import customtkinter as ctk
3+
import yt_dlp
4+
import threading
5+
import os
6+
7+
ctk.set_appearance_mode("dark")
8+
ctk.set_default_color_theme("blue")
9+
10+
DOWNLOAD_DIR = os.path.expanduser("~/Downloads")
11+
12+
class YTConverterApp(ctk.CTk):
13+
def __init__(self):
14+
super().__init__()
15+
self.title("YT Converter")
16+
self.geometry("520x420")
17+
self.resizable(False, False)
18+
19+
# Title
20+
ctk.CTkLabel(self, text="YouTube Converter", font=ctk.CTkFont(size=22, weight="bold")).pack(pady=(24, 4))
21+
ctk.CTkLabel(self, text="Download videos or audio from YouTube", text_color="gray").pack()
22+
23+
# URL input
24+
self.url_entry = ctk.CTkEntry(self, placeholder_text="Paste YouTube URL here...", width=420, height=40)
25+
self.url_entry.pack(pady=20)
26+
27+
# Format frame
28+
fmt_frame = ctk.CTkFrame(self, fg_color="transparent")
29+
fmt_frame.pack()
30+
ctk.CTkLabel(fmt_frame, text="Format:").pack(side="left", padx=(0, 12))
31+
self.format_var = ctk.StringVar(value="mp3")
32+
ctk.CTkRadioButton(fmt_frame, text="MP3 (Audio)", variable=self.format_var, value="mp3").pack(side="left", padx=8)
33+
ctk.CTkRadioButton(fmt_frame, text="MP4 (Video)", variable=self.format_var, value="mp4").pack(side="left", padx=8)
34+
35+
# Quality frame
36+
qual_frame = ctk.CTkFrame(self, fg_color="transparent")
37+
qual_frame.pack(pady=8)
38+
ctk.CTkLabel(qual_frame, text="Quality:").pack(side="left", padx=(0, 12))
39+
self.quality_var = ctk.StringVar(value="best")
40+
ctk.CTkRadioButton(qual_frame, text="Best", variable=self.quality_var, value="best").pack(side="left", padx=8)
41+
ctk.CTkRadioButton(qual_frame, text="720p", variable=self.quality_var, value="720").pack(side="left", padx=8)
42+
ctk.CTkRadioButton(qual_frame, text="480p", variable=self.quality_var, value="480").pack(side="left", padx=8)
43+
44+
# Progress bar
45+
self.progress = ctk.CTkProgressBar(self, width=420)
46+
self.progress.pack(pady=16)
47+
self.progress.set(0)
48+
49+
# Status
50+
self.status = ctk.CTkLabel(self, text="Ready", text_color="gray")
51+
self.status.pack()
52+
53+
# Button
54+
self.btn = ctk.CTkButton(self, text="⬇ Convert & Download", width=220, height=44,
55+
font=ctk.CTkFont(size=14, weight="bold"), command=self.start_download)
56+
self.btn.pack(pady=18)
57+
58+
# Output folder label
59+
ctk.CTkLabel(self, text=f"Saves to: {DOWNLOAD_DIR}", text_color="gray",
60+
font=ctk.CTkFont(size=11)).pack()
61+
62+
def start_download(self):
63+
url = self.url_entry.get().strip()
64+
if not url:
65+
self.status.configure(text="⚠️ Please enter a URL", text_color="orange")
66+
return
67+
self.btn.configure(state="disabled", text="Downloading...")
68+
self.progress.set(0)
69+
self.status.configure(text="Starting...", text_color="gray")
70+
threading.Thread(target=self.download, args=(url,), daemon=True).start()
71+
72+
def download(self, url):
73+
fmt = self.format_var.get()
74+
quality = self.quality_var.get()
75+
76+
def progress_hook(d):
77+
if d['status'] == 'downloading':
78+
total = d.get('total_bytes') or d.get('total_bytes_estimate', 0)
79+
downloaded = d.get('downloaded_bytes', 0)
80+
if total:
81+
pct = downloaded / total
82+
self.progress.set(pct)
83+
self.status.configure(text=f"Downloading... {int(pct*100)}%", text_color="gray")
84+
elif d['status'] == 'finished':
85+
self.status.configure(text="Processing...", text_color="gray")
86+
87+
if fmt == "mp3":
88+
ydl_opts = {
89+
'format': 'bestaudio/best',
90+
'outtmpl': os.path.join(DOWNLOAD_DIR, '%(title)s.%(ext)s'),
91+
'progress_hooks': [progress_hook],
92+
'postprocessors': [{'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192'}],
93+
}
94+
else:
95+
fmt_str = 'bestvideo+bestaudio/best' if quality == 'best' else f'bestvideo[height<={quality}]+bestaudio/best'
96+
ydl_opts = {
97+
'format': fmt_str,
98+
'outtmpl': os.path.join(DOWNLOAD_DIR, '%(title)s.%(ext)s'),
99+
'progress_hooks': [progress_hook],
100+
'merge_output_format': 'mp4',
101+
}
102+
103+
try:
104+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
105+
ydl.download([url])
106+
self.progress.set(1)
107+
self.status.configure(text="✅ Done! File saved to ~/Downloads", text_color="#4CAF50")
108+
except Exception as e:
109+
self.status.configure(text=f"❌ Error: {str(e)[:60]}", text_color="#f44336")
110+
finally:
111+
self.btn.configure(state="normal", text="⬇ Convert & Download")
112+
113+
if __name__ == "__main__":
114+
app = YTConverterApp()
115+
app.mainloop()

appimage/yt-converter.desktop

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Desktop Entry]
2+
Name=YT Converter
3+
Comment=Download and convert YouTube videos
4+
Exec=app.py
5+
Icon=yt-converter
6+
Type=Application
7+
Categories=AudioVideo;Network;
8+
Terminal=false

build-appimage.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "================================================"
5+
echo " YT Converter - AppImage Builder"
6+
echo "================================================"
7+
8+
# Install appimage-builder if not present
9+
if ! command -v appimage-builder &> /dev/null; then
10+
echo "📦 Installing appimage-builder..."
11+
sudo apt install -y appimage-builder
12+
fi
13+
14+
# Clean previous build
15+
rm -rf AppDir *.AppImage 2>/dev/null || true
16+
17+
# Build
18+
echo "🔨 Building AppImage..."
19+
appimage-builder --recipe AppImageBuilder.yml
20+
21+
echo ""
22+
echo "✅ Done! Your AppImage is ready:"
23+
ls -lh *.AppImage
24+
25+
echo ""
26+
echo "To run it:"
27+
echo " chmod +x *.AppImage"
28+
echo " ./*.AppImage"

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
customtkinter
2+
yt-dlp

run.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "================================================"
5+
echo " YT Converter - Setup & Run"
6+
echo "================================================"
7+
8+
# Check Python
9+
if ! command -v python3 &> /dev/null; then
10+
echo "❌ Python3 not found. Install with: sudo apt install python3"
11+
exit 1
12+
fi
13+
14+
# Check tkinter
15+
python3 -c "import tkinter" 2>/dev/null || {
16+
echo "⚠️ tkinter not found. Installing..."
17+
sudo apt install -y python3-tk
18+
}
19+
20+
# Check ffmpeg
21+
if ! command -v ffmpeg &> /dev/null; then
22+
echo "⚠️ ffmpeg not found. Installing..."
23+
sudo apt install -y ffmpeg
24+
fi
25+
26+
# Install dependencies
27+
echo "📦 Installing Python dependencies..."
28+
pip3 install customtkinter yt-dlp --break-system-packages -q
29+
30+
# Run the app
31+
echo "🚀 Launching YT Converter..."
32+
python3 app.py

0 commit comments

Comments
 (0)