Coverage for youtube.py: 100%
53 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-01 05:56 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-01 05:56 +0000
1# This file is part of Vallenato.fr.
2#
3# Vallenato.fr is free software: you can redistribute it and/or modify
4# it under the terms of the GNU Affero General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# Vallenato.fr is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU Affero General Public License for more details.
12#
13# You should have received a copy of the GNU Affero General Public License
14# along with Vallenato.fr. If not, see <http://www.gnu.org/licenses/>.
16# Adapted from:
17# https://github.com/youtube/api-samples/blob/master/python/my_uploads.py
18# https://github.com/youtube/api-samples/blob/master/python/upload_video.py
20import logging
21import os
23from googleapiclient.discovery import build
24from googleapiclient.errors import HttpError
25from oauth2client.client import flow_from_clientsecrets
26from oauth2client.file import Storage
27from oauth2client.tools import run_flow
29# HttpError is re-exported for website.py/tests - __all__ marks the import as
30# intentionally used, rather than needing a linter-specific suppression.
31__all__ = ["HttpError"]
33logger = logging.getLogger(__name__)
35# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains
36# the OAuth 2.0 information for this application, including its client_id and
37# client_secret. You can acquire an OAuth 2.0 client ID and client secret from
38# the {{ Google Cloud Console }} at
39# {{ https://cloud.google.com/console }}.
40# Please ensure that you have enabled the YouTube Data API for your project.
41# For more information about using OAuth2 to access the YouTube Data API, see:
42# https://developers.google.com/youtube/v3/guides/authentication
43# For more information about the client_secrets.json file format, see:
44# https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
45CLIENT_SECRETS_FILE = "client_secret.json"
47# This OAuth 2.0 access scope allows an application to view videos and playlists
48SCOPES = ["https://www.googleapis.com/auth/youtube.readonly"]
49API_SERVICE_NAME = "youtube"
50API_VERSION = "v3"
52# This variable defines a message to display if the CLIENT_SECRETS_FILE is
53# missing.
54MISSING_CLIENT_SECRETS_MESSAGE = f"""
55WARNING: Please configure OAuth 2.0
57To make this sample run you will need to populate the client_secrets.json file
58found at:
59 {os.path.abspath(os.path.join(os.path.dirname(__file__), CLIENT_SECRETS_FILE))}
60with information from the APIs Console
61https://console.developers.google.com
63For more information about the client_secrets.json file format, please visit:
64https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
65"""
68# Authorize the request and store authorization credentials.
69def yt_get_authenticated_service(args):
70 flow = flow_from_clientsecrets(
71 CLIENT_SECRETS_FILE, scope=SCOPES, message=MISSING_CLIENT_SECRETS_MESSAGE
72 )
73 storage = Storage("vallenato.fr-oauth2.json")
74 credentials = storage.get()
75 if credentials is None or credentials.invalid:
76 credentials = run_flow(flow, storage, args)
77 return build(
78 API_SERVICE_NAME, API_VERSION, credentials=credentials, cache_discovery=False
79 )
82def yt_get_my_uploads_list(youtube):
83 return "UU_8R235jg1ld6MCMOzz2khQ"
84 # # Retrieve the contentDetails part of the channel resource for the
85 # # authenticated user's channel.
86 # channels_response = youtube.channels().list(
87 # mine=True,
88 # part='contentDetails'
89 # ).execute()
90 #
91 # for channel in channels_response['items']:
92 # # From the API response, extract the playlist ID that identifies the list
93 # # of videos uploaded to the authenticated user's channel.
94 # return channel['contentDetails']['relatedPlaylists']['uploads']
95 # return None
98def yt_list_my_uploaded_videos(uploads_playlist_id, youtube):
99 uploaded_videos = []
100 # Retrieve the list of videos uploaded to the authenticated user's channel.
101 playlistitems_list_request = youtube.playlistItems().list(
102 playlistId=uploads_playlist_id, part="contentDetails"
103 )
105 logger.debug(f"Videos in list {uploads_playlist_id}")
106 while playlistitems_list_request:
107 playlistitems_list_response = playlistitems_list_request.execute()
109 videoIds = ",".join(
110 playlist_item["contentDetails"]["videoId"]
111 for playlist_item in playlistitems_list_response["items"]
112 )
114 # To get the videos' tags, we need to do an extra query
115 videos_list_request = youtube.videos().list(id=videoIds, part="snippet,status")
117 while videos_list_request:
118 videos_list_response = videos_list_request.execute()
120 for video in videos_list_response["items"]:
121 # Only keep Public videos
122 if video["status"]["privacyStatus"] != "public":
123 continue
124 # Skip videos tagged as "no-website" or "Tutorial"
125 if "tags" in video["snippet"] and (
126 "no-website" in video["snippet"]["tags"]
127 or "Tutorial" in video["snippet"]["tags"]
128 ):
129 continue
130 vid = {}
131 vid["id"] = video["id"]
132 vid["title"] = video["snippet"]["title"]
133 vid["description"] = video["snippet"]["description"]
134 try:
135 vid["tags"] = video["snippet"]["tags"]
136 except KeyError:
137 vid["tags"] = []
138 vid["publishedAt"] = video["snippet"]["publishedAt"]
139 # Sizes: default, medium, high, standard, maxres
140 vid["thumbnail"] = video["snippet"]["thumbnails"]["medium"]
142 uploaded_videos.append(vid)
143 logger.debug(vid)
144 videos_list_request = youtube.videos().list_next(
145 videos_list_request, videos_list_response
146 )
148 playlistitems_list_request = youtube.playlistItems().list_next(
149 playlistitems_list_request, playlistitems_list_response
150 )
151 return uploaded_videos