Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.

Commit a259046

Browse files
committed
main
0 parents  commit a259046

27 files changed

+820
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
installer
2+
build

app.ico

4.08 KB
Binary file not shown.

bin/avcodec-61.dll

84.2 MB
Binary file not shown.

bin/avdevice-61.dll

4.95 MB
Binary file not shown.

bin/avfilter-10.dll

27.4 MB
Binary file not shown.

bin/avformat-61.dll

18.8 MB
Binary file not shown.

bin/avutil-59.dll

2.67 MB
Binary file not shown.

bin/ffmpeg.exe

414 KB
Binary file not shown.

bin/ffplay.exe

12.1 MB
Binary file not shown.

bin/ffprobe.exe

216 KB
Binary file not shown.

bin/postproc-58.dll

87 KB
Binary file not shown.

bin/swresample-5.dll

643 KB
Binary file not shown.

bin/swscale-8.dll

687 KB
Binary file not shown.

bin/yt-dlp.exe

18.3 MB
Binary file not shown.

makeinstall.iss

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
; Inno Setup Script
2+
[Setup]
3+
AppName=Youtube Downloader
4+
AppVersion=2024.09.23
5+
DefaultDirName={localappdata}\kaoniewji\mduyoutube
6+
DefaultGroupName=Youtube Downloader
7+
OutputDir=.\installer
8+
OutputBaseFilename=YoutubeDownloaderInstaller
9+
ArchitecturesInstallIn64BitMode=x64
10+
Compression=lzma2
11+
SolidCompression=yes
12+
13+
[Files]
14+
Source: "build\exe.win-amd64-3.12\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
15+
16+
[Icons]
17+
Name: "{group}\Youtube Downloader"; Filename: "{app}\ytmdu.exe"
18+
Name: "{userdesktop}\Youtube Downloader"; Filename: "{app}\ytmdu.exe"
19+
20+
[Run]
21+
Filename: "{app}\ytmdu.exe"; Description: "Launch Youtube Downloader"; Flags: nowait postinstall skipifsilent

mdu.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import sys
2+
from PySide6.QtWidgets import QApplication
3+
from PySide6.QtGui import QIcon # Import QIcon
4+
from src.gui.mainwindow import MainWindow
5+
import src.gui.resources_rc
6+
if __name__ == "__main__":
7+
app = QApplication(sys.argv)
8+
app.setStyle("fusion")
9+
10+
# Set application icon using resource
11+
app.setWindowIcon(QIcon(":/app.ico"))
12+
13+
window = MainWindow()
14+
window.show()
15+
sys.exit(app.exec())

