Skip to content

Commit

Permalink
Merge pull request #8 from jslay88/proxy_support
Browse files Browse the repository at this point in the history
Finalized proxy support and added pyinstaller spec file
  • Loading branch information
mraza007 authored Jul 24, 2018
2 parents 7de94e7 + 2eb2121 commit 5d26570
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 38 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ MANIFEST
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
*.exe
!pyinstaller.spec

# Installer logs
pip-log.txt
Expand Down Expand Up @@ -107,4 +109,5 @@ venv.bak/
.idea/

# Downloaded video/audio
*.mp4
*.mp4
*.webm
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# videodownloader
A Simple Script writtten in Python to Download YouTube Videos
A Simple Script written in Python to Download YouTube Videos
These videos can also be downloaded as audio

### Installation
Expand All @@ -21,21 +21,28 @@ or use the command line arguments on the script (`script.py`).

python3 gui.py
python3 script.py
python3 script.py -u rfscVS0vtbw -l
python3 script.py -u rfscVS0vtbw -o videos/ -f my_downloaded_video
python3 script.py -u rfscVS0vtbw -i 278 -o videos/ -f my_downloaded_itag_video
python3 script.py -u rfscVS0vtbw -o audio/ -f my_downloaded_audio --audio-only
python3 script.py --help

-u --url YouTube URL or YouTube Video ID to download
-l --list-streams List available streams for this YouTube Video
instead of download. Use -a/--audio-only to list audio streams.
Download specific stream with the
itag ID and -i/--itag argument.
-i --itag Stream ITAG to download for given YouTube Video/ID.
List streams with -l/--list-streams argument.
If ITAG is not provided, default stream will be downloaded.
Downloading with ITAG ignores -a/--audio-only.
-o --output-path Output Directory Path
-f --filename Override the output filename. Does not override file extension
-p --proxy Proxy to use. Ex http://xxx.xxx.xxx:8080. NOTE: You need https proxy for https URL!
-a --audio-only Download Audio Only



If you would like to suggest changes feel free to fork this repo and create PR or submit an issue

