QTube 2.5.0__py3-none-any.whl → 2.5.1__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 +4 -2
- QTube/utils/checks.py +1 -1
- {QTube-2.5.0.dist-info → qtube-2.5.1.dist-info}/METADATA +42 -18
- qtube-2.5.1.dist-info/RECORD +13 -0
- {QTube-2.5.0.dist-info → qtube-2.5.1.dist-info}/WHEEL +1 -1
- {QTube-2.5.0.dist-info → qtube-2.5.1.dist-info/licenses}/LICENSE.txt +1 -1
- QTube/utils/youtube/captions.py +0 -48
- QTube/utils/youtube/channels.py +0 -110
- QTube/utils/youtube/playlists.py +0 -140
- QTube/utils/youtube/videos.py +0 -649
- QTube-2.5.0.dist-info/RECORD +0 -17
- {QTube-2.5.0.dist-info → qtube-2.5.1.dist-info}/entry_points.txt +0 -0
- {QTube-2.5.0.dist-info → qtube-2.5.1.dist-info}/top_level.txt +0 -0
QTube/scripts/qtube.py
CHANGED
|
@@ -369,7 +369,9 @@ def main():
|
|
|
369
369
|
## Additional information retrieving on the videos
|
|
370
370
|
split_videos = QTube.utils.helpers.split_dict(videos, 50)
|
|
371
371
|
|
|
372
|
-
responses = {
|
|
372
|
+
responses = {
|
|
373
|
+
"items": []
|
|
374
|
+
} # Initialize with expected structure to handle empty videos dict case
|
|
373
375
|
for sub_dict in split_videos:
|
|
374
376
|
partial = QTube.utils.helpers.handle_http_errors(
|
|
375
377
|
verb,
|
|
@@ -379,7 +381,7 @@ def main():
|
|
|
379
381
|
sub_dict.keys(),
|
|
380
382
|
)
|
|
381
383
|
|
|
382
|
-
if len(responses) == 0: # first run of the loop
|
|
384
|
+
if len(responses["items"]) == 0: # first run of the loop
|
|
383
385
|
responses.update(partial)
|
|
384
386
|
else:
|
|
385
387
|
vid_dicts = partial["items"]
|
QTube/utils/checks.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: QTube
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.1
|
|
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) [2026] [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
|
|
@@ -44,15 +44,18 @@ Classifier: Topic :: Multimedia :: Video
|
|
|
44
44
|
Requires-Python: >=3.8
|
|
45
45
|
Description-Content-Type: text/markdown
|
|
46
46
|
License-File: LICENSE.txt
|
|
47
|
-
Requires-Dist: colorama
|
|
48
|
-
Requires-Dist:
|
|
49
|
-
Requires-Dist:
|
|
50
|
-
Requires-Dist: isodate
|
|
51
|
-
Requires-Dist: numpy
|
|
52
|
-
Requires-Dist: protobuf
|
|
53
|
-
Requires-Dist: pytube
|
|
54
|
-
Requires-Dist: Requests
|
|
55
|
-
Requires-Dist: setuptools
|
|
47
|
+
Requires-Dist: colorama>=0.4.6
|
|
48
|
+
Requires-Dist: google_api_python_client>=2.119.0
|
|
49
|
+
Requires-Dist: google_auth_oauthlib>=1.0.0
|
|
50
|
+
Requires-Dist: isodate>=0.6.1
|
|
51
|
+
Requires-Dist: numpy>=1.24.3
|
|
52
|
+
Requires-Dist: protobuf>=4.25.1
|
|
53
|
+
Requires-Dist: pytube>=15.0.0
|
|
54
|
+
Requires-Dist: Requests>=2.32.0
|
|
55
|
+
Requires-Dist: setuptools>=70.0.0
|
|
56
|
+
Dynamic: author
|
|
57
|
+
Dynamic: home-page
|
|
58
|
+
Dynamic: license-file
|
|
56
59
|
|
|
57
60
|
<h1 align="center">
|
|
58
61
|
<br>
|
|
@@ -85,12 +88,15 @@ Requires-Dist: setuptools >=70.0.0
|
|
|
85
88
|
</p>
|
|
86
89
|
|
|
87
90
|
## About
|
|
91
|
+
|
|
88
92
|
The reason for the existence of this software is Youtube's seemingly random behavior when it comes to notifying people that a new video has been published (late or missing notifications, useless notification bell, videos not appearing in the subscription tab, ...).
|
|
89
93
|
|
|
90
94
|
With this software, you can set a number of rules that determine which videos are added to a dedicated playlist, so you won't miss any new uploads!
|
|
91
95
|
|
|
92
96
|
## Features
|
|
97
|
+
|
|
93
98
|
Each of these rules is based on putting some kind of constraint on video properties. Currently, the following features are available:
|
|
99
|
+
|
|
94
100
|
* Channel name filtering
|
|
95
101
|
* Title filtering
|
|
96
102
|
* Description filtering
|
|
@@ -109,9 +115,10 @@ Each of these rules is based on putting some kind of constraint on video propert
|
|
|
109
115
|
* Duplicate checking
|
|
110
116
|
|
|
111
117
|
## How to use
|
|
112
|
-
Before using this software, you first need to get a Youtube API key and create a web app to get a client secrets file (that should look like [this](docs/client_secrets_template.json)). This [Corey Schafer video](https://www.youtube.com/watch?v=vQQEaSnQ_bs) goes through the process step by step.
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
Before using this software, you first need to get a Youtube API key and create a web app to get a client secrets file (that should look like [this](docs/client_secrets_template.json)). This [Corey Schafer video](https://www.youtube.com/watch?v=vQQEaSnQ_bs) goes through the process step by step. Rename this file to *client_secrets.json*.
|
|
120
|
+
|
|
121
|
+
Once that's done, download this project or install the package with [PyPI](https://pypi.org/project/QTube/). Modify the [user parameters template](docs/user_params_template.json) file so that it fits your needs (more information on how in the [following table](#user-defined-parameters) and in the [examples section](#examples)). Then, rename the file to *user_params.json* and move it along with the *client_secrets.json* file to the [QTube folder](./QTube/).
|
|
115
122
|
|
|
116
123
|
Verify that you have all of the dependencies installed (see the [requirements](requirements.txt) file or the [TOML](pyproject.toml) file).
|
|
117
124
|
|
|
@@ -122,6 +129,7 @@ I would recommend creating a task to execute the program regularly (like once a
|
|
|
122
129
|
For more versatile uses, you can also use command line arguments with the [qtube.py](QTube/scripts/qtube.py) file. Enable this option by setting the `override_json` parameter to *True* in your JSON user parameters file. Provided command line arguments will then override what is in your JSON user parameters file. This is especially useful to manage different types of videos and put them in dedicated playlists (music playlist, gaming playlist, ect...).
|
|
123
130
|
|
|
124
131
|
### User-defined parameters
|
|
132
|
+
|
|
125
133
|
|Parameter|Optional|Description|Possible values|
|
|
126
134
|
|--|:--:|:--:|:--:|
|
|
127
135
|
|`required_in_channel_name`|Yes|Words that must be in channel names, typically channel names themselves. Videos from channels not containing any of the words of this list in their name will not be added.|Any string|
|
|
@@ -167,18 +175,24 @@ For more versatile uses, you can also use command line arguments with the [qtube
|
|
|
167
175
|
All parameters are case-sensitive by default and if you do not want to use an optional parameter, replace its value with *null* or delete the entry.
|
|
168
176
|
|
|
169
177
|
For further information about each parameter, check the note associated with the [release](https://github.com/Killian42/QTube/releases) they were introduced in.
|
|
178
|
+
|
|
170
179
|
### Requirements
|
|
180
|
+
|
|
171
181
|
See the [requirements](requirements.txt) file or the [TOML](pyproject.toml) file.
|
|
172
182
|
|
|
173
183
|
## Examples
|
|
184
|
+
|
|
174
185
|
This section presents examples of user parameters json files for concrete use-cases.
|
|
186
|
+
|
|
175
187
|
* <a href="#example-1---every-videos-from-subscribed-channels">Every videos from subscribed channels</a>
|
|
176
188
|
* <a href="#example-2---higher-quality-videos">Higher quality videos</a>
|
|
177
|
-
* <a href="#example-3---specific-video-series-from-a-creator">Video series from a creator</a>
|
|
189
|
+
* <a href="#example-3---specific-video-series-from-a-creator">Video series from a creator</a>
|
|
178
190
|
|
|
179
191
|
### Example 1 - Every videos from subscribed channels
|
|
192
|
+
|
|
180
193
|
The following *user_params.json* file would add every new videos from channels you are subcribed to.
|
|
181
|
-
|
|
194
|
+
|
|
195
|
+
```python
|
|
182
196
|
{
|
|
183
197
|
"required_in_channel_name": null,
|
|
184
198
|
"banned_in_channel_name": null,
|
|
@@ -221,9 +235,12 @@ The following *user_params.json* file would add every new videos from channels y
|
|
|
221
235
|
"verbosity": ["credentials","videos"]
|
|
222
236
|
}
|
|
223
237
|
```
|
|
238
|
+
|
|
224
239
|
### Example 2 - Higher quality videos
|
|
240
|
+
|
|
225
241
|
The following *user_params.json* file would only add videos with good quality.
|
|
226
|
-
|
|
242
|
+
|
|
243
|
+
```python
|
|
227
244
|
{
|
|
228
245
|
"required_in_channel_name": null,
|
|
229
246
|
"banned_in_channel_name": null,
|
|
@@ -266,9 +283,12 @@ The following *user_params.json* file would only add videos with good quality.
|
|
|
266
283
|
"verbosity": ["credentials","videos"]
|
|
267
284
|
}
|
|
268
285
|
```
|
|
286
|
+
|
|
269
287
|
### Example 3 - Specific video series from a creator
|
|
288
|
+
|
|
270
289
|
The following *user_params.json* file would only add the *$1 vs.* MrBeast videos.
|
|
271
|
-
|
|
290
|
+
|
|
291
|
+
```python
|
|
272
292
|
{
|
|
273
293
|
"required_in_channel_name": ["MrBeast"],
|
|
274
294
|
"banned_in_channel_name": null,
|
|
@@ -313,13 +333,17 @@ The following *user_params.json* file would only add the *$1 vs.* MrBeast videos
|
|
|
313
333
|
```
|
|
314
334
|
|
|
315
335
|
## FAQ
|
|
336
|
+
|
|
316
337
|
There are none yet. But don't hesitate to ask by sending me an [email](mailto:killian.lebreton35@gmail.com).
|
|
317
338
|
|
|
318
339
|
## Contact
|
|
340
|
+
|
|
319
341
|
You can reach me by [email](mailto:killian.lebreton35@gmail.com). Please put *QTube* in the subject line.
|
|
320
342
|
|
|
321
343
|
## Acknowledgments
|
|
344
|
+
|
|
322
345
|
Big thanks [Corey Schafer](https://github.com/CoreyMSchafer) for his great tutorials, as well as for providing the OAuth snippets used in this software.
|
|
323
346
|
|
|
324
347
|
## License
|
|
348
|
+
|
|
325
349
|
This project is licensed under the [MIT License](LICENSE.txt).
|
|
@@ -0,0 +1,13 @@
|
|
|
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=bJuD2BJ51Gv4cx26d-dN3Mbg4t0SXMmatINWoZYavEM,35290
|
|
4
|
+
QTube/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
QTube/utils/checks.py,sha256=gqkZtTv4tk8i9vgzy68a1IC7jk575BC2yUEaHpX7zu4,13309
|
|
6
|
+
QTube/utils/helpers.py,sha256=fl1_fePwy7ot-KRFB9Ieq7fMFtD8Q4TriK8cW17qPy0,9187
|
|
7
|
+
QTube/utils/parsing.py,sha256=H1uVtevbDF-6JWy42--pDvkBBHzAvxBgujIErQk54AI,11057
|
|
8
|
+
qtube-2.5.1.dist-info/licenses/LICENSE.txt,sha256=Z9Z3S-Ah5KeUZAbXRObR5OLIws8vsBwdSib4jX0RE7U,1098
|
|
9
|
+
qtube-2.5.1.dist-info/METADATA,sha256=GqByZxpWCNy9P44KbMKUGrtM53lM54Q1oiGmkNyMbmc,18190
|
|
10
|
+
qtube-2.5.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
qtube-2.5.1.dist-info/entry_points.txt,sha256=Q3SLDyRahuzrNnHC3UOkMH5gM_Um3eCLiLG4vSTPjqE,51
|
|
12
|
+
qtube-2.5.1.dist-info/top_level.txt,sha256=z6oXrT8BiTTZuygjsbfe-NE4rmkHlDK8euAL9m6BT4A,6
|
|
13
|
+
qtube-2.5.1.dist-info/RECORD,,
|
QTube/utils/youtube/captions.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
def make_caption_requests(youtube, video_IDs: list[str]) -> dict[dict]:
|
|
2
|
-
"""Retrieves API caption responses of a list of YT videos.
|
|
3
|
-
|
|
4
|
-
Args:
|
|
5
|
-
youtube (Resource): YT API resource.
|
|
6
|
-
video_IDs (list[str]): List of video IDs.
|
|
7
|
-
|
|
8
|
-
Returns:
|
|
9
|
-
responses_dict (dict[dict]): Dictionary with video IDs as keys and YT API caption responses as values.
|
|
10
|
-
"""
|
|
11
|
-
responses_dict = {
|
|
12
|
-
video_ID: youtube.captions()
|
|
13
|
-
.list(part="snippet", videoId=video_ID)
|
|
14
|
-
.execute(num_retries=5)
|
|
15
|
-
for video_ID in video_IDs
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return responses_dict
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_captions(
|
|
22
|
-
youtube=None,
|
|
23
|
-
response: dict = None,
|
|
24
|
-
video_IDs: list[str] = None,
|
|
25
|
-
use_API: bool = False,
|
|
26
|
-
) -> dict[dict]:
|
|
27
|
-
"""Retrieves the captions of YT videos.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
youtube (Resource): YT API resource.
|
|
31
|
-
response (dict[dict]): YT API response from the make_caption_request function.
|
|
32
|
-
video_IDs (list[str]): List of video IDs.
|
|
33
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
34
|
-
|
|
35
|
-
Returns:
|
|
36
|
-
captions_dict (dict[dict]): Dictionary with video IDs as keys and caption dictionaries as values.
|
|
37
|
-
"""
|
|
38
|
-
if use_API:
|
|
39
|
-
response = make_caption_requests(youtube, video_IDs)
|
|
40
|
-
|
|
41
|
-
captions_dict = {}
|
|
42
|
-
for video_ID, vid_resp in response.items():
|
|
43
|
-
caption_dict = {
|
|
44
|
-
caption["id"]: caption["snippet"] for caption in vid_resp.get("items", [])
|
|
45
|
-
}
|
|
46
|
-
captions_dict.update({video_ID: caption_dict})
|
|
47
|
-
|
|
48
|
-
return captions_dict
|
QTube/utils/youtube/channels.py
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
def get_subscriptions(youtube, next_page_token=None) -> dict:
|
|
2
|
-
"""Retrieves the subscriptions of the logged user.
|
|
3
|
-
|
|
4
|
-
Args:
|
|
5
|
-
youtube (Resource): YT API resource.
|
|
6
|
-
next_page_token (str): Token of the subscription page (optional).
|
|
7
|
-
|
|
8
|
-
Returns:
|
|
9
|
-
channels (dict): Dictionary of channel names (keys) and channel IDs (values).
|
|
10
|
-
"""
|
|
11
|
-
channels = {}
|
|
12
|
-
|
|
13
|
-
while True:
|
|
14
|
-
response = (
|
|
15
|
-
youtube.subscriptions()
|
|
16
|
-
.list(
|
|
17
|
-
part="snippet",
|
|
18
|
-
mine=True,
|
|
19
|
-
maxResults=50,
|
|
20
|
-
order="alphabetical",
|
|
21
|
-
pageToken=next_page_token,
|
|
22
|
-
)
|
|
23
|
-
.execute(num_retries=5)
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
for item in response.get("items", []):
|
|
27
|
-
title = item["snippet"]["title"]
|
|
28
|
-
channel_id = item["snippet"]["resourceId"]["channelId"]
|
|
29
|
-
channels[title] = channel_id
|
|
30
|
-
|
|
31
|
-
next_page_token = response.get("nextPageToken")
|
|
32
|
-
|
|
33
|
-
if not next_page_token:
|
|
34
|
-
break
|
|
35
|
-
|
|
36
|
-
return channels
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def get_channel_info(youtube, handle: str) -> dict:
|
|
40
|
-
"""Retrieves basic information about a YT channel.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
youtube (Resource): YT API resource.
|
|
44
|
-
handle (str): Handle of the YT channel.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
response (dict): Dictionary containing basic information on the requested YT channel.
|
|
48
|
-
"""
|
|
49
|
-
channel = {}
|
|
50
|
-
|
|
51
|
-
response = (
|
|
52
|
-
youtube.channels().list(part="snippet", forHandle=handle).execute(num_retries=5)
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
if "items" in response.keys():
|
|
56
|
-
title = response["items"][0]["snippet"]["title"]
|
|
57
|
-
channel_id = response["items"][0]["id"]
|
|
58
|
-
channel[title] = channel_id
|
|
59
|
-
else:
|
|
60
|
-
print(
|
|
61
|
-
f"Could not find a YT channel associated with the following handle: {handle}."
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
return channel
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def get_uploads_playlists(youtube, channel_IDs: list[str]) -> list[str]:
|
|
68
|
-
"""Retrieves the upload playlists of YT channels.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
youtube (Resource): YT API ressource.
|
|
72
|
-
channel_IDs (list[str]): Channel IDs of YT channels.
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
upload_pl_ids (list[str]): IDs of the uploads playlist of the YT channels.
|
|
76
|
-
"""
|
|
77
|
-
channel_IDs_str = ",".join(channel_IDs)
|
|
78
|
-
response = (
|
|
79
|
-
youtube.channels()
|
|
80
|
-
.list(part="contentDetails", id=channel_IDs_str)
|
|
81
|
-
.execute(num_retries=5)
|
|
82
|
-
)
|
|
83
|
-
# Create a dictionary to store the mapping between channel IDs and upload playlist IDs
|
|
84
|
-
channel_to_upload_map = {
|
|
85
|
-
item["id"]: item["contentDetails"]["relatedPlaylists"]["uploads"]
|
|
86
|
-
for item in response.get("items", [])
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Generate the resulting list in the same order as the input channel IDs
|
|
90
|
-
upload_pl_ids = [channel_to_upload_map[channel_ID] for channel_ID in channel_IDs]
|
|
91
|
-
|
|
92
|
-
return upload_pl_ids
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def get_user_info(youtube) -> dict:
|
|
96
|
-
"""Retrieves information about the logged-in user channel.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
youtube (Resource): YT API resource.
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
response (dict): Dictionary containing information on the logged-in user channel.
|
|
103
|
-
"""
|
|
104
|
-
response = (
|
|
105
|
-
youtube.channels()
|
|
106
|
-
.list(part="snippet,contentDetails,statistics", mine=True)
|
|
107
|
-
.execute(num_retries=5)
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
return response
|
QTube/utils/youtube/playlists.py
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import datetime as dt
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def get_recent_videos(youtube, playlist_ID: str, vid_nb: int) -> dict:
|
|
5
|
-
"""Retrieves the last videos added in a YT playlist.
|
|
6
|
-
|
|
7
|
-
Args:
|
|
8
|
-
youtube (Resource): YT API resource.
|
|
9
|
-
playlist_ID (str): ID of the playlist.
|
|
10
|
-
vid_nb (int): Number of videos to retrieve.
|
|
11
|
-
|
|
12
|
-
Returns:
|
|
13
|
-
recent_vids (dict): Dictionary containing the ID (keys) and upload date (values) of the last videos added in the playlist.
|
|
14
|
-
"""
|
|
15
|
-
response = (
|
|
16
|
-
youtube.playlistItems()
|
|
17
|
-
.list(part="contentDetails", playlistId=playlist_ID, maxResults=vid_nb)
|
|
18
|
-
.execute(num_retries=5)
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
recent_vids = {
|
|
22
|
-
item["contentDetails"]["videoId"]: {
|
|
23
|
-
"upload datetime": dt.datetime.fromisoformat(
|
|
24
|
-
item["contentDetails"]["videoPublishedAt"]
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
for item in response.get("items", [])
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return recent_vids
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def get_playlist_content(youtube, playlist_ID: str) -> list[str]:
|
|
34
|
-
"""Retrieves the IDs of videos saved in a YT playlist.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
youtube (Resource): YT API resource.
|
|
38
|
-
playlist_ID (str): ID of the playlist.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
videos_IDs (list[str]): List containing the IDs of the videos saved in the playlist.
|
|
42
|
-
"""
|
|
43
|
-
next_page_token = None
|
|
44
|
-
videos_IDs = []
|
|
45
|
-
while True:
|
|
46
|
-
response = (
|
|
47
|
-
youtube.playlistItems()
|
|
48
|
-
.list(
|
|
49
|
-
part="contentDetails",
|
|
50
|
-
playlistId=playlist_ID,
|
|
51
|
-
maxResults=50,
|
|
52
|
-
pageToken=next_page_token,
|
|
53
|
-
)
|
|
54
|
-
.execute(num_retries=5)
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
temp_videos_IDs = [
|
|
58
|
-
item["contentDetails"]["videoId"] for item in response.get("items")
|
|
59
|
-
]
|
|
60
|
-
videos_IDs.extend(temp_videos_IDs)
|
|
61
|
-
|
|
62
|
-
next_page_token = response.get("nextPageToken")
|
|
63
|
-
|
|
64
|
-
if not next_page_token:
|
|
65
|
-
break
|
|
66
|
-
|
|
67
|
-
return videos_IDs
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def get_playlists_titles(youtube=None, playlist_IDs: list[str] = None) -> list[str]:
|
|
71
|
-
"""Retrieves the titles of a list of YT playlists.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
youtube (Resource): YT API resource.
|
|
75
|
-
playlists_IDs (list[str]): List of playlist IDs.
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
titles (list[str]): List of YT playlist titles.
|
|
79
|
-
"""
|
|
80
|
-
playlist_IDs_str = ",".join(playlist_IDs)
|
|
81
|
-
response = (
|
|
82
|
-
youtube.playlists()
|
|
83
|
-
.list(part="snippet", id=playlist_IDs_str)
|
|
84
|
-
.execute(num_retries=5)
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
titles = [playlist["snippet"]["title"] for playlist in response["items"]]
|
|
88
|
-
|
|
89
|
-
return titles
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def get_playlists_video_counts(
|
|
93
|
-
youtube=None, playlist_IDs: list[str] = None
|
|
94
|
-
) -> list[int]:
|
|
95
|
-
"""Retrieves the number of videos of a list of YT playlists.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
youtube (Resource): YT API resource.
|
|
99
|
-
playlists_IDs (list[str]): List of playlist IDs.
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
counts (list[int]): List of YT playlist video counts.
|
|
103
|
-
"""
|
|
104
|
-
playlist_IDs_str = ",".join(playlist_IDs)
|
|
105
|
-
response = (
|
|
106
|
-
youtube.playlists()
|
|
107
|
-
.list(part="contentDetails", id=playlist_IDs_str)
|
|
108
|
-
.execute(num_retries=5)
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
counts = [playlist["contentDetails"]["itemCount"] for playlist in response["items"]]
|
|
112
|
-
|
|
113
|
-
return counts
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def add_to_playlist(youtube, playlist_ID: str, video_ID: str) -> None:
|
|
117
|
-
"""Adds a YT video to the YT playlist.
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
youtube (Resource): YT API resource.
|
|
121
|
-
playlist_ID (str): Playlist ID.
|
|
122
|
-
video_ID (str): Video ID.
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
None
|
|
126
|
-
"""
|
|
127
|
-
response = (
|
|
128
|
-
youtube.playlistItems()
|
|
129
|
-
.insert(
|
|
130
|
-
part="snippet",
|
|
131
|
-
body={
|
|
132
|
-
"snippet": {
|
|
133
|
-
"playlistId": playlist_ID,
|
|
134
|
-
"resourceId": {"kind": "youtube#video", "videoId": video_ID},
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
)
|
|
138
|
-
.execute(num_retries=5)
|
|
139
|
-
)
|
|
140
|
-
return
|
QTube/utils/youtube/videos.py
DELETED
|
@@ -1,649 +0,0 @@
|
|
|
1
|
-
import isodate
|
|
2
|
-
|
|
3
|
-
from pytube import YouTube
|
|
4
|
-
|
|
5
|
-
from QTube.utils import helpers, checks
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def make_video_requests(youtube, video_IDs: list[str]) -> dict:
|
|
9
|
-
"""Retrieves information on a list of YT videos.
|
|
10
|
-
|
|
11
|
-
Args:
|
|
12
|
-
youtube (Resource): YT API resource.
|
|
13
|
-
video_IDs (list[str]): List of video IDs.
|
|
14
|
-
|
|
15
|
-
Returns:
|
|
16
|
-
response (dict[dict]): YT API response.
|
|
17
|
-
"""
|
|
18
|
-
video_IDs_str = ",".join(video_IDs)
|
|
19
|
-
response = (
|
|
20
|
-
youtube.videos()
|
|
21
|
-
.list(
|
|
22
|
-
part="snippet,contentDetails,statistics,paidProductPlacementDetails,status",
|
|
23
|
-
id=video_IDs_str,
|
|
24
|
-
)
|
|
25
|
-
.execute(num_retries=5)
|
|
26
|
-
)
|
|
27
|
-
return response
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def get_titles(
|
|
31
|
-
youtube=None,
|
|
32
|
-
response: dict = None,
|
|
33
|
-
video_IDs: list[str] = None,
|
|
34
|
-
use_API: bool = False,
|
|
35
|
-
) -> list[str]:
|
|
36
|
-
"""Retrieves the titles of a list of YT videos.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
youtube (Resource): YT API resource.
|
|
40
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
41
|
-
video_IDs (list[str]): List of video IDs.
|
|
42
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
titles (list[str]): List of YT videos titles.
|
|
46
|
-
"""
|
|
47
|
-
if use_API:
|
|
48
|
-
video_IDs_str = ",".join(video_IDs)
|
|
49
|
-
response = (
|
|
50
|
-
youtube.videos()
|
|
51
|
-
.list(part="snippet", id=video_IDs_str)
|
|
52
|
-
.execute(num_retries=5)
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
titles = [vid["snippet"]["title"] for vid in response["items"]]
|
|
56
|
-
|
|
57
|
-
return titles
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def get_tags(
|
|
61
|
-
youtube=None,
|
|
62
|
-
response: dict = None,
|
|
63
|
-
video_IDs: list[str] = None,
|
|
64
|
-
use_API: bool = False,
|
|
65
|
-
) -> list[list[str] | None]:
|
|
66
|
-
"""Retrieves the tags of YT videos.
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
youtube (Resource): YT API resource.
|
|
70
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
71
|
-
video_IDs (list[str]): List of video IDs.
|
|
72
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
tags (list[list[str]|None]): List of YT videos tags, or None if there are no tags for this video.
|
|
76
|
-
"""
|
|
77
|
-
if use_API:
|
|
78
|
-
video_IDs_str = ",".join(video_IDs)
|
|
79
|
-
response = (
|
|
80
|
-
youtube.videos()
|
|
81
|
-
.list(part="snippet", id=video_IDs_str)
|
|
82
|
-
.execute(num_retries=5)
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
tags = [
|
|
86
|
-
vid["snippet"]["tags"] if "tags" in vid["snippet"] else None
|
|
87
|
-
for vid in response.get("items", [])
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
return tags
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def get_descriptions(
|
|
94
|
-
youtube=None,
|
|
95
|
-
response: dict = None,
|
|
96
|
-
video_IDs: list[str] = None,
|
|
97
|
-
use_API: bool = False,
|
|
98
|
-
) -> list[str]:
|
|
99
|
-
"""Retrieves the descriptions of YT videos.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
youtube (Resource): YT API resource.
|
|
103
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
104
|
-
video_IDs (list[str]): List of video IDs.
|
|
105
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
description (list[str]): YT videos descriptions.
|
|
109
|
-
"""
|
|
110
|
-
if use_API:
|
|
111
|
-
video_IDs_str = ",".join(video_IDs) # Join the video IDs with commas
|
|
112
|
-
response = (
|
|
113
|
-
youtube.videos()
|
|
114
|
-
.list(part="snippet", id=video_IDs_str)
|
|
115
|
-
.execute(num_retries=5)
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
descriptions = [vid["snippet"]["description"] for vid in response.get("items", [])]
|
|
119
|
-
return descriptions
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def get_durations(
|
|
123
|
-
youtube=None,
|
|
124
|
-
response: dict = None,
|
|
125
|
-
video_IDs: list[str] = None,
|
|
126
|
-
use_API: bool = False,
|
|
127
|
-
) -> list[float]:
|
|
128
|
-
"""Retrieves the duration of YT videos.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
youtube (Resource): YT API resource.
|
|
132
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
133
|
-
video_IDs (list[str]): List of video IDs.
|
|
134
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
durations (list[float]): List of YT videos durations in seconds.
|
|
138
|
-
"""
|
|
139
|
-
if use_API:
|
|
140
|
-
video_IDs_str = ",".join(video_IDs)
|
|
141
|
-
response = (
|
|
142
|
-
youtube.videos()
|
|
143
|
-
.list(part="contentDetails", id=video_IDs_str)
|
|
144
|
-
.execute(num_retries=5)
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
durations_iso = [
|
|
148
|
-
vid["contentDetails"].get("duration", "PT0M3.14159265S")
|
|
149
|
-
for vid in response["items"]
|
|
150
|
-
] # Set the duration of videos without duration info to pi seconds to identify them without breaking the iso conversion
|
|
151
|
-
durations = [isodate.parse_duration(d).total_seconds() for d in durations_iso]
|
|
152
|
-
return durations
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def get_languages(
|
|
156
|
-
youtube=None,
|
|
157
|
-
response: dict = None,
|
|
158
|
-
video_IDs: list[str] = None,
|
|
159
|
-
use_API: bool = False,
|
|
160
|
-
) -> list[str]:
|
|
161
|
-
"""Retrieves the original language of YT videos.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
youtube (Resource): YT API resource.
|
|
165
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
166
|
-
video_IDs (list[str]): List of video IDs.
|
|
167
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
languages (list[str]): List of YT videos languages.
|
|
171
|
-
"""
|
|
172
|
-
if use_API:
|
|
173
|
-
video_IDs_str = ",".join(video_IDs)
|
|
174
|
-
response = (
|
|
175
|
-
youtube.videos()
|
|
176
|
-
.list(part="snippet", id=video_IDs_str)
|
|
177
|
-
.execute(num_retries=5)
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
languages = [
|
|
181
|
-
(
|
|
182
|
-
vid["snippet"]["defaultAudioLanguage"]
|
|
183
|
-
if "defaultAudioLanguage" in vid["snippet"]
|
|
184
|
-
else (
|
|
185
|
-
vid["snippet"]["defaultLanguage"]
|
|
186
|
-
if "defaultLanguage" in vid["snippet"]
|
|
187
|
-
else "unknown"
|
|
188
|
-
)
|
|
189
|
-
).split("-")[
|
|
190
|
-
0
|
|
191
|
-
] # strips regional specifiers
|
|
192
|
-
for vid in response["items"]
|
|
193
|
-
]
|
|
194
|
-
|
|
195
|
-
return languages
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def get_dimensions(
|
|
199
|
-
youtube=None,
|
|
200
|
-
response: dict = None,
|
|
201
|
-
video_IDs: list[str] = None,
|
|
202
|
-
use_API: bool = False,
|
|
203
|
-
) -> list[str]:
|
|
204
|
-
"""Retrieves the dimension of YT videos (2D or 3D).
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
youtube (Resource): YT API resource.
|
|
208
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
209
|
-
video_IDs (list[str]): List of video IDs.
|
|
210
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
dimensions (list[str]): List of YT videos dimensions.
|
|
214
|
-
"""
|
|
215
|
-
if use_API:
|
|
216
|
-
video_IDs_str = ",".join(video_IDs)
|
|
217
|
-
response = (
|
|
218
|
-
youtube.videos()
|
|
219
|
-
.list(part="contentDetails", id=video_IDs_str)
|
|
220
|
-
.execute(num_retries=5)
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
dimensions = [vid["contentDetails"]["dimension"] for vid in response["items"]]
|
|
224
|
-
return dimensions
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def get_definitions(
|
|
228
|
-
youtube=None,
|
|
229
|
-
response: dict = None,
|
|
230
|
-
video_IDs: list[str] = None,
|
|
231
|
-
use_API: bool = False,
|
|
232
|
-
) -> list[str]:
|
|
233
|
-
"""Retrieves the definition (sd or hd) of YT videos.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
youtube (Resource): YT API resource.
|
|
237
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
238
|
-
video_IDs (list[str]): List of video IDs.
|
|
239
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
240
|
-
|
|
241
|
-
Returns:
|
|
242
|
-
definitions (list[str]): List of YT videos definitions.
|
|
243
|
-
"""
|
|
244
|
-
if use_API:
|
|
245
|
-
video_IDs_str = ",".join(video_IDs)
|
|
246
|
-
response = (
|
|
247
|
-
youtube.videos()
|
|
248
|
-
.list(part="contentDetails", id=video_IDs_str)
|
|
249
|
-
.execute(num_retries=5)
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
definitions = [vid["contentDetails"]["definition"] for vid in response["items"]]
|
|
253
|
-
return definitions
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
def get_resolutions(video_IDs: list[str] = None) -> dict[str, list[int]]:
|
|
257
|
-
"""Retrieves the resolutions of YT videos.
|
|
258
|
-
This function does not rely on the YT API but on a third party
|
|
259
|
-
package (pytube), so it takes longer to run.
|
|
260
|
-
|
|
261
|
-
Args:
|
|
262
|
-
video_IDs (list[str]): List of video IDs.
|
|
263
|
-
|
|
264
|
-
Returns:
|
|
265
|
-
resolutions (dict[str, list[int]]): Dictionary mapping video IDs to the resolutions.
|
|
266
|
-
"""
|
|
267
|
-
base_url = "http://youtube.com/watch?v"
|
|
268
|
-
resolutions = {}
|
|
269
|
-
|
|
270
|
-
for vid_ID in video_IDs:
|
|
271
|
-
url = f"{base_url}={vid_ID}"
|
|
272
|
-
|
|
273
|
-
try:
|
|
274
|
-
yt = YouTube(url)
|
|
275
|
-
vid_resolutions = list(
|
|
276
|
-
{
|
|
277
|
-
int(stream.resolution.split("p")[0])
|
|
278
|
-
for stream in yt.streams.filter(type="video")
|
|
279
|
-
}
|
|
280
|
-
)
|
|
281
|
-
resolutions[vid_ID] = vid_resolutions
|
|
282
|
-
except Exception as e:
|
|
283
|
-
print(f"Error processing video {vid_ID}: {e}")
|
|
284
|
-
|
|
285
|
-
return resolutions
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
def get_framerates(video_IDs: list[str] = None) -> dict[str, list[int]]:
|
|
289
|
-
"""Retrieves the framerates of YT videos.
|
|
290
|
-
This function does not rely on the YT API but on a third party
|
|
291
|
-
package (pytube), so it takes longer to run.
|
|
292
|
-
|
|
293
|
-
Args:
|
|
294
|
-
video_IDs (list[str]): List of video IDs.
|
|
295
|
-
|
|
296
|
-
Returns:
|
|
297
|
-
framerates (dict[str, list[int]]): Dictionary mapping video IDs to the framerates.
|
|
298
|
-
"""
|
|
299
|
-
base_url = "http://youtube.com/watch?v"
|
|
300
|
-
framerates = {}
|
|
301
|
-
|
|
302
|
-
for vid_ID in video_IDs:
|
|
303
|
-
url = f"{base_url}={vid_ID}"
|
|
304
|
-
|
|
305
|
-
try:
|
|
306
|
-
yt = YouTube(url)
|
|
307
|
-
vid_framerates = list(
|
|
308
|
-
{stream.fps for stream in yt.streams.filter(type="video")}
|
|
309
|
-
)
|
|
310
|
-
framerates[vid_ID] = vid_framerates
|
|
311
|
-
except Exception as e:
|
|
312
|
-
print(f"Error processing video {vid_ID}: {e}")
|
|
313
|
-
|
|
314
|
-
return framerates
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def get_projections(
|
|
318
|
-
youtube=None,
|
|
319
|
-
response: dict = None,
|
|
320
|
-
video_IDs: list[str] = None,
|
|
321
|
-
use_API: bool = False,
|
|
322
|
-
) -> list[str]:
|
|
323
|
-
"""Retrieves the projection (360 or rectangular) of YT videos.
|
|
324
|
-
|
|
325
|
-
Args:
|
|
326
|
-
youtube (Resource): YT API resource.
|
|
327
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
328
|
-
video_IDs (list[str]): List of video IDs.
|
|
329
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
330
|
-
|
|
331
|
-
Returns:
|
|
332
|
-
projections (list[str]): List of YT videos projections.
|
|
333
|
-
"""
|
|
334
|
-
if use_API:
|
|
335
|
-
video_IDs_str = ",".join(video_IDs)
|
|
336
|
-
response = (
|
|
337
|
-
youtube.videos()
|
|
338
|
-
.list(part="contentDetails", id=video_IDs_str)
|
|
339
|
-
.execute(num_retries=5)
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
projections = [vid["contentDetails"]["projection"] for vid in response["items"]]
|
|
343
|
-
return projections
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
def get_view_counts(
|
|
347
|
-
youtube=None,
|
|
348
|
-
response: dict = None,
|
|
349
|
-
video_IDs: list[str] = None,
|
|
350
|
-
use_API: bool = False,
|
|
351
|
-
) -> list[int]:
|
|
352
|
-
"""Retrieves the number of views of a list of YT videos.
|
|
353
|
-
|
|
354
|
-
Args:
|
|
355
|
-
youtube (Resource): YT API resource.
|
|
356
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
357
|
-
video_IDs (list[str]): List of video IDs.
|
|
358
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
359
|
-
|
|
360
|
-
Returns:
|
|
361
|
-
views (list[int]): List of YT videos views.
|
|
362
|
-
"""
|
|
363
|
-
if use_API:
|
|
364
|
-
video_IDs_str = ",".join(video_IDs)
|
|
365
|
-
response = (
|
|
366
|
-
youtube.videos()
|
|
367
|
-
.list(part="statistics", id=video_IDs_str)
|
|
368
|
-
.execute(num_retries=5)
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
views = [int(vid["statistics"]["viewCount"]) for vid in response["items"]]
|
|
372
|
-
|
|
373
|
-
return views
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
def get_like_counts(
|
|
377
|
-
youtube=None,
|
|
378
|
-
response: dict = None,
|
|
379
|
-
video_IDs: list[str] = None,
|
|
380
|
-
use_API: bool = False,
|
|
381
|
-
) -> list[int]:
|
|
382
|
-
"""Retrieves the number of likes of a list of YT videos.
|
|
383
|
-
|
|
384
|
-
Args:
|
|
385
|
-
youtube (Resource): YT API resource.
|
|
386
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
387
|
-
video_IDs (list[str]): List of video IDs.
|
|
388
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
389
|
-
|
|
390
|
-
Returns:
|
|
391
|
-
likes (list[int]): List of YT videos likes.
|
|
392
|
-
"""
|
|
393
|
-
if use_API:
|
|
394
|
-
video_IDs_str = ",".join(video_IDs)
|
|
395
|
-
response = (
|
|
396
|
-
youtube.videos()
|
|
397
|
-
.list(part="statistics", id=video_IDs_str)
|
|
398
|
-
.execute(num_retries=5)
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
likes = [int(vid["statistics"]["likeCount"]) for vid in response["items"]]
|
|
402
|
-
|
|
403
|
-
return likes
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
def get_comment_counts(
|
|
407
|
-
youtube=None,
|
|
408
|
-
response: dict = None,
|
|
409
|
-
video_IDs: list[str] = None,
|
|
410
|
-
use_API: bool = False,
|
|
411
|
-
) -> list[int]:
|
|
412
|
-
"""Retrieves the number of comments of a list of YT videos.
|
|
413
|
-
|
|
414
|
-
Args:
|
|
415
|
-
youtube (Resource): YT API resource.
|
|
416
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
417
|
-
video_IDs (list[str]): List of video IDs.
|
|
418
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
419
|
-
|
|
420
|
-
Returns:
|
|
421
|
-
comment_counts (list[int]): List of YT videos comment counts.
|
|
422
|
-
"""
|
|
423
|
-
if use_API:
|
|
424
|
-
video_IDs_str = ",".join(video_IDs)
|
|
425
|
-
response = (
|
|
426
|
-
youtube.videos()
|
|
427
|
-
.list(part="statistics", id=video_IDs_str)
|
|
428
|
-
.execute(num_retries=5)
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
comment_counts = [
|
|
432
|
-
int(vid["statistics"]["commentCount"]) for vid in response["items"]
|
|
433
|
-
]
|
|
434
|
-
|
|
435
|
-
return comment_counts
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def get_likes_to_views_ratio(
|
|
439
|
-
likes,
|
|
440
|
-
views,
|
|
441
|
-
youtube=None,
|
|
442
|
-
response: dict = None,
|
|
443
|
-
video_IDs: list[str] = None,
|
|
444
|
-
use_API: bool = False,
|
|
445
|
-
) -> list[int | float]:
|
|
446
|
-
"""Retrieves the likes to views ratio of a list of YT videos.
|
|
447
|
-
|
|
448
|
-
Args:
|
|
449
|
-
likes (list[int]): List of the number of likes.
|
|
450
|
-
views (list[int]): List of the number of views.
|
|
451
|
-
youtube (Resource): YT API resource.
|
|
452
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
453
|
-
video_IDs (list[str]): List of video IDs.
|
|
454
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
455
|
-
|
|
456
|
-
Returns:
|
|
457
|
-
ratio (list[int|float]): List of YT videos' likes to views ratios.
|
|
458
|
-
"""
|
|
459
|
-
if use_API:
|
|
460
|
-
views = get_view_counts(youtube, response, video_IDs, use_API)
|
|
461
|
-
likes = get_like_counts(youtube, response, video_IDs, use_API)
|
|
462
|
-
|
|
463
|
-
ratio = helpers.divide_lists(likes, views, False)
|
|
464
|
-
|
|
465
|
-
return ratio
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
def get_comments_to_views_ratio(
|
|
469
|
-
likes,
|
|
470
|
-
views,
|
|
471
|
-
youtube=None,
|
|
472
|
-
response: dict = None,
|
|
473
|
-
video_IDs: list[str] = None,
|
|
474
|
-
use_API: bool = False,
|
|
475
|
-
) -> list[int | float]:
|
|
476
|
-
"""Retrieves the comments to views ratio of a list of YT videos.
|
|
477
|
-
|
|
478
|
-
Args:
|
|
479
|
-
comments (list[int]): List of the number of comments.
|
|
480
|
-
views (list[int]): List of the number of views.
|
|
481
|
-
youtube (Resource): YT API resource.
|
|
482
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
483
|
-
video_IDs (list[str]): List of video IDs.
|
|
484
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
485
|
-
|
|
486
|
-
Returns:
|
|
487
|
-
views (list[int|float]): List of YT videos' comments to views ratios.
|
|
488
|
-
"""
|
|
489
|
-
if use_API:
|
|
490
|
-
views = get_view_counts(youtube, response, video_IDs, use_API)
|
|
491
|
-
comments = get_comment_counts(youtube, response, video_IDs, use_API)
|
|
492
|
-
|
|
493
|
-
ratio = helpers.divide_lists(comments, views, False)
|
|
494
|
-
|
|
495
|
-
return ratio
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
def has_captions(
|
|
499
|
-
youtube=None,
|
|
500
|
-
response: dict = None,
|
|
501
|
-
video_IDs: list[str] = None,
|
|
502
|
-
use_API: bool = False,
|
|
503
|
-
) -> list[bool]:
|
|
504
|
-
"""Determines if YT videos have captions.
|
|
505
|
-
|
|
506
|
-
Args:
|
|
507
|
-
youtube (Resource): YT API resource.
|
|
508
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
509
|
-
video_IDs (list[str]): List of video IDs.
|
|
510
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
511
|
-
|
|
512
|
-
Returns:
|
|
513
|
-
captions (list[bool]): True if the video has captions, False otherwise.
|
|
514
|
-
"""
|
|
515
|
-
if use_API:
|
|
516
|
-
video_IDs_str = ",".join(video_IDs)
|
|
517
|
-
response = (
|
|
518
|
-
youtube.videos()
|
|
519
|
-
.list(part="contentDetails", id=video_IDs_str)
|
|
520
|
-
.execute(num_retries=5)
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
captions = [vid["contentDetails"]["caption"] for vid in response["items"]]
|
|
524
|
-
return captions
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
def is_short(
|
|
528
|
-
youtube=None,
|
|
529
|
-
response: dict = None,
|
|
530
|
-
video_IDs: list[str] = None,
|
|
531
|
-
use_API: bool = False,
|
|
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.
|
|
534
|
-
|
|
535
|
-
Args:
|
|
536
|
-
youtube (Resource): YT API resource.
|
|
537
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
538
|
-
video_IDs (list[str]): List of video IDs.
|
|
539
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
540
|
-
|
|
541
|
-
Returns:
|
|
542
|
-
is_a_short (list[bool]): True if the video is shorter than 181 seconds and there is no URL redirection, False otherwise.
|
|
543
|
-
"""
|
|
544
|
-
durations = get_durations(youtube, response, video_IDs, use_API=use_API)
|
|
545
|
-
|
|
546
|
-
is_short_vid = [
|
|
547
|
-
True if length <= 181 else False for length in durations
|
|
548
|
-
] # Shorts cannot last over 3 minutes.
|
|
549
|
-
|
|
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
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
def is_live(
|
|
560
|
-
youtube=None,
|
|
561
|
-
response: dict = None,
|
|
562
|
-
video_IDs: list[str] = None,
|
|
563
|
-
use_API: bool = False,
|
|
564
|
-
) -> list[str]:
|
|
565
|
-
"""Retrieves the live status of YT videos.
|
|
566
|
-
|
|
567
|
-
Args:
|
|
568
|
-
youtube (Resource): YT API resource.
|
|
569
|
-
response (dict[dict]): YT API response from the make_video_request function.
|
|
570
|
-
video_IDs (list[str]): List of video IDs.
|
|
571
|
-
use_API (bool): Determines if a new API request is made or if the response dictionary is used.
|
|
572
|
-
|
|
573
|
-
Returns:
|
|
574
|
-
live_statuses (list[str]): live if the video is live, upcoming if it is a premiere and none otherwise.
|
|
575
|
-
"""
|
|
576
|
-
if use_API:
|
|
577
|
-
video_IDs_str = ",".join(video_IDs)
|
|
578
|
-
response = (
|
|
579
|
-
youtube.videos()
|
|
580
|
-
.list(part="snippet", id=video_IDs_str)
|
|
581
|
-
.execute(num_retries=5)
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
live_statuses = [
|
|
585
|
-
vid["snippet"]["liveBroadcastContent"] for vid in response["items"]
|
|
586
|
-
]
|
|
587
|
-
|
|
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"]]
|
QTube-2.5.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=x-kzqfB54hzFTT7oolaTTn3Y-Jy9cM7W7L-fIlrj7Ls,35183
|
|
4
|
-
QTube/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
QTube/utils/checks.py,sha256=w6_Mwhh1lpxYEQ6cusKtiOTtvrhDHFjNU5Yx43V3Ee0,13310
|
|
6
|
-
QTube/utils/helpers.py,sha256=fl1_fePwy7ot-KRFB9Ieq7fMFtD8Q4TriK8cW17qPy0,9187
|
|
7
|
-
QTube/utils/parsing.py,sha256=H1uVtevbDF-6JWy42--pDvkBBHzAvxBgujIErQk54AI,11057
|
|
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=tjfY-ohrQvk1BznbsdwQUcnX0Wq7rlBK43C-ZWOE4iY,3953
|
|
11
|
-
QTube/utils/youtube/videos.py,sha256=YWhpLQVADr95lp1XMgnkg5z05YOxtBgpEauUz95zMeQ,20607
|
|
12
|
-
QTube-2.5.0.dist-info/LICENSE.txt,sha256=cIZNbD-BZYZPzWYHhtE-iUCasUxQIwWzALL9nZh32pQ,1098
|
|
13
|
-
QTube-2.5.0.dist-info/METADATA,sha256=5onw-FEmuyBiM62nsxA_iTUJCXSP8vc2j2ncI2Tjvkc,18001
|
|
14
|
-
QTube-2.5.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
15
|
-
QTube-2.5.0.dist-info/entry_points.txt,sha256=Q3SLDyRahuzrNnHC3UOkMH5gM_Um3eCLiLG4vSTPjqE,51
|
|
16
|
-
QTube-2.5.0.dist-info/top_level.txt,sha256=z6oXrT8BiTTZuygjsbfe-NE4rmkHlDK8euAL9m6BT4A,6
|
|
17
|
-
QTube-2.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|