readme.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# YouTube Downloader
2+
3+
A minimalist YouTube Downloader built with Qt, allowing users to easily download videos from YouTube.
4+
5+
## Features
6+
7+
- Simple and intuitive user interface
8+
- Download videos in various formats
9+
- Lightweight and efficient
10+
- Customizable settings for download preferences
11+
12+
## Installation
13+
14+
### Prerequisites
15+
16+
- Windows 10 or later
17+
- MacOS / Linux (coming soon...)
18+
- Internet connection
19+
20+
### Steps to Install
21+
22+
1. Download the installer from the [releases](link-to-your-releases) page.
23+
2. Run the installer.
24+
3. Follow the prompts to install the application to `C:\Users\<Username>\AppData\Local\kaoniewji\mduyoutube`.
25+
4. After installation, you can find the application in your Start Menu or on your Desktop.
26+
27+
## Usage
28+
29+
1. Launch the application.
30+
2. Enter the URL of the YouTube video you want to download.
31+
3. Select the desired format and quality.
32+
4. Click the "Download" button.
33+
5. Your video / audio will be saved to the designated folder.
34+
35+
## Contributing
36+
37+
Contributions are welcome! Please fork the repository and submit a pull request with your changes.
38+
39+
## License
40+
41+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
42+
43+
## Contact
44+
45+
For support or inquiries, please contact [Your Name](info.neixproducer@gmail.com).
46+
47+
## Acknowledgments
48+
49+
- [Qt](https://www.qt.io/) for the framework
50+
- [cx_Freeze](https://cx-freeze.readthedocs.io/en/latest/) for packaging the application
51+
52+
## Credit
53+
- [Yt-Dlp](https://github.com/yt-dlp/yt-dlp) A feature-rich command-line audio/video downloader
54+
- [FFmpeg](https://www.ffmpeg.org/) A complete, cross-platform solution to record, convert and stream audio and video. Download Converting video and audio has never been so easy.

setup.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from cx_Freeze import setup, Executable
2+
import sys
3+
import os
4+
from PySide6 import QtCore
5+
6+
# Collect PySide6 required libraries (Qt's dynamic libraries)
7+
pyside6_path = os.path.dirname(QtCore.__file__)
8+
9+
# Specify the bin folder path
10+
bin_folder = os.path.join(os.getcwd(), 'bin')
11+
12+
# Collect all .exe and .dll files from the bin folder
13+
include_files = [
14+
os.path.join(pyside6_path, "plugins", "platforms"), # Ensure platform plugins are included
15+
("app.ico", "app.ico"), # Include the application icon
16+
(bin_folder, "bin"), # Include the entire bin folder
17+
]
18+
19+
# # Add all .exe and .dll files from the bin folder
20+
# for file in os.listdir(bin_folder):
21+
# if file.endswith('.exe') or file.endswith('.dll'):
22+
# include_files.append((os.path.join(bin_folder, file), file))
23+
24+
build_exe_options = {
25+
"packages": ["os", "sys", "PySide6.QtCore", "PySide6.QtGui", "PySide6.QtWidgets"],
26+
"include_files": include_files,
27+
"excludes": ["tkinter"], # Exclude any unnecessary libraries
28+
}
29+
30+
# Define the setup
31+
setup(
32+
name="Youtube Downloader",
33+
version="2024.09.23",
34+
description="Minimalist Youtube Downloader with Qt",
35+
options={"build_exe": build_exe_options},
36+
executables=[Executable("mdu.py", base="Win32GUI", icon="app.ico")],
37+
)
5.42 KB
Binary file not shown.

src/core/downloader.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import os
2+
import re
3+
import subprocess
4+
import sys
5+
from PySide6.QtCore import QObject, Signal
6+
7+
workdir = os.path.join(os.path.dirname(__file__),'bin')
8+
class DownloaderSignals(QObject):
9+
progress = Signal(float, str, str, str)
10+
error = Signal(str)
11+
finished = Signal(str, str, str) # filename, file_path, file_type
12+
13+
class Downloader(QObject):
14+
def __init__(self):
15+
super().__init__()
16+
self.signals = DownloaderSignals()
17+
18+
def download(self, url, is_audio, audio_format, resolution, fps, download_dir):
19+
try:
20+
# Construct the command with the working directory set to the bin directory
21+
cmd = ['yt-dlp.exe', url, '--newline', '-P', download_dir]
22+
if is_audio:
23+
cmd.extend(['-x', '--audio-format', audio_format])
24+
else:
25+
# Construct video format string based on resolution
26+
format_string = f"bestvideo[height<={resolution}][ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"
27+
cmd.extend(['-f', format_string])
28+
if fps:
29+
cmd.append(f'--fps={fps}')
30+
31+
# Run the subprocess with the bin directory as the working directory
32+
process = subprocess.Popen(
33+
cmd,
34+
stdout=subprocess.PIPE,
35+
stderr=subprocess.STDOUT,
36+
universal_newlines=True,
37+
cwd=workdir, # Set working directory
38+
creationflags=subprocess.CREATE_NO_WINDOW
39+
)
40+
41+
filename = None
42+
for line in process.stdout:
43+
if '[download]' in line:
44+
self.parse_progress(line)
45+
elif 'ERROR:' in line:
46+
self.signals.error.emit(line.strip())
47+
elif '[ExtractAudio] Destination:' in line:
48+
filename = line.split('Destination:')[1].strip()
49+
50+
process.wait()
51+
if process.returncode != 0:
52+
self.signals.error.emit(f"yt-dlp exited with code {process.returncode}")
53+
else:
54+
if not filename:
55+
filename = self.get_last_modified_file(download_dir)
56+
file_path = os.path.join(download_dir, filename)
57+
file_type = "Audio" if is_audio else "Video"
58+
self.signals.finished.emit(filename, file_path, file_type)
59+
except Exception as e:
60+
self.signals.error.emit(str(e))
61+
print(os.path.join(os.path.dirname(__file__),".", 'bin'))
62+
63+
def parse_progress(self, line):
64+
progress = 0
65+
file_size = ""
66+
download_speed = ""
67+
eta = ""
68+
69+
match = re.search(r'(\d+(?:\.\d+)?)%', line)
70+
if match:
71+
progress = float(match.group(1))
72+
73+
size_match = re.search(r'of\s+(\S+)', line)
74+
if size_match:
75+
file_size = size_match.group(1)
76+
77+
speed_match = re.search(r'at\s+(\S+)', line)
78+
if speed_match:
79+
download_speed = speed_match.group(1)
80+
81+
eta_match = re.search(r'ETA\s+(\S+)', line)
82+
if eta_match:
83+
eta = eta_match.group(1)
84+
85+
self.signals.progress.emit(progress, file_size, download_speed, eta)
86+
87+
def get_last_modified_file(self, directory):
88+
files = [os.path.join(directory, f) for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
89+
if not files:
90+
return None
91+
return max(files, key=os.path.getmtime)
17.2 KB
Binary file not shown.
4.96 KB
Binary file not shown.

0 commit comments

Comments
 (0)