Feel Free to DM on twitter if you have any questions
[twitter](http://www.twitter.com/muhammad_o7)
**NOTE:** If you are using a proxy, you need https proxy for https URL!

![GUI Screenshot](assets/gui_screenshot.png)![GUI Screenshot Linux](assets/gui_screenshot_linux.png)

Expand All @@ -45,3 +52,18 @@ Here's the demo on how to use it
<a href="https://vimeo.com/281200561"><img width="60%" src="assets/img.png" alt="view demo"></a><br>
</div>

### Building Windows EXE

If you want to build the Windows EXE manually, make sure you have pywin32 installed,
as well as pyinstaller. Then run the following command from the script directory.

pyinstaller --clean --onefile pyinstaller.spec

The EXE will be put into the `dist` directory within the script directory.

### Contributing

If you would like to suggest changes feel free to fork this repo and create PR or submit an issue

Feel Free to DM on twitter if you have any questions
[twitter](http://www.twitter.com/muhammad_o7)
Binary file modified assets/gui_screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/gui_screenshot_linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/ytdl.ico
Binary file not shown.
27 changes: 18 additions & 9 deletions download_youtube_video.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from os import makedirs
from pytube import YouTube
from pytube.helpers import safe_filename


def download_youtube_video(url, audio_only=False, output_path=None,
def download_youtube_video(url, itag=None, audio_only=False, output_path=None,
filename=None, filename_prefix=None,
proxies=None):
proxies=None, progress_callback=None):
"""
Download a YouTube Video.
:param url: Full URL to YouTube Video or YouTube Video ID
:type url: str
:param itag: YouTube Stream ITAG to Download
:type itag: int
:param audio_only: Download only the audio for the video. Takes longer than video.
:type audio_only: bool
:param output_path: Path to folder to output file.
Expand All @@ -24,18 +27,24 @@ def download_youtube_video(url, audio_only=False, output_path=None,
"""
if output_path:
makedirs(output_path, exist_ok=True)
if 'https' not in url:
if 'http' not in url:
url = 'https://www.youtube.com/watch?v=%s' % url
if proxies:
print(proxies)
video = YouTube(url, proxies=proxies)
else:
video = YouTube(url)
if audio_only:
stream = video.streams.filter(only_audio=True).first()
if progress_callback:
video.register_on_progress_callback(progress_callback)
if itag:
itag = int(itag)
stream = video.streams.get_by_itag(itag)
else:
stream = video.streams.first()
stream = video.streams.filter(only_audio=audio_only).first()
print('Download Started: %s' % video.title)
if filename:
filename = safe_filename(filename)
stream.download(output_path=output_path, filename=filename)
print('Download Complete: %s' % video.title)
return video.title + '.mp4' if filename is None else filename + '.mp4'
file_type = '.' + stream.mime_type.split('/')[1]
filename = stream.default_filename if filename is None else filename + file_type
print('Download Complete! Saved to file: %s' % filename)
return filename
123 changes: 105 additions & 18 deletions gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import os.path


from pytube import YouTube
from threading import Thread
from tkinter import filedialog, messagebox
from tkinter import filedialog, messagebox, ttk
from download_youtube_video import download_youtube_video
from pytube.exceptions import PytubeError, RegexMatchError

Expand All @@ -14,8 +15,10 @@ class YouTubeDownloadGUI(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.label_video_title = None
self.btn_download = None
self.btn_output_browse = None
self.btn_check_id = None
self.text_url = None
self.text_output_path = None
self.text_filename_override = None
Expand All @@ -25,15 +28,25 @@ def __init__(self, master=None):
self.output_path = tk.StringVar(self)
self.filename_override = tk.StringVar(self)
self.proxy = tk.StringVar(self)
self.video = None
self.stream = tk.IntVar(self)
self.streams = []
self.stream_widgets = []
self.file_size = 0
self.progress_bar = None

self.quit = None
self.last_row = 0

self.create_widgets()

def create_widgets(self):
tk.Label(self, text='YouTube URL/ID').grid(row=0, column=0)
self.text_url = tk.Entry(self, width=60)
self.text_url.grid(row=0, column=1, columnspan=2)
self.btn_check_id = tk.Button(self)
self.btn_check_id['text'] = 'Check Video'
self.btn_check_id['command'] = self.check_video
self.btn_check_id.grid(row=0, column=3)

tk.Label(self, text='Output Directory').grid(row=1, column=0)
self.text_output_path = tk.Entry(self, width=60, textvariable=self.output_path)
Expand All @@ -51,43 +64,107 @@ def create_widgets(self):
self.text_proxy = tk.Entry(self, width=60, textvariable=self.proxy)
self.text_proxy.grid(row=3, column=1, columnspan=2)

self.radio_video_audio.append(tk.Radiobutton(self, text='Video', variable=self.audio_only, value=False))
tk.Label(self, text='Media Type').grid(row=4, column=0)
self.radio_video_audio.append(tk.Radiobutton(self, text='Video', variable=self.audio_only,
value=False, command=self.check_video))
self.radio_video_audio.append(tk.Radiobutton(self, text='Audio (Takes Longer)', variable=self.audio_only,
value=True))

value=True, command=self.check_video))
self.radio_video_audio[0].grid(row=4, column=1)
self.radio_video_audio[1].grid(row=4, column=2)

self.btn_download = tk.Button(self)
self.btn_download['text'] = 'Download'
self.btn_download['command'] = self.download
self.btn_download.grid(row=5, column=1, columnspan=2)

self.quit = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.quit.grid(row=6, column=1, columnspan=2)
self.label_video_title = tk.Label(self)
self.label_video_title.grid(row=5, column=0, columnspan=4)
self.last_row = 5

def browse_output_path(self):
self.output_path.set(filedialog.askdirectory(initialdir='/', title='Select Output Folder'))
self.text_output_path.delete(0, tk.END)
self.text_output_path.insert(0, self.output_path.get())

def check_video(self):
self.btn_check_id['text'] = 'Checking...'
self.btn_check_id.config(state=tk.DISABLED)
Thread(target=self.threaded_check_video).start()

def threaded_check_video(self):
self.last_row = 5
self.stream.set(0)
[radio_button.destroy() for radio_button in self.stream_widgets]
if self.btn_download:
self.progress_bar.destroy()
self.btn_download.destroy()
url = self.text_url.get()
if 'https' not in url:
url = 'https://www.youtube.com/watch?v=%s' % url
try:
if self.proxy.get() != '':
self.video = YouTube(url, proxies={self.proxy.get().split(':')[0]: self.proxy.get()})
else:
self.video = YouTube(url)
self.label_video_title['text'] = self.video.title
self.streams = self.video.streams.filter(only_audio=self.audio_only.get()).all()

for stream in self.streams:
if self.audio_only.get():
text = f'Codec: {stream.audio_codec}, ' \
f'ABR: {stream.abr} ' \
f'File Type: {stream.mime_type.split("/")[1]}, Size: {stream.filesize // 1024} KB'
else:
if stream.video_codec is None:
continue
text = f'Res: {stream.resolution}, FPS: {stream.fps},' \
f' Video Codec: {stream.video_codec}, Audio Codec: {stream.audio_codec}, ' \
f'File Type: {stream.mime_type.split("/")[1]}, Size: {stream.filesize // 1024} KB'
radio_button = tk.Radiobutton(self, text=text, variable=self.stream, value=stream.itag)
self.last_row += 1
radio_button.grid(row=self.last_row, column=0, columnspan=4)
self.stream_widgets.append(radio_button)
self.last_row += 1
self.progress_bar = ttk.Progressbar(self, orient='horizontal', length=350, mode='determinate')
self.progress_bar.grid(row=self.last_row, column=1, columnspan=2)

self.progress_bar['value'] = 0
self.progress_bar['maximum'] = 100

self.last_row += 1
self.btn_download = tk.Button(self)
self.btn_download['text'] = 'Download'
self.btn_download['command'] = self.download
self.btn_download.config(state=tk.NORMAL)
self.btn_download.grid(row=self.last_row, column=1, columnspan=2)
except PytubeError as e:
messagebox.showerror('Something went wrong...', e)
except RegexMatchError as e:
messagebox.showerror('Something went wrong...', e)
finally:
self.btn_check_id['text'] = 'Check Video'
self.btn_check_id.config(state=tk.NORMAL)

def download(self):
self.btn_download['text'] = 'Downloading...'
self.btn_download.config(state=tk.DISABLED)
self.btn_check_id.config(state=tk.DISABLED)
self.btn_output_browse.config(state=tk.DISABLED)
[radio_button.config(state=tk.DISABLED) for radio_button in self.radio_video_audio]
Thread(target=self.threaded_download).start()

def update_progress_bar(self, stream, chunk, file_handle, bytes_remaining):
percentage = ((self.file_size - bytes_remaining) / self.file_size) * 100
self.progress_bar['value'] = percentage

def threaded_download(self):
try:
if self.proxy.get() != '':
proxy = {self.proxy.get().split(':')[0]: self.proxy.get()}
else:
proxy = None
filename = download_youtube_video(self.text_url.get(), audio_only=self.audio_only.get(),
for search_stream in self.streams:
if int(search_stream.itag) == int(self.stream.get()):
self.file_size = search_stream.filesize
break
filename = download_youtube_video(self.text_url.get(), itag=self.stream.get(),
output_path=self.output_path.get(),
filename=self.filename_override.get()
if self.filename_override.get() != '' else None,
proxies=proxy)
proxies=proxy, progress_callback=self.update_progress_bar)
messagebox.showinfo('Download Complete!', 'Download Complete!\n%s' % filename)
except PytubeError as e:
messagebox.showerror('Something went wrong...', e)
Expand All @@ -100,11 +177,21 @@ def threaded_download(self):
finally:
self.btn_download['text'] = 'Download'
self.btn_download.config(state=tk.NORMAL)
self.btn_check_id.config(state=tk.NORMAL)
self.btn_output_browse.config(state=tk.NORMAL)
[radio_button.config(state=tk.NORMAL) for radio_button in self.radio_video_audio]


def resource_path(relative_path):
import sys
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath('.'), relative_path)


if __name__ == '__main__':
root = tk.Tk()
app = YouTubeDownloadGUI(master=root)
app.master.title('YouTube Video/Audio Downloader')
app.master.tk.call('wm', 'iconphoto', app.master._w, tk.PhotoImage(file=os.path.abspath('assets/ytdl.png')))
app.master.tk.call('wm', 'iconphoto', app.master._w, tk.PhotoImage(file=resource_path('assets/ytdl.png')))
app.mainloop()
29 changes: 29 additions & 0 deletions pyinstaller.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
block_cipher = None

a = Analysis(['gui.py'],
binaries=None,
datas=None,
hiddenimports=[],
hookspath=None,
runtime_hooks=None,
excludes=None,
win_no_prefer_redirects=None,
win_private_assemblies=None,
cipher=block_cipher)

a.datas += [('assets\\ytdl.png', '.\\assets\\ytdl.png', 'DATA')]

pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
a.binaries,
name='YouTube Video & Audio Downloader',
debug=False,
strip=None,
upx=True,
console=False,
icon='assets\\ytdl.ico')
Loading

0 comments on commit 5d26570

Please sign in to comment.