Skip to content

Commit 32030d6

Browse files
committed
fix: playlist download methods.
pyutube/cli.py The handle_playlist function has been modified to check if any videos in the playlist have already been downloaded. If so, it removes them from the download queue. pyutube/downloader.py The Downloader class has been modified to handle cases where the specified quality is not available. It now falls back to the best available quality if the specified quality is not found. requirements.txt The pytubefix version has been updated from 5.4.2 to 5.6.3.
1 parent 4b9d45b commit 32030d6

File tree

5 files changed

+88
-15
lines changed

5 files changed

+88
-15
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Pyutube Changelog
22

3+
## 1.2.8
4+
5+
- Add: if you download half of the playlist, you can resume the others without having to download them again.
6+
It will check the root directory for the file, and if the playlist folder is found it will see its content and remove all the files that already downloaded from the download queue.
7+
38
## 1.2.6
49

510
- Fix: switch into pytubefix instead of pytube.

pyutube/cli.py

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import os
4141
import sys
42+
import re
4243

4344
import typer
4445
import threading
@@ -190,13 +191,10 @@ def handle_playlist(url: str, path: str):
190191
Returns:
191192
None
192193
"""
193-
def get_title(video):
194-
"""Function to get the title of a YouTube video."""
195-
return video.title
196194

197195
def fetch_title_thread(video):
198196
"""Fetch the title of a YouTube video in a separate thread."""
199-
video_title = video.title
197+
video_title = safe_filename(video.title)
200198
video_id = video.video_id
201199
playlist_videos.append((video_title, video_id))
202200

@@ -229,19 +227,77 @@ def fetch_title_thread(video):
229227

230228
# Now all video titles are stored in the video_titles list
231229
console.print(f"\nPlaylist title: {title}\n", style="info")
232-
console.print(f"Total videos: {total}\n\n", style="info")
233-
console.print("Chose what video you want to download")
234-
235-
videos_selected = ask_playlist_video_names(playlist_videos)
230+
console.print(f"Total videos: {total}\n", style="info")
236231

237232
os.makedirs(title, exist_ok=True)
238233
new_path = os.path.join(path, title)
239234

235+
# check if there is any video already downloaded in the past
236+
for file in os.listdir(new_path):
237+
for video in playlist_videos:
238+
if file.startswith(video):
239+
playlist_videos.remove(video)
240+
# Exit the inner loop since we found a match
241+
break
242+
243+
if not playlist_videos:
244+
console.print(f"All playlist are already downloaded in this directory, see '{
245+
title}' folder", style="info")
246+
sys.exit()
247+
248+
console.print("Chose what video you want to download")
249+
videos_selected = ask_playlist_video_names(playlist_videos)
250+
240251
for index, video_id in enumerate(videos_selected):
241252
url = f"https://www.youtube.com/watch?v={video_id}"
253+
242254
if index == 0:
243255
quality = download(url, new_path, is_audio, is_playlist=False)
244256
continue
245-
246257
download(url, new_path, is_audio,
247-
quality_choice=quality, is_playlist=False)
258+
quality_choice=quality,
259+
is_playlist=False)
260+
261+
262+
def safe_filename(s: str, max_length: int = 255) -> str:
263+
"""Sanitize a string making it safe to use as a filename.
264+
265+
This function was based off the limitations outlined here:
266+
https://en.wikipedia.org/wiki/Filename.
267+
268+
:param str s:
269+
A string to make safe for use as a file name.
270+
:param int max_length:
271+
The maximum filename character length.
272+
:rtype: str
273+
:returns:
274+
A sanitized string.
275+
"""
276+
# Characters in range 0-31 (0x00-0x1F) are not allowed in ntfs filenames.
277+
ntfs_characters = [chr(i) for i in range(31)]
278+
characters = [
279+
r'"',
280+
r"\#",
281+
r"\$",
282+
r"\%",
283+
r"'",
284+
r"\*",
285+
r"\,",
286+
r"\.",
287+
r"\/",
288+
r"\:",
289+
r'"',
290+
r"\;",
291+
r"\<",
292+
r"\>",
293+
r"\?",
294+
r"\\",
295+
r"\^",
296+
r"\|",
297+
r"\~",
298+
r"\\\\",
299+
]
300+
pattern = "|".join(ntfs_characters + characters)
301+
regex = re.compile(pattern, re.UNICODE)
302+
filename = regex.sub("", s)
303+
return filename[:max_length].rsplit(" ", 0)[0]

pyutube/downloader.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,16 @@ def get_video_streams(self, quality: str, streams: YouTube.streams) -> YouTube:
130130
The video stream with the specified quality,
131131
or the best available stream if no match is found.
132132
"""
133-
return streams.filter(res=quality).first()
133+
s = streams.filter(res=quality).first()
134+
135+
if not s:
136+
available_qualities = [stream.resolution for stream in streams]
137+
available_qualities = list(map(int, available_qualities))
138+
selected_quality = min(available_qualities,
139+
key=lambda x: abs(int(quality) - x))
140+
s = streams.filter(res=str(selected_quality)).first()
141+
142+
return s
134143

135144
@yaspin(
136145
text=colored("Downloading the audio...", "green"),

pyutube/utils.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from termcolor import colored
1717

1818

19-
__version__ = "1.2.7"
19+
__version__ = "1.2.8"
2020
__app__ = "pyutube"
2121
ABORTED_PREFIX = "aborted"
2222
CANCEL_PREFIX = "cancel"
@@ -251,12 +251,14 @@ def ask_rename_file(filename: str) -> str:
251251

252252
def ask_playlist_video_names(videos):
253253
note = colored("NOTE:", "cyan")
254+
select_one = colored("<space>", "red")
254255
select_all = colored("<ctrl+a>", "red")
255256
invert_selection = colored("<ctrl+i>", "red")
256257
restart_selection = colored("<ctrl+r>", "red")
257258

258259
print(
259-
f"{note} Press {select_all} to select all, {invert_selection} to invert selection, and {restart_selection} to restart selection",
260+
f"{note} Press {select_one} to select the videos, {select_all} to select all, {
261+
invert_selection} to invert selection, and {restart_selection} to restart selection",
260262
)
261263
questions = [
262264
inquirer.Checkbox(
@@ -348,7 +350,8 @@ def check_for_updates() -> None:
348350

349351
if latest_version != __version__:
350352
console.print(
351-
f"👉 A new version of {__app__} is available: {latest_version}. Update it by running [bold red link=https://github.com/Hetari/pyutube]pip install --upgrade {__app__}[/bold red link]",
353+
f"👉 A new version of {__app__} is available: {
354+
latest_version}. Update it by running [bold red link=https://github.com/Hetari/pyutube]pip install --upgrade {__app__}[/bold red link]",
352355
style="warning"
353356
)
354357
else:

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ typer==0.9.0
44
requests==2.31.0
55
rich==13.7.1
66
yaspin==3.0.1
7-
pytubefix==5.4.2
7+
pytubefix==5.6.3
88
inquirer==3.2.4
99
termcolor==2.4.0
1010
moviepy==1.0.3

0 commit comments

Comments
 (0)