QTube 2.3.0__py3-none-any.whl → 2.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- QTube/scripts/qtube.py +36 -2
- QTube/utils/checks.py +18 -1
- QTube/utils/helpers.py +6 -6
- QTube/utils/parsing.py +17 -3
- QTube/utils/youtube/channels.py +2 -4
- QTube/utils/youtube/playlists.py +4 -2
- QTube/utils/youtube/videos.py +85 -13
- {QTube-2.3.0.dist-info → QTube-2.4.0.dist-info}/LICENSE.txt +1 -1
- {QTube-2.3.0.dist-info → QTube-2.4.0.dist-info}/METADATA +12 -2
- QTube-2.4.0.dist-info/RECORD +17 -0
- QTube-2.3.0.dist-info/RECORD +0 -17
- {QTube-2.3.0.dist-info → QTube-2.4.0.dist-info}/WHEEL +0 -0
- {QTube-2.3.0.dist-info → QTube-2.4.0.dist-info}/entry_points.txt +0 -0
- {QTube-2.3.0.dist-info → QTube-2.4.0.dist-info}/top_level.txt +0 -0
QTube/scripts/qtube.py
CHANGED
|
@@ -127,7 +127,7 @@ def main():
|
|
|
127
127
|
["internal"],
|
|
128
128
|
)
|
|
129
129
|
QTube.utils.helpers.print2(
|
|
130
|
-
f"The following verbosity options are enabled: {verb}.\n",
|
|
130
|
+
f"The following verbosity options are enabled: {', '.join(verb)}.\n",
|
|
131
131
|
fancy,
|
|
132
132
|
"info",
|
|
133
133
|
["internal"],
|
|
@@ -383,6 +383,8 @@ def main():
|
|
|
383
383
|
vid_dicts = partial["items"]
|
|
384
384
|
responses["items"].extend(vid_dicts)
|
|
385
385
|
|
|
386
|
+
video_IDs_lst = [vid["id"] for vid in responses["items"]]
|
|
387
|
+
|
|
386
388
|
# Titles retrieving
|
|
387
389
|
titles = QTube.utils.youtube.videos.get_titles(response=responses)
|
|
388
390
|
|
|
@@ -390,7 +392,9 @@ def main():
|
|
|
390
392
|
durations = QTube.utils.youtube.videos.get_durations(response=responses)
|
|
391
393
|
|
|
392
394
|
# Shorts retrieving
|
|
393
|
-
shorts = QTube.utils.youtube.videos.is_short(
|
|
395
|
+
shorts = QTube.utils.youtube.videos.is_short(
|
|
396
|
+
response=responses, video_IDs=video_IDs_lst
|
|
397
|
+
)
|
|
394
398
|
|
|
395
399
|
# Languages retrieving
|
|
396
400
|
languages = QTube.utils.youtube.videos.get_languages(response=responses)
|
|
@@ -429,6 +433,14 @@ def main():
|
|
|
429
433
|
comment_counts, view_counts
|
|
430
434
|
)
|
|
431
435
|
|
|
436
|
+
# Paid promotions retrieving
|
|
437
|
+
paid_advertising = QTube.utils.youtube.videos.has_paid_advertising(
|
|
438
|
+
response=responses
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Made for Kids retrieving
|
|
442
|
+
made_for_kids = QTube.utils.youtube.videos.is_made_for_kids(response=responses)
|
|
443
|
+
|
|
432
444
|
# Resolutions retrieving (does not use YT API)
|
|
433
445
|
lowest_resolution = user_params_dict.get("lowest_resolution")
|
|
434
446
|
if lowest_resolution is not None:
|
|
@@ -503,6 +515,12 @@ def main():
|
|
|
503
515
|
# Comments/views
|
|
504
516
|
vid_info.update({"comments_to_views_ratio": comments_to_views_ratio[index]})
|
|
505
517
|
|
|
518
|
+
# Paid promotions
|
|
519
|
+
vid_info.update({"has_paid_ad": paid_advertising[index]})
|
|
520
|
+
|
|
521
|
+
# Made for kids
|
|
522
|
+
vid_info.update({"made_for_kids": made_for_kids[index]})
|
|
523
|
+
|
|
506
524
|
# Resolutions
|
|
507
525
|
if lowest_resolution is not None:
|
|
508
526
|
vid_info.update({"resolutions": resolutions[index]})
|
|
@@ -661,6 +679,22 @@ def main():
|
|
|
661
679
|
if vid_ID in new_vid_IDs:
|
|
662
680
|
videos[vid_ID].update({"to add": False})
|
|
663
681
|
|
|
682
|
+
# Paid advertisement filtering
|
|
683
|
+
if user_params_dict["allow_paid_promotions"] is False:
|
|
684
|
+
for vid_ID, vid_info in videos.items():
|
|
685
|
+
if vid_info["to add"] is False:
|
|
686
|
+
continue
|
|
687
|
+
elif vid_info["has_paid_ad"]:
|
|
688
|
+
vid_info.update({"to add": False})
|
|
689
|
+
|
|
690
|
+
# Made for kids filtering
|
|
691
|
+
if user_params_dict["only_made_for_kids"] is True:
|
|
692
|
+
for vid_ID, vid_info in videos.items():
|
|
693
|
+
if vid_info["to add"] is False:
|
|
694
|
+
continue
|
|
695
|
+
elif not vid_info["made_for_kids"]:
|
|
696
|
+
vid_info.update({"to add": False})
|
|
697
|
+
|
|
664
698
|
# Language filtering
|
|
665
699
|
if preferred_languages is not None:
|
|
666
700
|
preferred_languages.append("unknown")
|
QTube/utils/checks.py
CHANGED
|
@@ -349,6 +349,8 @@ def check_user_params(params_dict: dict) -> bool:
|
|
|
349
349
|
# Comments/views ratio
|
|
350
350
|
isinstance(params_dict.get("comments_to_views_ratio"), (int, float))
|
|
351
351
|
and 0 <= params_dict.get("comments_to_views_ratio") <= 1,
|
|
352
|
+
# Paid promotions
|
|
353
|
+
isinstance(params_dict.get("allow_paid_promotions"), bool),
|
|
352
354
|
]
|
|
353
355
|
|
|
354
356
|
ok = all(checks)
|
|
@@ -419,7 +421,7 @@ def check_version() -> tuple[str]:
|
|
|
419
421
|
return version, latest_release
|
|
420
422
|
|
|
421
423
|
|
|
422
|
-
def compare_software_versions(version1, version2):
|
|
424
|
+
def compare_software_versions(version1, version2) -> str:
|
|
423
425
|
"""Compare two software versions, using version2 as the reference against which version1 is compared.
|
|
424
426
|
|
|
425
427
|
Args:
|
|
@@ -455,3 +457,18 @@ def compare_software_versions(version1, version2):
|
|
|
455
457
|
return "older"
|
|
456
458
|
|
|
457
459
|
return "same"
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def check_URL_redirect(url: str, redirect_code: int) -> bool:
|
|
463
|
+
"""Checks if the provided URL redirects to another page.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
url (str): URL to check for redirection
|
|
467
|
+
redirect_code (int): Status code to check for (3xx)
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
(bool): True if the URL redirects to another page with the correct status code, False otherwise.
|
|
471
|
+
"""
|
|
472
|
+
r = requests.get(url)
|
|
473
|
+
|
|
474
|
+
return any([resp.status_code == redirect_code for resp in r.history])
|
QTube/utils/helpers.py
CHANGED
|
@@ -65,7 +65,7 @@ def handle_http_errors(verbosity: list[str], fancy, func, *args, **kwargs):
|
|
|
65
65
|
sys.exit() # Exit the program after 5 retries
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def fancify_text(text, color, style, emoji):
|
|
68
|
+
def fancify_text(text, color, style, emoji) -> str:
|
|
69
69
|
"""Modifies the color and content of a string.
|
|
70
70
|
|
|
71
71
|
Args:
|
|
@@ -161,7 +161,7 @@ def merge_dicts(list_of_dicts: list) -> dict:
|
|
|
161
161
|
return {key: value for d in list_of_dicts for key, value in d.items()}
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
def strip_emojis(text):
|
|
164
|
+
def strip_emojis(text) -> str:
|
|
165
165
|
"""Strips emojis from a string.
|
|
166
166
|
|
|
167
167
|
Args:
|
|
@@ -190,7 +190,7 @@ def strip_emojis(text):
|
|
|
190
190
|
return remove_multiple_spaces(clean_text)
|
|
191
191
|
|
|
192
192
|
|
|
193
|
-
def strip_punctuation(text):
|
|
193
|
+
def strip_punctuation(text) -> str:
|
|
194
194
|
"""Strips punctuation from a string (all of these characters: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~).
|
|
195
195
|
|
|
196
196
|
Args:
|
|
@@ -204,7 +204,7 @@ def strip_punctuation(text):
|
|
|
204
204
|
return remove_multiple_spaces(clean_text)
|
|
205
205
|
|
|
206
206
|
|
|
207
|
-
def make_lowercase(text):
|
|
207
|
+
def make_lowercase(text) -> str:
|
|
208
208
|
"""Converts all uppercase letters to lowercase from a string.
|
|
209
209
|
|
|
210
210
|
Args:
|
|
@@ -217,7 +217,7 @@ def make_lowercase(text):
|
|
|
217
217
|
return text.lower()
|
|
218
218
|
|
|
219
219
|
|
|
220
|
-
def remove_multiple_spaces(text):
|
|
220
|
+
def remove_multiple_spaces(text) -> str:
|
|
221
221
|
"""Removes multiple spaces in a string and replaces them with a single space.
|
|
222
222
|
|
|
223
223
|
Args:
|
|
@@ -229,7 +229,7 @@ def remove_multiple_spaces(text):
|
|
|
229
229
|
return re.sub(" +", " ", text)
|
|
230
230
|
|
|
231
231
|
|
|
232
|
-
def divide_lists(list1, list2, percentage: False):
|
|
232
|
+
def divide_lists(list1, list2, percentage: False) -> list[int | float]:
|
|
233
233
|
"""Divides two python lists element-wise.
|
|
234
234
|
|
|
235
235
|
Args:
|
QTube/utils/parsing.py
CHANGED
|
@@ -3,7 +3,7 @@ import json
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def parse_arguments():
|
|
6
|
+
def parse_arguments() -> dict:
|
|
7
7
|
"""Parses command line arguments.
|
|
8
8
|
|
|
9
9
|
Args:
|
|
@@ -273,6 +273,20 @@ def parse_arguments():
|
|
|
273
273
|
help="Determines whether to add shorts. Default: True",
|
|
274
274
|
)
|
|
275
275
|
|
|
276
|
+
parser.add_argument(
|
|
277
|
+
"-app",
|
|
278
|
+
"--allow_paid_promotions",
|
|
279
|
+
action="store_false",
|
|
280
|
+
help="Allow videos containing paid advertising. Default: True",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
parser.add_argument(
|
|
284
|
+
"-mfk",
|
|
285
|
+
"--only_made_for_kids",
|
|
286
|
+
action="store_true",
|
|
287
|
+
help="Determines whether to only add videos that are made for kids, based on Youtube and FTC guidelines. Default: False",
|
|
288
|
+
)
|
|
289
|
+
|
|
276
290
|
parser.add_argument(
|
|
277
291
|
"-kd",
|
|
278
292
|
"--keep_duplicates",
|
|
@@ -308,7 +322,7 @@ def parse_arguments():
|
|
|
308
322
|
return vars(parser.parse_args())
|
|
309
323
|
|
|
310
324
|
|
|
311
|
-
def format_arguments(args_dict):
|
|
325
|
+
def format_arguments(args_dict) -> dict:
|
|
312
326
|
"""Formats the parsed command line arguments (written with the help of AI, regex is witchcraft to me).
|
|
313
327
|
|
|
314
328
|
Args:
|
|
@@ -346,7 +360,7 @@ def format_arguments(args_dict):
|
|
|
346
360
|
return args_dict
|
|
347
361
|
|
|
348
362
|
|
|
349
|
-
def format_arguments_legacy(args_dict):
|
|
363
|
+
def format_arguments_legacy(args_dict) -> dict:
|
|
350
364
|
"""Formats the parsed command line arguments (legacy function).
|
|
351
365
|
|
|
352
366
|
Args:
|
QTube/utils/youtube/channels.py
CHANGED
|
@@ -36,7 +36,7 @@ def get_subscriptions(youtube, next_page_token=None) -> dict:
|
|
|
36
36
|
return channels
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def get_channel_info(youtube, handle: str):
|
|
39
|
+
def get_channel_info(youtube, handle: str) -> dict:
|
|
40
40
|
"""Retrieves basic information about a YT channel.
|
|
41
41
|
|
|
42
42
|
Args:
|
|
@@ -49,9 +49,7 @@ def get_channel_info(youtube, handle: str):
|
|
|
49
49
|
channel = {}
|
|
50
50
|
|
|
51
51
|
response = (
|
|
52
|
-
youtube.channels()
|
|
53
|
-
.list(part="snippet", forHandle=handle)
|
|
54
|
-
.execute(num_retries=5)
|
|
52
|
+
youtube.channels().list(part="snippet", forHandle=handle).execute(num_retries=5)
|
|
55
53
|
)
|
|
56
54
|
|
|
57
55
|
if "items" in response.keys():
|
QTube/utils/youtube/playlists.py
CHANGED
|
@@ -66,7 +66,7 @@ def get_playlist_content(youtube, playlist_ID: str) -> list[str]:
|
|
|
66
66
|
return videos_IDs
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def get_playlists_titles(youtube=None, playlist_IDs: list[str] = None):
|
|
69
|
+
def get_playlists_titles(youtube=None, playlist_IDs: list[str] = None) -> list[str]:
|
|
70
70
|
"""Retrieves the titles of a list of YT playlists.
|
|
71
71
|
|
|
72
72
|
Args:
|
|
@@ -88,7 +88,9 @@ def get_playlists_titles(youtube=None, playlist_IDs: list[str] = None):
|
|
|
88
88
|
return titles
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def get_playlists_video_counts(
|
|
91
|
+
def get_playlists_video_counts(
|
|
92
|
+
youtube=None, playlist_IDs: list[str] = None
|
|
93
|
+
) -> list[int]:
|
|
92
94
|
"""Retrieves the number of videos of a list of YT playlists.
|
|
93
95
|
|
|
94
96
|
Args:
|
QTube/utils/youtube/videos.py
CHANGED
|
@@ -2,7 +2,7 @@ import isodate
|
|
|
2
2
|
|
|
3
3
|
from pytube import YouTube
|
|
4
4
|
|
|
5
|
-
from QTube.utils import helpers
|
|
5
|
+
from QTube.utils import helpers, checks
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def make_video_requests(youtube, video_IDs: list[str]) -> dict:
|
|
@@ -18,7 +18,10 @@ def make_video_requests(youtube, video_IDs: list[str]) -> dict:
|
|
|
18
18
|
video_IDs_str = ",".join(video_IDs)
|
|
19
19
|
response = (
|
|
20
20
|
youtube.videos()
|
|
21
|
-
.list(
|
|
21
|
+
.list(
|
|
22
|
+
part="snippet,contentDetails,statistics,paidProductPlacementDetails,status",
|
|
23
|
+
id=video_IDs_str,
|
|
24
|
+
)
|
|
22
25
|
.execute(num_retries=5)
|
|
23
26
|
)
|
|
24
27
|
return response
|
|
@@ -345,7 +348,7 @@ def get_view_counts(
|
|
|
345
348
|
response: dict = None,
|
|
346
349
|
video_IDs: list[str] = None,
|
|
347
350
|
use_API: bool = False,
|
|
348
|
-
) -> list[
|
|
351
|
+
) -> list[int]:
|
|
349
352
|
"""Retrieves the number of views of a list of YT videos.
|
|
350
353
|
|
|
351
354
|
Args:
|
|
@@ -375,7 +378,7 @@ def get_like_counts(
|
|
|
375
378
|
response: dict = None,
|
|
376
379
|
video_IDs: list[str] = None,
|
|
377
380
|
use_API: bool = False,
|
|
378
|
-
) -> list[
|
|
381
|
+
) -> list[int]:
|
|
379
382
|
"""Retrieves the number of likes of a list of YT videos.
|
|
380
383
|
|
|
381
384
|
Args:
|
|
@@ -405,7 +408,7 @@ def get_comment_counts(
|
|
|
405
408
|
response: dict = None,
|
|
406
409
|
video_IDs: list[str] = None,
|
|
407
410
|
use_API: bool = False,
|
|
408
|
-
) -> list[
|
|
411
|
+
) -> list[int]:
|
|
409
412
|
"""Retrieves the number of comments of a list of YT videos.
|
|
410
413
|
|
|
411
414
|
Args:
|
|
@@ -439,7 +442,7 @@ def get_likes_to_views_ratio(
|
|
|
439
442
|
response: dict = None,
|
|
440
443
|
video_IDs: list[str] = None,
|
|
441
444
|
use_API: bool = False,
|
|
442
|
-
) -> list[
|
|
445
|
+
) -> list[int | float]:
|
|
443
446
|
"""Retrieves the likes to views ratio of a list of YT videos.
|
|
444
447
|
|
|
445
448
|
Args:
|
|
@@ -469,7 +472,7 @@ def get_comments_to_views_ratio(
|
|
|
469
472
|
response: dict = None,
|
|
470
473
|
video_IDs: list[str] = None,
|
|
471
474
|
use_API: bool = False,
|
|
472
|
-
) -> list[
|
|
475
|
+
) -> list[int | float]:
|
|
473
476
|
"""Retrieves the comments to views ratio of a list of YT videos.
|
|
474
477
|
|
|
475
478
|
Args:
|
|
@@ -526,8 +529,8 @@ def is_short(
|
|
|
526
529
|
response: dict = None,
|
|
527
530
|
video_IDs: list[str] = None,
|
|
528
531
|
use_API: bool = False,
|
|
529
|
-
) -> list[
|
|
530
|
-
"""Determines if videos are a short or not by putting a threshold on video duration.
|
|
532
|
+
) -> list[bool]:
|
|
533
|
+
"""Determines if videos are a short or not by putting a threshold on video duration and checking for a redirection at the youtube.com/shorts/*vid_ID* URL.
|
|
531
534
|
|
|
532
535
|
Args:
|
|
533
536
|
youtube (Resource): YT API resource.
|
|
@@ -536,13 +539,21 @@ def is_short(
|
|
|
536
539
|
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
537
540
|
|
|
538
541
|
Returns:
|
|
539
|
-
|
|
542
|
+
is_a_short (list[bool]): True if the video is shorter than 181 seconds and there is no URL redirection, False otherwise.
|
|
540
543
|
"""
|
|
541
544
|
durations = get_durations(youtube, response, video_IDs, use_API=use_API)
|
|
542
545
|
|
|
543
|
-
|
|
546
|
+
is_short_vid = [
|
|
547
|
+
True if length <= 181 else False for length in durations
|
|
548
|
+
] # Shorts cannot last over 3 minutes.
|
|
544
549
|
|
|
545
|
-
|
|
550
|
+
is_not_redirected = [
|
|
551
|
+
not checks.check_URL_redirect("https://www.youtube.com/shorts/" + vid_ID, 303)
|
|
552
|
+
for vid_ID in video_IDs
|
|
553
|
+
] # Shorts do not trigger a redirection.
|
|
554
|
+
|
|
555
|
+
is_a_short = is_short_vid and is_not_redirected
|
|
556
|
+
return is_a_short
|
|
546
557
|
|
|
547
558
|
|
|
548
559
|
def is_live(
|
|
@@ -550,7 +561,7 @@ def is_live(
|
|
|
550
561
|
response: dict = None,
|
|
551
562
|
video_IDs: list[str] = None,
|
|
552
563
|
use_API: bool = False,
|
|
553
|
-
):
|
|
564
|
+
) -> list[str]:
|
|
554
565
|
"""Retrieves the live status of YT videos.
|
|
555
566
|
|
|
556
567
|
Args:
|
|
@@ -575,3 +586,64 @@ def is_live(
|
|
|
575
586
|
]
|
|
576
587
|
|
|
577
588
|
return live_statuses
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def has_paid_advertising(
|
|
592
|
+
youtube=None,
|
|
593
|
+
response: dict = None,
|
|
594
|
+
video_IDs: list[str] = None,
|
|
595
|
+
use_API: bool = False,
|
|
596
|
+
)-> list[bool]:
|
|
597
|
+
"""Determines if a video contains paid advertising.
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
youtube (Resource): YT API resource.
|
|
601
|
+
response (dict[dict]): YT API response from the make_video_request function.
|
|
602
|
+
video_IDs (list[str]): List of video IDs.
|
|
603
|
+
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
(list[bool]): True if the video contains paid advertising, False otherwise.
|
|
607
|
+
"""
|
|
608
|
+
|
|
609
|
+
if use_API:
|
|
610
|
+
video_IDs_str = ",".join(video_IDs)
|
|
611
|
+
response = (
|
|
612
|
+
youtube.videos()
|
|
613
|
+
.list(part="paidProductPlacementDetails", id=video_IDs_str)
|
|
614
|
+
.execute(num_retries=5)
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
return [
|
|
618
|
+
vid["paidProductPlacementDetails"]["hasPaidProductPlacement"]
|
|
619
|
+
for vid in response["items"]
|
|
620
|
+
]
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def is_made_for_kids(
|
|
624
|
+
youtube=None,
|
|
625
|
+
response: dict = None,
|
|
626
|
+
video_IDs: list[str] = None,
|
|
627
|
+
use_API: bool = False,
|
|
628
|
+
)-> list[bool]:
|
|
629
|
+
"""Determines if a video is appropriate for children (based on YT's guidelines).
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
youtube (Resource): YT API resource.
|
|
633
|
+
response (dict[dict]): YT API response from the make_video_request function.
|
|
634
|
+
video_IDs (list[str]): List of video IDs.
|
|
635
|
+
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
(list(bool)): True if the video is made for kids, False otherwise.
|
|
639
|
+
"""
|
|
640
|
+
|
|
641
|
+
if use_API:
|
|
642
|
+
video_IDs_str = ",".join(video_IDs)
|
|
643
|
+
response = (
|
|
644
|
+
youtube.videos()
|
|
645
|
+
.list(part="status", id=video_IDs_str)
|
|
646
|
+
.execute(num_retries=5)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
return [vid["status"]["madeForKids"] for vid in response["items"]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: QTube
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Automatically add Youtube videos to a playlist.
|
|
5
5
|
Home-page: https://github.com/Killian42/QTube
|
|
6
6
|
Author: Killian Lebreton
|
|
@@ -8,7 +8,7 @@ Author-email: Killian Lebreton <killian.lebreton35@gmail.com>
|
|
|
8
8
|
Maintainer-email: Killian Lebreton <killian.lebreton35@gmail.com>
|
|
9
9
|
License: MIT License
|
|
10
10
|
|
|
11
|
-
Copyright (c) [
|
|
11
|
+
Copyright (c) [2025] [Killian Lebreton]
|
|
12
12
|
|
|
13
13
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
14
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -104,6 +104,8 @@ Each of these rules is based on putting some kind of constraint on video propert
|
|
|
104
104
|
* Quality filtering
|
|
105
105
|
* Upload date filtering
|
|
106
106
|
* Shorts filtering
|
|
107
|
+
* Paid promotion filtering
|
|
108
|
+
* Made for Kids filtering
|
|
107
109
|
* Duplicate checking
|
|
108
110
|
|
|
109
111
|
## How to use
|
|
@@ -153,6 +155,8 @@ For more versatile uses, you can also use command line arguments with the [qtube
|
|
|
153
155
|
|`comments_to_views_ratio`|No|Minimum comments to views ratio.|Positive float between 0 & 1|
|
|
154
156
|
|`run_frequency`|No|Defines the duration, in days, of the timeframe considered by the software. Can be interpreted as the frequency the program should be run.|*daily*, *weekly*, *monthly* or any positive integer|
|
|
155
157
|
|`keep_shorts`|No|Determines whether to add shorts.|boolean|
|
|
158
|
+
|`allow_paid_promotions`|No|Determines whether to add videos containing paid advertisement.|boolean|
|
|
159
|
+
|`only_made_for_kids`|No|Determines whether to only add videos that are *Made for Kids* (based on [Youtube and FTC guidelines](https://support.google.com/youtube/answer/9528076)).|boolean|
|
|
156
160
|
|`keep_duplicates`|No|Determines whether to add videos that are already in the playlist.|boolean|
|
|
157
161
|
|`upload_playlist_ID`|No|ID of the playlist the videos will be added to. Playlist IDs are found at the end of their URL: `https://www.youtube.com/playlist?list=*playlist_ID*`|Playlist ID|
|
|
158
162
|
|`override_json`|No|Allow command line arguments to override user_params.json parameters.|boolean|
|
|
@@ -206,6 +210,8 @@ The following *user_params.json* file would add every new videos from channels y
|
|
|
206
210
|
"comments_to_views_ratio": 0,
|
|
207
211
|
"run_frequency":"daily",
|
|
208
212
|
"keep_shorts": true,
|
|
213
|
+
"allow_paid_promotions": true,
|
|
214
|
+
"only_made_for_kids": false,
|
|
209
215
|
"keep_duplicates": false,
|
|
210
216
|
"upload_playlist_ID": "your_playlist_ID",
|
|
211
217
|
"override_json":false,
|
|
@@ -248,6 +254,8 @@ The following *user_params.json* file would only add videos with good quality.
|
|
|
248
254
|
"comments_to_views_ratio": 0,
|
|
249
255
|
"run_frequency":"daily",
|
|
250
256
|
"keep_shorts": true,
|
|
257
|
+
"allow_paid_promotions": true,
|
|
258
|
+
"only_made_for_kids": false,
|
|
251
259
|
"keep_duplicates": false,
|
|
252
260
|
"upload_playlist_ID": "your_playlist_ID",
|
|
253
261
|
"override_json":false,
|
|
@@ -290,6 +298,8 @@ The following *user_params.json* file would only add the *$1 vs.* MrBeast videos
|
|
|
290
298
|
"comments_to_views_ratio": 0,
|
|
291
299
|
"run_frequency":"daily",
|
|
292
300
|
"keep_shorts": false,
|
|
301
|
+
"allow_paid_promotions": true,
|
|
302
|
+
"only_made_for_kids": false,
|
|
293
303
|
"keep_duplicates": false,
|
|
294
304
|
"upload_playlist_ID": "your_playlist_ID",
|
|
295
305
|
"override_json":false,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
QTube/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
QTube/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
QTube/scripts/qtube.py,sha256=7sr4UnaNtxjUAK_cxW9_fgLzW3jMD-kCkBKJXmXh0_o,35108
|
|
4
|
+
QTube/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
QTube/utils/checks.py,sha256=EV-oV89Ufd02fAzsT68wyVCziSMlnbo1NR1XiNqGa0k,13159
|
|
6
|
+
QTube/utils/helpers.py,sha256=fl1_fePwy7ot-KRFB9Ieq7fMFtD8Q4TriK8cW17qPy0,9187
|
|
7
|
+
QTube/utils/parsing.py,sha256=LQ7onVUyqef9bQOS54YNixOHdJGkfydsazP7ijQQoNA,10789
|
|
8
|
+
QTube/utils/youtube/captions.py,sha256=0jUs8SH4L4d2RTS4QHJ5J2Zd9qe3SOHf9VZW966NuY8,1591
|
|
9
|
+
QTube/utils/youtube/channels.py,sha256=_IITSU3tZJLqrJkdQOLGAwTyrrsZb-WywRq2LyqXVBQ,3331
|
|
10
|
+
QTube/utils/youtube/playlists.py,sha256=kMWeaxGp09J3IY6UWpB7qKY7peTpIBlJkMUDb7CDIpM,3874
|
|
11
|
+
QTube/utils/youtube/videos.py,sha256=YWhpLQVADr95lp1XMgnkg5z05YOxtBgpEauUz95zMeQ,20607
|
|
12
|
+
QTube-2.4.0.dist-info/LICENSE.txt,sha256=cIZNbD-BZYZPzWYHhtE-iUCasUxQIwWzALL9nZh32pQ,1098
|
|
13
|
+
QTube-2.4.0.dist-info/METADATA,sha256=SyyF5njBo11yORDL9_UBmRSGlni982O2ZNgyXiP-G28,17798
|
|
14
|
+
QTube-2.4.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
15
|
+
QTube-2.4.0.dist-info/entry_points.txt,sha256=Q3SLDyRahuzrNnHC3UOkMH5gM_Um3eCLiLG4vSTPjqE,51
|
|
16
|
+
QTube-2.4.0.dist-info/top_level.txt,sha256=z6oXrT8BiTTZuygjsbfe-NE4rmkHlDK8euAL9m6BT4A,6
|
|
17
|
+
QTube-2.4.0.dist-info/RECORD,,
|
QTube-2.3.0.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
QTube/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
QTube/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
QTube/scripts/qtube.py,sha256=wzEkhjGgmRFtN9kNBZfqd_6rklX11hwzadh_DAQGZpM,33916
|
|
4
|
-
QTube/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
QTube/utils/checks.py,sha256=wq__Xj_NEZa9tjliRTh-kZcHHbll0rqdjBXwIfZso9E,12569
|
|
6
|
-
QTube/utils/helpers.py,sha256=cOhy4hauguO5-eJK9pK7NvPOtF5ZWp4zfFipA-DY1QM,9131
|
|
7
|
-
QTube/utils/parsing.py,sha256=Q9GnSZSKwX7-AVQDlIoXlQ4xIgQIwMZfOIKXZWi7xSk,10359
|
|
8
|
-
QTube/utils/youtube/captions.py,sha256=0jUs8SH4L4d2RTS4QHJ5J2Zd9qe3SOHf9VZW966NuY8,1591
|
|
9
|
-
QTube/utils/youtube/channels.py,sha256=8K-5_Ff8zHyKhEM02cdwMEV6HLU14MngxI1v2yJh6iI,3343
|
|
10
|
-
QTube/utils/youtube/playlists.py,sha256=Cdohhb2M4bxKiTnZ-R-0xNXPV050nhhI6hepvtDVpIM,3840
|
|
11
|
-
QTube/utils/youtube/videos.py,sha256=lHl-MrloJODCbGD38lwrPQmyEE8t-ZvLA0B4gfMSG0c,18159
|
|
12
|
-
QTube-2.3.0.dist-info/LICENSE.txt,sha256=cctyZoZUseENxkeq5p_C5oCFYpK0-ECku_sCZmWbL0E,1098
|
|
13
|
-
QTube-2.3.0.dist-info/METADATA,sha256=Ftm4t0X_hgyJelTS1v46YehDTPbZglWR5Hp4d3iMIio,17265
|
|
14
|
-
QTube-2.3.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
15
|
-
QTube-2.3.0.dist-info/entry_points.txt,sha256=Q3SLDyRahuzrNnHC3UOkMH5gM_Um3eCLiLG4vSTPjqE,51
|
|
16
|
-
QTube-2.3.0.dist-info/top_level.txt,sha256=z6oXrT8BiTTZuygjsbfe-NE4rmkHlDK8euAL9m6BT4A,6
|
|
17
|
-
QTube-2.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|