QTube 2.2.0__tar.gz → 2.3.0__tar.gz
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-2.2.0 → qtube-2.3.0}/PKG-INFO +22 -1
- {qtube-2.2.0 → qtube-2.3.0}/QTube/scripts/qtube.py +77 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/checks.py +15 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/helpers.py +26 -4
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/parsing.py +40 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/youtube/videos.py +155 -1
- {qtube-2.2.0 → qtube-2.3.0}/QTube.egg-info/PKG-INFO +22 -1
- {qtube-2.2.0 → qtube-2.3.0}/README.md +21 -0
- {qtube-2.2.0 → qtube-2.3.0}/pyproject.toml +1 -1
- {qtube-2.2.0 → qtube-2.3.0}/setup.py +1 -1
- {qtube-2.2.0 → qtube-2.3.0}/LICENSE.txt +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/MANIFEST.in +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/__init__.py +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/scripts/__init__.py +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/__init__.py +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/youtube/captions.py +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/youtube/channels.py +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube/utils/youtube/playlists.py +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube.egg-info/SOURCES.txt +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube.egg-info/dependency_links.txt +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube.egg-info/entry_points.txt +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube.egg-info/requires.txt +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/QTube.egg-info/top_level.txt +0 -0
- {qtube-2.2.0 → qtube-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: QTube
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -98,6 +98,7 @@ Each of these rules is based on putting some kind of constraint on video propert
|
|
|
98
98
|
* Language filtering
|
|
99
99
|
* Caption filtering
|
|
100
100
|
* Duration filtering
|
|
101
|
+
* Views, likes & comments counts filtering
|
|
101
102
|
* Livestream filtering
|
|
102
103
|
* Premiere filtering
|
|
103
104
|
* Quality filtering
|
|
@@ -145,6 +146,11 @@ For more versatile uses, you can also use command line arguments with the [qtube
|
|
|
145
146
|
|`lowest_framerate`|Yes|Minimum framerate. Videos with framerates stricly lower than this value will not be added.|Positive integer|
|
|
146
147
|
|`preferred_dimensions`|Yes|Dimension the videos need to be in.|*2D*, *3D* or both|
|
|
147
148
|
|`preferred_projections`|Yes|Projection the videos need to be in.|*rectangular*, *360* or both|
|
|
149
|
+
|`views_threshold`|No|Minimum number of times videos have been viewed.|Positive integer|
|
|
150
|
+
|`likes_threshold`|No|Minimum number of times videos have been liked.|Positive integer|
|
|
151
|
+
|`comments_threshold`|No|Minimum number of times videos have been commented on.|Positive integer|
|
|
152
|
+
|`likes_to_views_ratio`|No|Minimum likes to views ratio.|Positive float between 0 & 1|
|
|
153
|
+
|`comments_to_views_ratio`|No|Minimum comments to views ratio.|Positive float between 0 & 1|
|
|
148
154
|
|`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|
|
|
149
155
|
|`keep_shorts`|No|Determines whether to add shorts.|boolean|
|
|
150
156
|
|`keep_duplicates`|No|Determines whether to add videos that are already in the playlist.|boolean|
|
|
@@ -193,6 +199,11 @@ The following *user_params.json* file would add every new videos from channels y
|
|
|
193
199
|
"lowest_framerate": null,
|
|
194
200
|
"preferred_dimensions": null,
|
|
195
201
|
"preferred_projections": null,
|
|
202
|
+
"views_threshold": 0,
|
|
203
|
+
"likes_threshold": 0,
|
|
204
|
+
"comments_threshold": 0,
|
|
205
|
+
"likes_to_views_ratio": 0,
|
|
206
|
+
"comments_to_views_ratio": 0,
|
|
196
207
|
"run_frequency":"daily",
|
|
197
208
|
"keep_shorts": true,
|
|
198
209
|
"keep_duplicates": false,
|
|
@@ -230,6 +241,11 @@ The following *user_params.json* file would only add videos with good quality.
|
|
|
230
241
|
"lowest_framerate": 60,
|
|
231
242
|
"preferred_dimensions": ["2D"],
|
|
232
243
|
"preferred_projections": ["rectangular"],
|
|
244
|
+
"views_threshold": 0,
|
|
245
|
+
"likes_threshold": 0,
|
|
246
|
+
"comments_threshold": 0,
|
|
247
|
+
"likes_to_views_ratio": 0,
|
|
248
|
+
"comments_to_views_ratio": 0,
|
|
233
249
|
"run_frequency":"daily",
|
|
234
250
|
"keep_shorts": true,
|
|
235
251
|
"keep_duplicates": false,
|
|
@@ -267,6 +283,11 @@ The following *user_params.json* file would only add the *$1 vs.* MrBeast videos
|
|
|
267
283
|
"lowest_framerate": null,
|
|
268
284
|
"preferred_dimensions": ["2D"],
|
|
269
285
|
"preferred_projections": ["rectangular"],
|
|
286
|
+
"views_threshold": 0,
|
|
287
|
+
"likes_threshold": 0,
|
|
288
|
+
"comments_threshold": 0,
|
|
289
|
+
"likes_to_views_ratio": 0,
|
|
290
|
+
"comments_to_views_ratio": 0,
|
|
270
291
|
"run_frequency":"daily",
|
|
271
292
|
"keep_shorts": false,
|
|
272
293
|
"keep_duplicates": false,
|
|
@@ -410,6 +410,25 @@ def main():
|
|
|
410
410
|
# Live status retrieving
|
|
411
411
|
live_statuses = QTube.utils.youtube.videos.is_live(response=responses)
|
|
412
412
|
|
|
413
|
+
# View counts retrieving
|
|
414
|
+
view_counts = QTube.utils.youtube.videos.get_view_counts(response=responses)
|
|
415
|
+
|
|
416
|
+
# Like counts retrieving
|
|
417
|
+
like_counts = QTube.utils.youtube.videos.get_like_counts(response=responses)
|
|
418
|
+
|
|
419
|
+
# Comment counts retrieving
|
|
420
|
+
comment_counts = QTube.utils.youtube.videos.get_comment_counts(response=responses)
|
|
421
|
+
|
|
422
|
+
# Likes/views ratio retrieving
|
|
423
|
+
likes_to_views_ratio = QTube.utils.youtube.videos.get_likes_to_views_ratio(
|
|
424
|
+
like_counts, view_counts
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Comments/views ratio retrieving
|
|
428
|
+
comments_to_views_ratio = QTube.utils.youtube.videos.get_likes_to_views_ratio(
|
|
429
|
+
comment_counts, view_counts
|
|
430
|
+
)
|
|
431
|
+
|
|
413
432
|
# Resolutions retrieving (does not use YT API)
|
|
414
433
|
lowest_resolution = user_params_dict.get("lowest_resolution")
|
|
415
434
|
if lowest_resolution is not None:
|
|
@@ -469,6 +488,21 @@ def main():
|
|
|
469
488
|
# Live statuses
|
|
470
489
|
vid_info.update({"live status": live_statuses[index]})
|
|
471
490
|
|
|
491
|
+
# Views
|
|
492
|
+
vid_info.update({"views": view_counts[index]})
|
|
493
|
+
|
|
494
|
+
# Likes
|
|
495
|
+
vid_info.update({"likes": like_counts[index]})
|
|
496
|
+
|
|
497
|
+
# Comments
|
|
498
|
+
vid_info.update({"comments": comment_counts[index]})
|
|
499
|
+
|
|
500
|
+
# Likes/views
|
|
501
|
+
vid_info.update({"likes_to_views_ratio": likes_to_views_ratio[index]})
|
|
502
|
+
|
|
503
|
+
# Comments/views
|
|
504
|
+
vid_info.update({"comments_to_views_ratio": comments_to_views_ratio[index]})
|
|
505
|
+
|
|
472
506
|
# Resolutions
|
|
473
507
|
if lowest_resolution is not None:
|
|
474
508
|
vid_info.update({"resolutions": resolutions[index]})
|
|
@@ -543,6 +577,12 @@ def main():
|
|
|
543
577
|
lowest_definition = user_params_dict.get("lowest_definition")
|
|
544
578
|
preferred_dimensions = user_params_dict.get("preferred_dimensions")
|
|
545
579
|
|
|
580
|
+
views_threshold = user_params_dict.get("views_threshold")
|
|
581
|
+
likes_threshold = user_params_dict.get("likes_threshold")
|
|
582
|
+
comments_threshold = user_params_dict.get("comments_threshold")
|
|
583
|
+
likes_to_views_ratio_threshold = user_params_dict.get("likes_to_views_ratio")
|
|
584
|
+
comments_to_views_ratio_threshold = user_params_dict.get("comments_to_views_ratio")
|
|
585
|
+
|
|
546
586
|
# Duration filtering
|
|
547
587
|
if min_max_durations is not None:
|
|
548
588
|
for vid_info in videos.values():
|
|
@@ -771,6 +811,43 @@ def main():
|
|
|
771
811
|
):
|
|
772
812
|
vid_info.update({"to add": False})
|
|
773
813
|
|
|
814
|
+
# Views filtering
|
|
815
|
+
if views_threshold > 0:
|
|
816
|
+
for vid_ID, vid_info in videos.items():
|
|
817
|
+
if vid_info["to add"] and vid_info["views"] < views_threshold:
|
|
818
|
+
vid_info.update({"to add": False})
|
|
819
|
+
|
|
820
|
+
# Likes Filtering
|
|
821
|
+
if likes_threshold > 0:
|
|
822
|
+
for vid_ID, vid_info in videos.items():
|
|
823
|
+
if vid_info["to add"] and vid_info["likes"] < likes_threshold:
|
|
824
|
+
vid_info.update({"to add": False})
|
|
825
|
+
|
|
826
|
+
# Comments filtering
|
|
827
|
+
if comments_threshold > 0:
|
|
828
|
+
for vid_ID, vid_info in videos.items():
|
|
829
|
+
if vid_info["to add"] and vid_info["comments"] < comments_threshold:
|
|
830
|
+
vid_info.update({"to add": False})
|
|
831
|
+
|
|
832
|
+
# Likes/views ratio filtering
|
|
833
|
+
if likes_to_views_ratio_threshold > 0:
|
|
834
|
+
for vid_ID, vid_info in videos.items():
|
|
835
|
+
if (
|
|
836
|
+
vid_info["to add"]
|
|
837
|
+
and vid_info["likes_to_views_ratio"] < likes_to_views_ratio_threshold
|
|
838
|
+
):
|
|
839
|
+
vid_info.update({"to add": False})
|
|
840
|
+
|
|
841
|
+
# Comments/views ratio filtering
|
|
842
|
+
if comments_to_views_ratio_threshold > 0:
|
|
843
|
+
for vid_ID, vid_info in videos.items():
|
|
844
|
+
if (
|
|
845
|
+
vid_info["to add"]
|
|
846
|
+
and vid_info["comments_to_views_ratio"]
|
|
847
|
+
< comments_to_views_ratio_threshold
|
|
848
|
+
):
|
|
849
|
+
vid_info.update({"to add": False})
|
|
850
|
+
|
|
774
851
|
## Selecting correct videos
|
|
775
852
|
videos_to_add = {
|
|
776
853
|
vid_ID: vid_info for vid_ID, vid_info in videos.items() if vid_info["to add"]
|
|
@@ -334,6 +334,21 @@ def check_user_params(params_dict: dict) -> bool:
|
|
|
334
334
|
isinstance(params_dict.get("ignore_premieres"), bool),
|
|
335
335
|
# Fancy text
|
|
336
336
|
isinstance(params_dict.get("fancy_mode"), bool),
|
|
337
|
+
# Views
|
|
338
|
+
isinstance(params_dict.get("views_threshold"), int)
|
|
339
|
+
and params_dict.get("views_threshold") >= 0,
|
|
340
|
+
# Likes
|
|
341
|
+
isinstance(params_dict.get("likes_threshold"), int)
|
|
342
|
+
and params_dict.get("likes_threshold") >= 0,
|
|
343
|
+
# Comments
|
|
344
|
+
isinstance(params_dict.get("comments_threshold"), int)
|
|
345
|
+
and params_dict.get("comments_threshold") >= 0,
|
|
346
|
+
# Likes/views ratio
|
|
347
|
+
isinstance(params_dict.get("likes_to_views_ratio"), (int, float))
|
|
348
|
+
and 0 <= params_dict.get("likes_to_views_ratio") <= 1,
|
|
349
|
+
# Comments/views ratio
|
|
350
|
+
isinstance(params_dict.get("comments_to_views_ratio"), (int, float))
|
|
351
|
+
and 0 <= params_dict.get("comments_to_views_ratio") <= 1,
|
|
337
352
|
]
|
|
338
353
|
|
|
339
354
|
ok = all(checks)
|
|
@@ -7,7 +7,7 @@ from googleapiclient.errors import HttpError
|
|
|
7
7
|
from colorama import Fore, Style
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def handle_http_errors(verbosity: list[str],fancy, func, *args, **kwargs):
|
|
10
|
+
def handle_http_errors(verbosity: list[str], fancy, func, *args, **kwargs):
|
|
11
11
|
"""Handles http errors when making API queries.
|
|
12
12
|
If after 5 tries, the function could not be executed, it shuts the program down.
|
|
13
13
|
|
|
@@ -27,7 +27,11 @@ def handle_http_errors(verbosity: list[str],fancy, func, *args, **kwargs):
|
|
|
27
27
|
try:
|
|
28
28
|
res = func(*args, **kwargs)
|
|
29
29
|
print2(
|
|
30
|
-
f"{func.__name__} successfully executed.",
|
|
30
|
+
f"{func.__name__} successfully executed.",
|
|
31
|
+
fancy,
|
|
32
|
+
"success",
|
|
33
|
+
["all", "func"],
|
|
34
|
+
verbosity,
|
|
31
35
|
)
|
|
32
36
|
return res # Return the response if no error occurs
|
|
33
37
|
except HttpError as err:
|
|
@@ -100,9 +104,9 @@ def print2(
|
|
|
100
104
|
elif fancy_type == "fail":
|
|
101
105
|
print(fancify_text(message, Fore.RED, Style.BRIGHT, "❌"))
|
|
102
106
|
elif fancy_type == "warning":
|
|
103
|
-
print(fancify_text(message, Fore.YELLOW, Style.
|
|
107
|
+
print(fancify_text(message, Fore.YELLOW, Style.BRIGHT, "⚠️"))
|
|
104
108
|
elif fancy_type == "info":
|
|
105
|
-
print(fancify_text(message, Fore.WHITE, Style.BRIGHT,"📢"))
|
|
109
|
+
print(fancify_text(message, Fore.WHITE, Style.BRIGHT, "📢"))
|
|
106
110
|
elif fancy_type == "video":
|
|
107
111
|
print(fancify_text(message, Fore.BLUE, Style.BRIGHT, "🎞️"))
|
|
108
112
|
else:
|
|
@@ -223,3 +227,21 @@ def remove_multiple_spaces(text):
|
|
|
223
227
|
(str): Same text string, but with multiple spaces replaced by a single space.
|
|
224
228
|
"""
|
|
225
229
|
return re.sub(" +", " ", text)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def divide_lists(list1, list2, percentage: False):
|
|
233
|
+
"""Divides two python lists element-wise.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
list1 (lst[int|float]): Dividend list.
|
|
237
|
+
list2 (lst[inf|float]): Divisor list.
|
|
238
|
+
percentage (bool): Determines if the result of the division is expressed as a percentage.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
res (lst[int|float]): List containing the results of the element-wise division.
|
|
242
|
+
"""
|
|
243
|
+
if percentage:
|
|
244
|
+
res = [(a / b) * 100 if b != 0 else None for a, b in zip(list1, list2)]
|
|
245
|
+
else:
|
|
246
|
+
res = [a / b if b != 0 else None for a, b in zip(list1, list2)]
|
|
247
|
+
return res
|
|
@@ -217,6 +217,46 @@ def parse_arguments():
|
|
|
217
217
|
help="Projection the videos need to be in. Default: None",
|
|
218
218
|
)
|
|
219
219
|
|
|
220
|
+
parser.add_argument(
|
|
221
|
+
"-vt",
|
|
222
|
+
"--views_threshold",
|
|
223
|
+
metavar="",
|
|
224
|
+
type=int,
|
|
225
|
+
help="Minimum number of views. Default: 0",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
parser.add_argument(
|
|
229
|
+
"-lt",
|
|
230
|
+
"--likes_threshold",
|
|
231
|
+
metavar="",
|
|
232
|
+
type=int,
|
|
233
|
+
help="Minimum number of likes. Default: 0",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
parser.add_argument(
|
|
237
|
+
"-ct",
|
|
238
|
+
"--comments_threshold",
|
|
239
|
+
metavar="",
|
|
240
|
+
type=int,
|
|
241
|
+
help="Minimum number of comments. Default: 0",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
parser.add_argument(
|
|
245
|
+
"-lvr",
|
|
246
|
+
"--likes_to_views_ratio",
|
|
247
|
+
metavar="",
|
|
248
|
+
type=float,
|
|
249
|
+
help="Minimum ratio of likes to views. Default: 0",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
parser.add_argument(
|
|
253
|
+
"-cvr",
|
|
254
|
+
"--comments_to_views_ratio",
|
|
255
|
+
metavar="",
|
|
256
|
+
type=float,
|
|
257
|
+
help="Minimum ratio of comments to views. Default: 0",
|
|
258
|
+
)
|
|
259
|
+
|
|
220
260
|
parser.add_argument(
|
|
221
261
|
"-rf",
|
|
222
262
|
"--run_frequency",
|
|
@@ -2,6 +2,8 @@ import isodate
|
|
|
2
2
|
|
|
3
3
|
from pytube import YouTube
|
|
4
4
|
|
|
5
|
+
from QTube.utils import helpers
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
def make_video_requests(youtube, video_IDs: list[str]) -> dict:
|
|
7
9
|
"""Retrieves information on a list of YT videos.
|
|
@@ -16,7 +18,7 @@ def make_video_requests(youtube, video_IDs: list[str]) -> dict:
|
|
|
16
18
|
video_IDs_str = ",".join(video_IDs)
|
|
17
19
|
response = (
|
|
18
20
|
youtube.videos()
|
|
19
|
-
.list(part="snippet,contentDetails", id=video_IDs_str)
|
|
21
|
+
.list(part="snippet,contentDetails,statistics", id=video_IDs_str)
|
|
20
22
|
.execute(num_retries=5)
|
|
21
23
|
)
|
|
22
24
|
return response
|
|
@@ -338,6 +340,158 @@ def get_projections(
|
|
|
338
340
|
return projections
|
|
339
341
|
|
|
340
342
|
|
|
343
|
+
def get_view_counts(
|
|
344
|
+
youtube=None,
|
|
345
|
+
response: dict = None,
|
|
346
|
+
video_IDs: list[str] = None,
|
|
347
|
+
use_API: bool = False,
|
|
348
|
+
) -> list[str]:
|
|
349
|
+
"""Retrieves the number of views of a list of YT videos.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
youtube (Resource): YT API resource.
|
|
353
|
+
response (dict[dict]): YT API response from the make_video_request function.
|
|
354
|
+
video_IDs (list[str]): List of video IDs.
|
|
355
|
+
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
views (list[int]): List of YT videos views.
|
|
359
|
+
"""
|
|
360
|
+
if use_API:
|
|
361
|
+
video_IDs_str = ",".join(video_IDs)
|
|
362
|
+
response = (
|
|
363
|
+
youtube.videos()
|
|
364
|
+
.list(part="statistics", id=video_IDs_str)
|
|
365
|
+
.execute(num_retries=5)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
views = [int(vid["statistics"]["viewCount"]) for vid in response["items"]]
|
|
369
|
+
|
|
370
|
+
return views
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def get_like_counts(
|
|
374
|
+
youtube=None,
|
|
375
|
+
response: dict = None,
|
|
376
|
+
video_IDs: list[str] = None,
|
|
377
|
+
use_API: bool = False,
|
|
378
|
+
) -> list[str]:
|
|
379
|
+
"""Retrieves the number of likes of a list of YT videos.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
youtube (Resource): YT API resource.
|
|
383
|
+
response (dict[dict]): YT API response from the make_video_request function.
|
|
384
|
+
video_IDs (list[str]): List of video IDs.
|
|
385
|
+
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
likes (list[int]): List of YT videos likes.
|
|
389
|
+
"""
|
|
390
|
+
if use_API:
|
|
391
|
+
video_IDs_str = ",".join(video_IDs)
|
|
392
|
+
response = (
|
|
393
|
+
youtube.videos()
|
|
394
|
+
.list(part="statistics", id=video_IDs_str)
|
|
395
|
+
.execute(num_retries=5)
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
likes = [int(vid["statistics"]["likeCount"]) for vid in response["items"]]
|
|
399
|
+
|
|
400
|
+
return likes
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def get_comment_counts(
|
|
404
|
+
youtube=None,
|
|
405
|
+
response: dict = None,
|
|
406
|
+
video_IDs: list[str] = None,
|
|
407
|
+
use_API: bool = False,
|
|
408
|
+
) -> list[str]:
|
|
409
|
+
"""Retrieves the number of comments of a list of YT videos.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
youtube (Resource): YT API resource.
|
|
413
|
+
response (dict[dict]): YT API response from the make_video_request function.
|
|
414
|
+
video_IDs (list[str]): List of video IDs.
|
|
415
|
+
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
comment_counts (list[int]): List of YT videos comment counts.
|
|
419
|
+
"""
|
|
420
|
+
if use_API:
|
|
421
|
+
video_IDs_str = ",".join(video_IDs)
|
|
422
|
+
response = (
|
|
423
|
+
youtube.videos()
|
|
424
|
+
.list(part="statistics", id=video_IDs_str)
|
|
425
|
+
.execute(num_retries=5)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
comment_counts = [
|
|
429
|
+
int(vid["statistics"]["commentCount"]) for vid in response["items"]
|
|
430
|
+
]
|
|
431
|
+
|
|
432
|
+
return comment_counts
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def get_likes_to_views_ratio(
|
|
436
|
+
likes,
|
|
437
|
+
views,
|
|
438
|
+
youtube=None,
|
|
439
|
+
response: dict = None,
|
|
440
|
+
video_IDs: list[str] = None,
|
|
441
|
+
use_API: bool = False,
|
|
442
|
+
) -> list[str]:
|
|
443
|
+
"""Retrieves the likes to views ratio of a list of YT videos.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
likes (list[int]): List of the number of likes.
|
|
447
|
+
views (list[int]): List of the number of views.
|
|
448
|
+
youtube (Resource): YT API resource.
|
|
449
|
+
response (dict[dict]): YT API response from the make_video_request function.
|
|
450
|
+
video_IDs (list[str]): List of video IDs.
|
|
451
|
+
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
ratio (list[int|float]): List of YT videos' likes to views ratios.
|
|
455
|
+
"""
|
|
456
|
+
if use_API:
|
|
457
|
+
views = get_view_counts(youtube, response, video_IDs, use_API)
|
|
458
|
+
likes = get_like_counts(youtube, response, video_IDs, use_API)
|
|
459
|
+
|
|
460
|
+
ratio = helpers.divide_lists(likes, views, False)
|
|
461
|
+
|
|
462
|
+
return ratio
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def get_comments_to_views_ratio(
|
|
466
|
+
likes,
|
|
467
|
+
views,
|
|
468
|
+
youtube=None,
|
|
469
|
+
response: dict = None,
|
|
470
|
+
video_IDs: list[str] = None,
|
|
471
|
+
use_API: bool = False,
|
|
472
|
+
) -> list[str]:
|
|
473
|
+
"""Retrieves the comments to views ratio of a list of YT videos.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
comments (list[int]): List of the number of comments.
|
|
477
|
+
views (list[int]): List of the number of views.
|
|
478
|
+
youtube (Resource): YT API resource.
|
|
479
|
+
response (dict[dict]): YT API response from the make_video_request function.
|
|
480
|
+
video_IDs (list[str]): List of video IDs.
|
|
481
|
+
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
views (list[int|float]): List of YT videos' comments to views ratios.
|
|
485
|
+
"""
|
|
486
|
+
if use_API:
|
|
487
|
+
views = get_view_counts(youtube, response, video_IDs, use_API)
|
|
488
|
+
comments = get_comment_counts(youtube, response, video_IDs, use_API)
|
|
489
|
+
|
|
490
|
+
ratio = helpers.divide_lists(comments, views, False)
|
|
491
|
+
|
|
492
|
+
return ratio
|
|
493
|
+
|
|
494
|
+
|
|
341
495
|
def has_captions(
|
|
342
496
|
youtube=None,
|
|
343
497
|
response: dict = None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: QTube
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -98,6 +98,7 @@ Each of these rules is based on putting some kind of constraint on video propert
|
|
|
98
98
|
* Language filtering
|
|
99
99
|
* Caption filtering
|
|
100
100
|
* Duration filtering
|
|
101
|
+
* Views, likes & comments counts filtering
|
|
101
102
|
* Livestream filtering
|
|
102
103
|
* Premiere filtering
|
|
103
104
|
* Quality filtering
|
|
@@ -145,6 +146,11 @@ For more versatile uses, you can also use command line arguments with the [qtube
|
|
|
145
146
|
|`lowest_framerate`|Yes|Minimum framerate. Videos with framerates stricly lower than this value will not be added.|Positive integer|
|
|
146
147
|
|`preferred_dimensions`|Yes|Dimension the videos need to be in.|*2D*, *3D* or both|
|
|
147
148
|
|`preferred_projections`|Yes|Projection the videos need to be in.|*rectangular*, *360* or both|
|
|
149
|
+
|`views_threshold`|No|Minimum number of times videos have been viewed.|Positive integer|
|
|
150
|
+
|`likes_threshold`|No|Minimum number of times videos have been liked.|Positive integer|
|
|
151
|
+
|`comments_threshold`|No|Minimum number of times videos have been commented on.|Positive integer|
|
|
152
|
+
|`likes_to_views_ratio`|No|Minimum likes to views ratio.|Positive float between 0 & 1|
|
|
153
|
+
|`comments_to_views_ratio`|No|Minimum comments to views ratio.|Positive float between 0 & 1|
|
|
148
154
|
|`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|
|
|
149
155
|
|`keep_shorts`|No|Determines whether to add shorts.|boolean|
|
|
150
156
|
|`keep_duplicates`|No|Determines whether to add videos that are already in the playlist.|boolean|
|
|
@@ -193,6 +199,11 @@ The following *user_params.json* file would add every new videos from channels y
|
|
|
193
199
|
"lowest_framerate": null,
|
|
194
200
|
"preferred_dimensions": null,
|
|
195
201
|
"preferred_projections": null,
|
|
202
|
+
"views_threshold": 0,
|
|
203
|
+
"likes_threshold": 0,
|
|
204
|
+
"comments_threshold": 0,
|
|
205
|
+
"likes_to_views_ratio": 0,
|
|
206
|
+
"comments_to_views_ratio": 0,
|
|
196
207
|
"run_frequency":"daily",
|
|
197
208
|
"keep_shorts": true,
|
|
198
209
|
"keep_duplicates": false,
|
|
@@ -230,6 +241,11 @@ The following *user_params.json* file would only add videos with good quality.
|
|
|
230
241
|
"lowest_framerate": 60,
|
|
231
242
|
"preferred_dimensions": ["2D"],
|
|
232
243
|
"preferred_projections": ["rectangular"],
|
|
244
|
+
"views_threshold": 0,
|
|
245
|
+
"likes_threshold": 0,
|
|
246
|
+
"comments_threshold": 0,
|
|
247
|
+
"likes_to_views_ratio": 0,
|
|
248
|
+
"comments_to_views_ratio": 0,
|
|
233
249
|
"run_frequency":"daily",
|
|
234
250
|
"keep_shorts": true,
|
|
235
251
|
"keep_duplicates": false,
|
|
@@ -267,6 +283,11 @@ The following *user_params.json* file would only add the *$1 vs.* MrBeast videos
|
|
|
267
283
|
"lowest_framerate": null,
|
|
268
284
|
"preferred_dimensions": ["2D"],
|
|
269
285
|
"preferred_projections": ["rectangular"],
|
|
286
|
+
"views_threshold": 0,
|
|
287
|
+
"likes_threshold": 0,
|
|
288
|
+
"comments_threshold": 0,
|
|
289
|
+
"likes_to_views_ratio": 0,
|
|
290
|
+
"comments_to_views_ratio": 0,
|
|
270
291
|
"run_frequency":"daily",
|
|
271
292
|
"keep_shorts": false,
|
|
272
293
|
"keep_duplicates": false,
|
|
@@ -42,6 +42,7 @@ Each of these rules is based on putting some kind of constraint on video propert
|
|
|
42
42
|
* Language filtering
|
|
43
43
|
* Caption filtering
|
|
44
44
|
* Duration filtering
|
|
45
|
+
* Views, likes & comments counts filtering
|
|
45
46
|
* Livestream filtering
|
|
46
47
|
* Premiere filtering
|
|
47
48
|
* Quality filtering
|
|
@@ -89,6 +90,11 @@ For more versatile uses, you can also use command line arguments with the [qtube
|
|
|
89
90
|
|`lowest_framerate`|Yes|Minimum framerate. Videos with framerates stricly lower than this value will not be added.|Positive integer|
|
|
90
91
|
|`preferred_dimensions`|Yes|Dimension the videos need to be in.|*2D*, *3D* or both|
|
|
91
92
|
|`preferred_projections`|Yes|Projection the videos need to be in.|*rectangular*, *360* or both|
|
|
93
|
+
|`views_threshold`|No|Minimum number of times videos have been viewed.|Positive integer|
|
|
94
|
+
|`likes_threshold`|No|Minimum number of times videos have been liked.|Positive integer|
|
|
95
|
+
|`comments_threshold`|No|Minimum number of times videos have been commented on.|Positive integer|
|
|
96
|
+
|`likes_to_views_ratio`|No|Minimum likes to views ratio.|Positive float between 0 & 1|
|
|
97
|
+
|`comments_to_views_ratio`|No|Minimum comments to views ratio.|Positive float between 0 & 1|
|
|
92
98
|
|`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|
|
|
93
99
|
|`keep_shorts`|No|Determines whether to add shorts.|boolean|
|
|
94
100
|
|`keep_duplicates`|No|Determines whether to add videos that are already in the playlist.|boolean|
|
|
@@ -137,6 +143,11 @@ The following *user_params.json* file would add every new videos from channels y
|
|
|
137
143
|
"lowest_framerate": null,
|
|
138
144
|
"preferred_dimensions": null,
|
|
139
145
|
"preferred_projections": null,
|
|
146
|
+
"views_threshold": 0,
|
|
147
|
+
"likes_threshold": 0,
|
|
148
|
+
"comments_threshold": 0,
|
|
149
|
+
"likes_to_views_ratio": 0,
|
|
150
|
+
"comments_to_views_ratio": 0,
|
|
140
151
|
"run_frequency":"daily",
|
|
141
152
|
"keep_shorts": true,
|
|
142
153
|
"keep_duplicates": false,
|
|
@@ -174,6 +185,11 @@ The following *user_params.json* file would only add videos with good quality.
|
|
|
174
185
|
"lowest_framerate": 60,
|
|
175
186
|
"preferred_dimensions": ["2D"],
|
|
176
187
|
"preferred_projections": ["rectangular"],
|
|
188
|
+
"views_threshold": 0,
|
|
189
|
+
"likes_threshold": 0,
|
|
190
|
+
"comments_threshold": 0,
|
|
191
|
+
"likes_to_views_ratio": 0,
|
|
192
|
+
"comments_to_views_ratio": 0,
|
|
177
193
|
"run_frequency":"daily",
|
|
178
194
|
"keep_shorts": true,
|
|
179
195
|
"keep_duplicates": false,
|
|
@@ -211,6 +227,11 @@ The following *user_params.json* file would only add the *$1 vs.* MrBeast videos
|
|
|
211
227
|
"lowest_framerate": null,
|
|
212
228
|
"preferred_dimensions": ["2D"],
|
|
213
229
|
"preferred_projections": ["rectangular"],
|
|
230
|
+
"views_threshold": 0,
|
|
231
|
+
"likes_threshold": 0,
|
|
232
|
+
"comments_threshold": 0,
|
|
233
|
+
"likes_to_views_ratio": 0,
|
|
234
|
+
"comments_to_views_ratio": 0,
|
|
214
235
|
"run_frequency":"daily",
|
|
215
236
|
"keep_shorts": false,
|
|
216
237
|
"keep_duplicates": false,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|