youtube_up
Installation
pip install youtube-up
Examples
Upload a video
from youtube_up import AllowCommentsEnum, Metadata, PrivacyEnum, YTUploaderSession
uploader = YTUploaderSession.from_cookies_txt("cookies/cookies.txt")
metadata = Metadata(
title="Video title",
description="Video description",
privacy=PrivacyEnum.PUBLIC,
made_for_kids=False,
allow_comments_mode=AllowCommentsEnum.HOLD_ALL,
)
uploader.upload("video.webm", metadata)
Note that for Enum-type parameters we can either pass the Enum itself (as shown above), or the Enum value, or the Enum key, as a string. For example, instead of writing
allow_comments_mode=AllowCommentsEnum.HOLD_ALL
we could instead write allow_comments_mode="HOLD_ALL"
or allow_comments_mode="APPROVED_COMMENTS"
Upload multiple videos
from youtube_up import Metadata, YTUploaderSession
uploader = YTUploaderSession.from_cookies_txt("cookies/cookies.txt")
metadata_1 = Metadata(
title="Video 1",
)
metadata_2 = Metadata(
title="Video 2",
)
uploader.upload("video1.webm", metadata_1)
uploader.upload("video2.webm", metadata_2)
Upload to a new or existing playlist
from youtube_up import Metadata, YTUploaderSession, Playlist
uploader = YTUploaderSession.from_cookies_txt("cookies/cookies.txt")
metadata = Metadata(
title="Video 1",
playlists=[
Playlist(
"Songs by me",
description="Playlist that will only be created if "
"no playlist exists with the title 'Songs by me'",
create_if_title_doesnt_exist=True,
create_if_title_exists=False,
),
Playlist(
"test playlist",
description="Playlist that video will be added to "
"only if it exists already. This description does "
"nothing.",
create_if_title_doesnt_exist=False,
create_if_title_exists=False,
),
Playlist(
"Album",
description="Playlist will be created even if there"
" is already a playlist with the name 'Album'"
create_if_title_doesnt_exist=True,
create_if_title_exists=True,
),
],
)
uploader.upload("video.webm", metadata)
CLI
youtube-up comes with a CLI app for uploading videos. For example, if we wanted to
create a public video with the title "Video title", we would execute the following command:
youtube-up video video.webm --title="Video title" --cookies_file="cookies/cookies.txt" --privacy="PUBLIC"
The app can also take a JSON file as input. For example, the following JSON file would upload one video to a new or existing playlist called "Music" and one video which is set to premiere on December 25th, 2023 at 5 PM (local time).
[
{
"file": "song.webm",
"metadata": {
"title": "New song",
"privacy": "PUBLIC",
"playlists": [
{
"title": "Music"
}
]
}
},
{
"file": "premiere.webm",
"metadata": {
"title": "Special Announcement",
"scheduled_upload": "2023-12-25T17:00:00",
"premiere_countdown_duration": "ONE_MIN",
"premiere_theme": "BRIGHT"
}
}
]
If we wanted the video to premiere at 5 PM GMT, would could have written "2023-12-25T17:00:00+00:00" instead. We then run
youtube-up json metadata.json --cookies_file="cookies/cookies.txt"
to upload these videos.
1""" 2# Installation 3 4`pip install youtube-up` 5 6# Examples 7 8## Upload a video 9```python 10from youtube_up import AllowCommentsEnum, Metadata, PrivacyEnum, YTUploaderSession 11 12uploader = YTUploaderSession.from_cookies_txt("cookies/cookies.txt") 13metadata = Metadata( 14 title="Video title", 15 description="Video description", 16 privacy=PrivacyEnum.PUBLIC, 17 made_for_kids=False, 18 allow_comments_mode=AllowCommentsEnum.HOLD_ALL, 19) 20uploader.upload("video.webm", metadata) 21``` 22Note that for Enum-type parameters we can either pass the Enum itself (as shown above), 23or the Enum value, or the Enum key, as a string. For example, instead of writing 24 25`allow_comments_mode=AllowCommentsEnum.HOLD_ALL` 26 27we could instead write `allow_comments_mode="HOLD_ALL"` 28or `allow_comments_mode="APPROVED_COMMENTS"` 29 30## Upload multiple videos 31```python 32from youtube_up import Metadata, YTUploaderSession 33 34uploader = YTUploaderSession.from_cookies_txt("cookies/cookies.txt") 35metadata_1 = Metadata( 36 title="Video 1", 37) 38metadata_2 = Metadata( 39 title="Video 2", 40) 41uploader.upload("video1.webm", metadata_1) 42uploader.upload("video2.webm", metadata_2) 43``` 44 45## Upload to a new or existing playlist 46```python 47from youtube_up import Metadata, YTUploaderSession, Playlist 48 49uploader = YTUploaderSession.from_cookies_txt("cookies/cookies.txt") 50metadata = Metadata( 51 title="Video 1", 52 playlists=[ 53 Playlist( 54 "Songs by me", 55 description="Playlist that will only be created if " 56 "no playlist exists with the title 'Songs by me'", 57 create_if_title_doesnt_exist=True, 58 create_if_title_exists=False, 59 ), 60 Playlist( 61 "test playlist", 62 description="Playlist that video will be added to " 63 "only if it exists already. This description does " 64 "nothing.", 65 create_if_title_doesnt_exist=False, 66 create_if_title_exists=False, 67 ), 68 Playlist( 69 "Album", 70 description="Playlist will be created even if there" 71 " is already a playlist with the name 'Album'" 72 create_if_title_doesnt_exist=True, 73 create_if_title_exists=True, 74 ), 75 ], 76) 77uploader.upload("video.webm", metadata) 78``` 79 80## CLI 81youtube-up comes with a CLI app for uploading videos. For example, if we wanted to 82create a public video with the title "Video title", we would execute the following command: 83`youtube-up video video.webm --title="Video title" --cookies_file="cookies/cookies.txt" --privacy="PUBLIC"` 84 85The app can also take a JSON file as input. For example, the following JSON file would upload 86one video to a new or existing playlist called "Music" and one video which is set to premiere 87on December 25th, 2023 at 5 PM (local time). 88 89```json 90[ 91 { 92 "file": "song.webm", 93 "metadata": { 94 "title": "New song", 95 "privacy": "PUBLIC", 96 "playlists": [ 97 { 98 "title": "Music" 99 } 100 ] 101 } 102 }, 103 { 104 "file": "premiere.webm", 105 "metadata": { 106 "title": "Special Announcement", 107 "scheduled_upload": "2023-12-25T17:00:00", 108 "premiere_countdown_duration": "ONE_MIN", 109 "premiere_theme": "BRIGHT" 110 } 111 } 112] 113``` 114 115If we wanted the video to premiere at 5 PM GMT, would could have written "2023-12-25T17:00:00+00:00" 116instead. We then run 117 118`youtube-up json metadata.json --cookies_file="cookies/cookies.txt"` 119 120to upload these videos. 121""" 122 123 124from .metadata import * 125from .uploader import * 126 127__all__ = uploader.__all__ + metadata.__all__
50class YTUploaderSession: 51 """ 52 Class for uploading YouTube videos to a single channel 53 """ 54 55 _delegated_session_id_regex = re.compile(r'"DELEGATED_SESSION_ID":"([^"]*)"') 56 _innertube_api_key_regex = re.compile(r'"INNERTUBE_API_KEY":"([^"]*)"') 57 _session_index_regex = re.compile(r'"SESSION_INDEX":"([^"]*)"') 58 _channel_id_regex = re.compile(r"https://studio.youtube.com/channel/([^/]*)/*") 59 _progress_steps = { 60 "start": 0, 61 "get_session_data": 10, 62 "get_upload_url": 20, 63 "upload_video": 70, 64 "get_session_token": 80, 65 "create_video": 90, 66 "upload_thumbnail": 95, 67 "finish": 100, 68 } 69 70 _session_token: str 71 _cookies: FileCookieJar 72 _session: requests.Session 73 74 def __init__(self, cookie_jar: FileCookieJar, webdriver_path: Optional[str] = None, selenium_timeout: float = 60): 75 """Create YTUploaderSession from generic FileCookieJar 76 77 Args: 78 cookie_jar (FileCookieJar): FileCookieJar. Must have save(), load(), 79 and set_cookie(http.cookiejar.Cookie) methods 80 webdriver_path (str, optional): Optional path to geckodriver or chromedriver executable 81 selenium_timeout (float, optional): Timeout to wait for grst request. Defaults to 60 seconds 82 """ 83 self._session_token = "" 84 self._webdriver_path = webdriver_path 85 self._selenium_timeout = selenium_timeout 86 87 # load cookies and init session 88 self._cookies = cookie_jar 89 self._cookies.load(ignore_discard=True, ignore_expires=True) 90 self._session = requests.Session() 91 for cookie in self._cookies: 92 if cookie.name == "SESSION_TOKEN": 93 self._session_token = cookie.value 94 else: 95 self._session.cookies.set_cookie(copy.copy(cookie)) 96 self._session.headers = { 97 "Authorization": f"SAPISIDHASH {self._generateSAPISIDHASH(self._session.cookies['SAPISID'])}", 98 "x-origin": "https://studio.youtube.com", 99 "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0", 100 } 101 102 @classmethod 103 def from_cookies_txt(cls, cookies_txt_path: str, webdriver_path: Optional[str] = None, selenium_timeout: float = 60): 104 """Create YTUploaderSession from cookies.txt file 105 106 Args: 107 cookies_txt_path (str): Path to Netscape cookies format file 108 webdriver_path (str, optional): Optional path to geckodriver or chromedriver executable 109 selenium_timeout (float, optional): Timeout to wait for grst request. Defaults to 60 seconds 110 """ 111 cj = MozillaCookieJar(cookies_txt_path) 112 return cls(cj, webdriver_path, selenium_timeout) 113 114 def upload( 115 self, 116 file_path: str, 117 metadata: Metadata, 118 progress_callback: Callable[[str, float], None] = lambda step, percent: None, 119 ) -> str: 120 """Upload a video 121 122 Args: 123 file_path (str): Path to video file 124 metadata (Metadata): Metadata of video to set when uploaded 125 progress_callback (Callable[[str, float], None], optional): Optional progress callback. 126 Callback receives what step uploader is on and what the total percentage of the upload 127 progress is (defined by YTUploaderSession._progress_steps). 128 129 Returns: 130 str: ID of video uploaded 131 """ 132 try: 133 metadata.validate() 134 except ValueError as ex: 135 raise YTUploaderException(f"Validation error: {ex}") from ex 136 progress_callback("start", self._progress_steps["start"]) 137 data = YTUploaderVideoData() 138 self._get_session_data(data) 139 progress_callback("get_session_data", self._progress_steps["get_session_data"]) 140 url = self._get_video_upload_url(data) 141 progress_callback("get_upload_url", self._progress_steps["get_upload_url"]) 142 scotty_resource_id = self._upload_file( 143 url, file_path, progress_callback, "get_upload_url", "upload_video" 144 ) 145 progress_callback("upload_video", self._progress_steps["upload_video"]) 146 try: 147 data.encrypted_video_id = self._create_video( 148 scotty_resource_id, metadata, data 149 ) 150 except requests.HTTPError as ex: 151 if ex.response.status_code == 400: 152 # could be bad session token, try to get new one 153 self._get_session_token() 154 progress_callback( 155 "get_session_token", self._progress_steps["get_session_token"] 156 ) 157 data.encrypted_video_id = self._create_video( 158 scotty_resource_id, metadata, data 159 ) 160 progress_callback("create_video", self._progress_steps["create_video"]) 161 162 # set thumbnail 163 if metadata.thumbnail is not None: 164 url = self._get_upload_url_thumbnail(data) 165 data.thumbnail_scotty_id = self._upload_file( 166 url, 167 metadata.thumbnail, 168 progress_callback, 169 "create_video", 170 "upload_thumbnail", 171 ) 172 data.thumbnail_format = self._get_thumbnail_format(metadata.thumbnail) 173 174 # playlists 175 if metadata.playlists: 176 playlists = self._get_creator_playlists(data) 177 if metadata.playlist_ids is None: 178 metadata.playlist_ids = [] 179 for playlist in metadata.playlists: 180 exists = playlist.title in playlists 181 if (playlist.create_if_title_exists and exists) or ( 182 playlist.create_if_title_doesnt_exist and not exists 183 ): 184 playlist_id = self._create_playlist(playlist, data) 185 metadata.playlist_ids.append(playlist_id) 186 elif exists: 187 metadata.playlist_ids.append(playlists[playlist.title]) 188 # captions 189 if metadata.captions_files: 190 for caption_file in metadata.captions_files: 191 if caption_file.language is None: 192 caption_file.language = metadata.audio_language 193 self._update_captions( 194 caption_file, 195 data 196 ) 197 198 self._update_metadata(metadata, data) 199 # save cookies 200 for cookie in self._session.cookies: 201 self._cookies.set_cookie(cookie) 202 self._cookies.save() 203 progress_callback("finish", self._progress_steps["finish"]) 204 return data.encrypted_video_id 205 206 def has_valid_cookies(self) -> bool: 207 """Check if cookies are valid 208 209 Returns: 210 bool: True if we are able to log in to YouTube with the given cookies 211 """ 212 r = self._session.get("https://youtube.com/upload") 213 214 return "studio.youtube.com/channel" in r.url 215 216 def _get_thumbnail_format(self, filename: str) -> ThumbnailFormatEnum: 217 ext = filename.split(".")[-1] 218 if ext in ("jpg", "jpeg", "jfif", "pjpeg", "pjp"): 219 return ThumbnailFormatEnum.JPG 220 if ext in ("png",): 221 return ThumbnailFormatEnum.PNG 222 raise YTUploaderException( 223 f"Unknown format for thumbnail with extension '{ext}'. Only JPEG and PNG allowed" 224 ) 225 226 def _get_session_token(self): 227 try: 228 # try firefox 229 options = webdriver.FirefoxOptions() 230 options.add_argument("--headless") 231 if self._webdriver_path: 232 service = FirefoxService(self._webdriver_path) 233 driver = webdriver.Firefox(options=options, service=service) 234 else: 235 driver = webdriver.Firefox(options=options) 236 except Exception: 237 try: 238 # try chrome 239 options = webdriver.ChromeOptions() 240 options.add_argument("--headless=new") 241 if self._webdriver_path: 242 service = ChromeService(self._webdriver_path) 243 driver = webdriver.Chrome(options=options, service=service) 244 else: 245 driver = webdriver.Chrome(options=options) 246 except Exception: 247 raise YTUploaderException( 248 "Could not launch Firefox or Chrome. Make sure geckodriver or chromedriver is installed" 249 ) 250 251 driver.set_page_load_timeout(self._selenium_timeout) 252 253 driver.get("https://youtube.com") 254 255 for cookie in self._cookies: 256 if cookie.name != "SESSION_TOKEN": 257 driver.add_cookie(cookie.__dict__) 258 259 driver.get("https://youtube.com/upload") 260 261 if "studio.youtube.com/channel" not in driver.current_url: 262 driver.quit() 263 raise YTUploaderException( 264 "Could not log in to YouTube account. Try getting new cookies" 265 ) 266 267 r = driver.wait_for_request("studio.youtube.com/youtubei/v1/ars/grst", timeout=self._selenium_timeout) 268 response = r.response 269 r_json = json.loads( 270 decode(response.body, response.headers.get("Content-Encoding")) 271 ) 272 self._session_token = r_json["sessionToken"] 273 self._cookies.set_cookie( 274 Cookie( 275 None, 276 "SESSION_TOKEN", 277 self._session_token, 278 None, 279 False, 280 "", 281 False, 282 False, 283 "", 284 False, 285 False, 286 None, 287 False, 288 None, 289 None, 290 {}, 291 ) 292 ) 293 self._cookies.save() 294 driver.quit() 295 296 @staticmethod 297 def _generateUUID() -> str: 298 return str(uuid.uuid4()).upper() 299 300 @staticmethod 301 def _generateSAPISIDHASH(SAPISID) -> str: 302 timestamp = math.floor(time.time()) 303 msg = f"{timestamp} {SAPISID} {'https://studio.youtube.com'}" 304 hash = sha1(msg.encode("utf-8")).hexdigest() 305 return f"{timestamp}_{hash}" 306 307 def _get_session_data(self, data: YTUploaderVideoData): 308 r = self._session.get("https://youtube.com/upload") 309 310 if "studio.youtube.com/channel" not in r.url: 311 raise YTUploaderException( 312 "Could not log in to YouTube account. Try getting new cookies" 313 ) 314 315 data.channel_id = self._channel_id_regex.match(r.url).group(1) 316 data.innertube_api_key = self._innertube_api_key_regex.search(r.text).group(1) 317 m = self._delegated_session_id_regex.search(r.text) 318 data.delegated_session_id = m and m.group(1) 319 data.authuser = self._session_index_regex.search(r.text).group(1) 320 self._session.headers["X-Goog-AuthUser"] = data.authuser 321 322 def _get_upload_url(self, api_url: str, authuser: str, data: dict) -> str: 323 params = {"authuser": authuser} 324 headers = { 325 "x-goog-upload-command": "start", 326 "x-goog-upload-protocol": "resumable", 327 } 328 r = self._session.post( 329 api_url, 330 headers=headers, 331 params=params, 332 json=data, 333 ) 334 r.raise_for_status() 335 upload_url = r.headers["x-goog-upload-url"] 336 return upload_url 337 338 def _get_video_upload_url(self, data: YTUploaderVideoData) -> str: 339 data.front_end_upload_id = f"innertube_studio:{self._generateUUID()}:0" 340 return self._get_upload_url( 341 "https://upload.youtube.com/upload/studio", 342 data.authuser, 343 {"frontendUploadId": data.front_end_upload_id}, 344 ) 345 346 def _get_upload_url_thumbnail(self, data: YTUploaderVideoData) -> str: 347 return self._get_upload_url( 348 "https://upload.youtube.com/upload/studiothumbnail", data.authuser, {} 349 ) 350 351 def _get_creator_playlists(self, data: YTUploaderVideoData) -> Dict[str, str]: 352 params = {"key": data.innertube_api_key, "alt": "json"} 353 data = APIRequestListPlaylists.from_session_data( 354 data.channel_id, 355 self._session_token, 356 data.delegated_session_id 357 ).to_dict() 358 r = self._session.post( 359 "https://studio.youtube.com/youtubei/v1/creator/list_creator_playlists", 360 params=params, 361 json=data, 362 ) 363 r.raise_for_status() 364 return { 365 playlist["title"]: playlist["playlistId"] 366 for playlist in r.json().get("playlists", []) 367 } 368 369 def _create_playlist( 370 self, 371 playlist: Playlist, 372 data: YTUploaderVideoData, 373 ) -> str: 374 params = {"key": data.innertube_api_key, "alt": "json"} 375 data = APIRequestCreatePlaylist.from_session_data( 376 data.channel_id, self._session_token, data.delegated_session_id, playlist 377 ).to_dict() 378 r = self._session.post( 379 "https://studio.youtube.com/youtubei/v1/playlist/create", 380 params=params, 381 json=data, 382 ) 383 r.raise_for_status() 384 return r.json()["playlistId"] 385 386 def _update_captions( 387 self, 388 caption_file: CaptionsFile, 389 data: YTUploaderVideoData, 390 ): 391 params = {"key": data.innertube_api_key, "alt": "json"} 392 with open(caption_file.path, "rb") as f: 393 captions_b64 = "data:application/octet-stream;base64," + base64.b64encode(f.read()).decode("utf-8") 394 timestamp = str(time.time_ns()) 395 data = APIRequestUpdateCaptions.from_session_data( 396 data.channel_id, 397 self._session_token, 398 data.delegated_session_id, 399 data.encrypted_video_id, 400 caption_file.path, 401 captions_b64, 402 caption_file.language, 403 timestamp 404 ).to_dict() 405 r = self._session.post( 406 "https://studio.youtube.com/youtubei/v1/globalization/update_captions", 407 params=params, 408 json=data, 409 ) 410 r.raise_for_status() 411 412 def _upload_file( 413 self, 414 upload_url: str, 415 file_path: str, 416 progress_callback: Callable[[str, float], None], 417 prev_progress_step: str, 418 cur_progress_step: str, 419 ): 420 headers = { 421 "x-goog-upload-command": "upload, finalize", 422 "x-goog-upload-offset": "0", 423 } 424 425 with open(file_path, "rb") as f: 426 f.seek(0, os.SEEK_END) 427 size = f.tell() 428 f.seek(0) 429 bytes_sent = 0 430 431 def upload_callback(bytes: int): 432 nonlocal bytes_sent 433 bytes_sent += bytes 434 start_prog = self._progress_steps[prev_progress_step] 435 end_prog = self._progress_steps[cur_progress_step] 436 cur_prog = start_prog + (end_prog - start_prog) * (bytes_sent / size) 437 cur_prog = round(cur_prog, 1) 438 progress_callback(cur_progress_step, cur_prog) 439 440 wrapped_file = CallbackIOWrapper(upload_callback, f) 441 r = self._session.post(upload_url, headers=headers, data=wrapped_file) 442 443 r.raise_for_status() 444 return r.json()["scottyResourceId"] 445 446 def _create_video( 447 self, scotty_resource_id: str, metadata: Metadata, data: YTUploaderVideoData 448 ) -> str: 449 params = {"key": data.innertube_api_key, "alt": "json"} 450 data = APIRequestCreateVideo.from_session_data( 451 data.channel_id, 452 self._session_token, 453 data.delegated_session_id, 454 data.front_end_upload_id, 455 metadata, 456 scotty_resource_id, 457 ).to_dict() 458 r = self._session.post( 459 "https://studio.youtube.com/youtubei/v1/upload/createvideo", 460 params=params, 461 json=data, 462 ) 463 r.raise_for_status() 464 r = r.json() 465 if "videoId" not in r: 466 raise YTUploaderException( 467 f"Could not upload. Daily limit may have been reached. Response: {r}" 468 ) 469 return r["videoId"] 470 471 def _update_metadata(self, metadata: Metadata, data: YTUploaderVideoData): 472 params = {"key": data.innertube_api_key, "alt": "json"} 473 data = APIRequestUpdateMetadata.from_session_data( 474 data.channel_id, 475 self._session_token, 476 data.delegated_session_id, 477 data.encrypted_video_id, 478 metadata, 479 data.thumbnail_scotty_id, 480 data.thumbnail_format, 481 ).to_dict() 482 r = self._session.post( 483 "https://studio.youtube.com/youtubei/v1/video_manager/metadata_update", 484 params=params, 485 json=data, 486 ) 487 r.raise_for_status()
Class for uploading YouTube videos to a single channel
74 def __init__(self, cookie_jar: FileCookieJar, webdriver_path: Optional[str] = None, selenium_timeout: float = 60): 75 """Create YTUploaderSession from generic FileCookieJar 76 77 Args: 78 cookie_jar (FileCookieJar): FileCookieJar. Must have save(), load(), 79 and set_cookie(http.cookiejar.Cookie) methods 80 webdriver_path (str, optional): Optional path to geckodriver or chromedriver executable 81 selenium_timeout (float, optional): Timeout to wait for grst request. Defaults to 60 seconds 82 """ 83 self._session_token = "" 84 self._webdriver_path = webdriver_path 85 self._selenium_timeout = selenium_timeout 86 87 # load cookies and init session 88 self._cookies = cookie_jar 89 self._cookies.load(ignore_discard=True, ignore_expires=True) 90 self._session = requests.Session() 91 for cookie in self._cookies: 92 if cookie.name == "SESSION_TOKEN": 93 self._session_token = cookie.value 94 else: 95 self._session.cookies.set_cookie(copy.copy(cookie)) 96 self._session.headers = { 97 "Authorization": f"SAPISIDHASH {self._generateSAPISIDHASH(self._session.cookies['SAPISID'])}", 98 "x-origin": "https://studio.youtube.com", 99 "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0", 100 }
Create YTUploaderSession from generic FileCookieJar
Arguments:
- cookie_jar (FileCookieJar): FileCookieJar. Must have save(), load(), and set_cookie(http.cookiejar.Cookie) methods
- webdriver_path (str, optional): Optional path to geckodriver or chromedriver executable
- selenium_timeout (float, optional): Timeout to wait for grst request. Defaults to 60 seconds
114 def upload( 115 self, 116 file_path: str, 117 metadata: Metadata, 118 progress_callback: Callable[[str, float], None] = lambda step, percent: None, 119 ) -> str: 120 """Upload a video 121 122 Args: 123 file_path (str): Path to video file 124 metadata (Metadata): Metadata of video to set when uploaded 125 progress_callback (Callable[[str, float], None], optional): Optional progress callback. 126 Callback receives what step uploader is on and what the total percentage of the upload 127 progress is (defined by YTUploaderSession._progress_steps). 128 129 Returns: 130 str: ID of video uploaded 131 """ 132 try: 133 metadata.validate() 134 except ValueError as ex: 135 raise YTUploaderException(f"Validation error: {ex}") from ex 136 progress_callback("start", self._progress_steps["start"]) 137 data = YTUploaderVideoData() 138 self._get_session_data(data) 139 progress_callback("get_session_data", self._progress_steps["get_session_data"]) 140 url = self._get_video_upload_url(data) 141 progress_callback("get_upload_url", self._progress_steps["get_upload_url"]) 142 scotty_resource_id = self._upload_file( 143 url, file_path, progress_callback, "get_upload_url", "upload_video" 144 ) 145 progress_callback("upload_video", self._progress_steps["upload_video"]) 146 try: 147 data.encrypted_video_id = self._create_video( 148 scotty_resource_id, metadata, data 149 ) 150 except requests.HTTPError as ex: 151 if ex.response.status_code == 400: 152 # could be bad session token, try to get new one 153 self._get_session_token() 154 progress_callback( 155 "get_session_token", self._progress_steps["get_session_token"] 156 ) 157 data.encrypted_video_id = self._create_video( 158 scotty_resource_id, metadata, data 159 ) 160 progress_callback("create_video", self._progress_steps["create_video"]) 161 162 # set thumbnail 163 if metadata.thumbnail is not None: 164 url = self._get_upload_url_thumbnail(data) 165 data.thumbnail_scotty_id = self._upload_file( 166 url, 167 metadata.thumbnail, 168 progress_callback, 169 "create_video", 170 "upload_thumbnail", 171 ) 172 data.thumbnail_format = self._get_thumbnail_format(metadata.thumbnail) 173 174 # playlists 175 if metadata.playlists: 176 playlists = self._get_creator_playlists(data) 177 if metadata.playlist_ids is None: 178 metadata.playlist_ids = [] 179 for playlist in metadata.playlists: 180 exists = playlist.title in playlists 181 if (playlist.create_if_title_exists and exists) or ( 182 playlist.create_if_title_doesnt_exist and not exists 183 ): 184 playlist_id = self._create_playlist(playlist, data) 185 metadata.playlist_ids.append(playlist_id) 186 elif exists: 187 metadata.playlist_ids.append(playlists[playlist.title]) 188 # captions 189 if metadata.captions_files: 190 for caption_file in metadata.captions_files: 191 if caption_file.language is None: 192 caption_file.language = metadata.audio_language 193 self._update_captions( 194 caption_file, 195 data 196 ) 197 198 self._update_metadata(metadata, data) 199 # save cookies 200 for cookie in self._session.cookies: 201 self._cookies.set_cookie(cookie) 202 self._cookies.save() 203 progress_callback("finish", self._progress_steps["finish"]) 204 return data.encrypted_video_id
Upload a video
Arguments:
- file_path (str): Path to video file
- metadata (Metadata): Metadata of video to set when uploaded
- progress_callback (Callable[[str, float], None], optional): Optional progress callback. Callback receives what step uploader is on and what the total percentage of the upload progress is (defined by YTUploaderSession._progress_steps).
Returns:
str: ID of video uploaded
YouTube uploader exception
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- args
383@dataclass_json 384@dataclass 385class Metadata: 386 """Metadata of video to upload""" 387 388 title: str 389 """Title. Max length 100. Cannot contain < or > characters""" 390 391 description: str = "" 392 """Description. Max length 5000. Cannot contain < or > characters""" 393 394 privacy: PrivacyEnum = PrivacyEnum.PRIVATE 395 """Privacy. Possible values: PUBLIC, UNLISTED, PRIVATE""" 396 397 made_for_kids: bool = False 398 """Made for kids. If true comments will be disabled""" 399 400 tags: List[str] = () 401 """List of tags""" 402 403 # optional metadata for update_metadata 404 scheduled_upload: Optional[datetime.datetime] = field( 405 default=None, 406 metadata=config( 407 decoder=lambda x: datetime.datetime.fromisoformat(x) if x is not None else None, 408 encoder=lambda x: datetime.datetime.isoformat(x) if x is not None else None, 409 mm_field=mm_field.DateTime("iso", allow_none=True) 410 ), 411 ) 412 """ 413 Date to make upload public. If set, video will be set to private until the date, unless video 414 is a premiere in which case it will be set to public. Video will not be a premiere unless both 415 premiere_countdown_duration and premiere_theme are set 416 """ 417 418 premiere_countdown_duration: Optional[PremiereDurationEnum] = None 419 """Duration of premiere countdown in seconds. Possible values: 60, 120, 180, 240, 300""" 420 421 premiere_theme: Optional[PremiereThemeEnum] = None 422 """ 423 Theme of premiere countdown. Possible values: CLASSIC, ALTERNATIVE, AMBIENT, BRIGHT, CALM, 424 CINEMATIC, CONTEMPORARY, DRAMATIC, FUNKY, GENTLE, HAPPY, INSPIRATIONAL, KIDS, SCI_FI, SPORTS 425 """ 426 427 playlist_ids: Optional[List[str]] = None 428 """List of existing playlist IDs to add video to""" 429 430 playlists: Optional[List[Playlist]] = None 431 """List of playlists to create and/or add video to""" 432 433 thumbnail: Optional[str] = None 434 """Path to thumbnail file to upload""" 435 436 publish_to_feed: Optional[bool] = None 437 """Whether to notify subscribers""" 438 439 category: Optional[CategoryEnum] = None 440 """Category. Category-specific metadata is not supported yet. Possible values: FILM_ANIMATION, 441 AUTOS_VEHICLES, MUSIC, PETS_ANIMALS, SPORTS, TRAVEL_EVENTS, GAMING, PEOPLE_BLOGS, COMEDY, 442 ENTERTAINMENT, NEWS_POLITICS, HOWTO_STYLE, EDUCATION, SCIENCE_TECH, NONPROFITS_ACTIVISM""" 443 444 auto_chapter: Optional[bool] = None 445 """Whether to use automatic video chapters""" 446 447 auto_places: Optional[bool] = None 448 """Whether to use automatic places""" 449 450 auto_concepts: Optional[bool] = None 451 """Whether to use automatic concepts""" 452 453 has_product_placement: Optional[bool] = None 454 """Whether video has product placement""" 455 456 show_product_placement_overlay: Optional[bool] = None 457 """Whether to show product placement overlay""" 458 459 recorded_date: Optional[datetime.date] = field( 460 default=None, 461 metadata=config( 462 decoder=lambda x: datetime.date.fromisoformat(x) if x is not None else None, 463 encoder=lambda x: datetime.date.isoformat(x) if x is not None else None, 464 mm_field=mm_field.Date("iso", allow_none=True) 465 ), 466 ) 467 """Day, month, and year that video was recorded""" 468 469 restricted_to_over_18: Optional[bool] = None 470 """Whether video is age restricted""" 471 472 audio_language: Optional[LanguageEnum] = None 473 """Language of audio""" 474 475 captions_files: Optional[List[CaptionsFile]] = None 476 """Path to captions files (.srt)""" 477 478 license: Optional[LicenseEnum] = None 479 """License. Possible values: STANDARD, CREATIVE_COMMONS""" 480 481 allow_comments: Optional[bool] = None 482 """Whether to allow comments""" 483 484 allow_comments_mode: Optional[AllowCommentsEnum] = None 485 """Comment filtering mode. Possible values: ALL_COMMENTS, HOLD_INAPPROPRIATE, 486 HOLD_INAPPROPRIATE_STRICT, HOLD_ALL""" 487 488 can_view_ratings: Optional[bool] = None 489 """Whether video likes/dislikes can be seen""" 490 491 comments_sort_order: Optional[CommentsSortOrderEnum] = None 492 """Default comment sort order. Possible values: LATEST, TOP""" 493 494 allow_embedding: Optional[bool] = None 495 """Whether to allow embedding on 3rd party sites""" 496 497 def validate(self): 498 """Raises error if metadata is invalid""" 499 if self.premiere_countdown_duration is not None or self.premiere_theme is not None: 500 if None in (self.premiere_countdown_duration, self.premiere_theme, self.scheduled_upload): 501 raise ValueError( 502 "If trying to upload a premiere, premiere_countdown_duration, " 503 "premiere_theme, and scheduled_upload must be set" 504 ) 505 506 if self.captions_files is not None: 507 for caption_file in self.captions_files: 508 if caption_file.language is None and self.audio_language is None: 509 raise ValueError( 510 "Must either specify captions file language or audio_language" 511 ) 512 513 if self.restricted_to_over_18 and self.made_for_kids: 514 raise ValueError( 515 "Video cannot be made for kids and also restricted to over 18" 516 ) 517 518 if len(self.title) > 100: 519 raise ValueError("Title must be at most 100 characters long") 520 521 if len(self.description) > 5000: 522 raise ValueError("Description must be at most 5000 characters long") 523 524 if any(c in s for c in "<>" for s in (self.title, self.description)): 525 raise ValueError("Title and description cannot contain angled brackets") 526 527 528 errors = self.schema().validate(self.to_dict()) 529 if errors: 530 raise ValueError(f"{errors}")
Metadata of video to upload
Date to make upload public. If set, video will be set to private until the date, unless video is a premiere in which case it will be set to public. Video will not be a premiere unless both premiere_countdown_duration and premiere_theme are set
Theme of premiere countdown. Possible values: CLASSIC, ALTERNATIVE, AMBIENT, BRIGHT, CALM, CINEMATIC, CONTEMPORARY, DRAMATIC, FUNKY, GENTLE, HAPPY, INSPIRATIONAL, KIDS, SCI_FI, SPORTS
Category. Category-specific metadata is not supported yet. Possible values: FILM_ANIMATION, AUTOS_VEHICLES, MUSIC, PETS_ANIMALS, SPORTS, TRAVEL_EVENTS, GAMING, PEOPLE_BLOGS, COMEDY, ENTERTAINMENT, NEWS_POLITICS, HOWTO_STYLE, EDUCATION, SCIENCE_TECH, NONPROFITS_ACTIVISM
Comment filtering mode. Possible values: ALL_COMMENTS, HOLD_INAPPROPRIATE, HOLD_INAPPROPRIATE_STRICT, HOLD_ALL
497 def validate(self): 498 """Raises error if metadata is invalid""" 499 if self.premiere_countdown_duration is not None or self.premiere_theme is not None: 500 if None in (self.premiere_countdown_duration, self.premiere_theme, self.scheduled_upload): 501 raise ValueError( 502 "If trying to upload a premiere, premiere_countdown_duration, " 503 "premiere_theme, and scheduled_upload must be set" 504 ) 505 506 if self.captions_files is not None: 507 for caption_file in self.captions_files: 508 if caption_file.language is None and self.audio_language is None: 509 raise ValueError( 510 "Must either specify captions file language or audio_language" 511 ) 512 513 if self.restricted_to_over_18 and self.made_for_kids: 514 raise ValueError( 515 "Video cannot be made for kids and also restricted to over 18" 516 ) 517 518 if len(self.title) > 100: 519 raise ValueError("Title must be at most 100 characters long") 520 521 if len(self.description) > 5000: 522 raise ValueError("Description must be at most 5000 characters long") 523 524 if any(c in s for c in "<>" for s in (self.title, self.description)): 525 raise ValueError("Title and description cannot contain angled brackets") 526 527 528 errors = self.schema().validate(self.to_dict()) 529 if errors: 530 raise ValueError(f"{errors}")
Raises error if metadata is invalid
27 def to_json(self, 28 *, 29 skipkeys: bool = False, 30 ensure_ascii: bool = True, 31 check_circular: bool = True, 32 allow_nan: bool = True, 33 indent: Optional[Union[int, str]] = None, 34 separators: Optional[Tuple[str, str]] = None, 35 default: Optional[Callable] = None, 36 sort_keys: bool = False, 37 **kw) -> str: 38 return json.dumps(self.to_dict(encode_json=False), 39 cls=_ExtendedEncoder, 40 skipkeys=skipkeys, 41 ensure_ascii=ensure_ascii, 42 check_circular=check_circular, 43 allow_nan=allow_nan, 44 indent=indent, 45 separators=separators, 46 default=default, 47 sort_keys=sort_keys, 48 **kw)
50 @classmethod 51 def from_json(cls: Type[A], 52 s: JsonData, 53 *, 54 parse_float=None, 55 parse_int=None, 56 parse_constant=None, 57 infer_missing=False, 58 **kw) -> A: 59 kvs = json.loads(s, 60 parse_float=parse_float, 61 parse_int=parse_int, 62 parse_constant=parse_constant, 63 **kw) 64 return cls.from_dict(kvs, infer_missing=infer_missing)
76 @classmethod 77 def schema(cls: Type[A], 78 *, 79 infer_missing: bool = False, 80 only=None, 81 exclude=(), 82 many: bool = False, 83 context=None, 84 load_only=(), 85 dump_only=(), 86 partial: bool = False, 87 unknown=None) -> "SchemaType[A]": 88 Schema = build_schema(cls, DataClassJsonMixin, infer_missing, partial) 89 90 if unknown is None: 91 undefined_parameter_action = _undefined_parameter_action_safe(cls) 92 if undefined_parameter_action is not None: 93 # We can just make use of the same-named mm keywords 94 unknown = undefined_parameter_action.name.lower() 95 96 return Schema(only=only, 97 exclude=exclude, 98 many=many, 99 context=context, 100 load_only=load_only, 101 dump_only=dump_only, 102 partial=partial, 103 unknown=unknown)
349@dataclass_json 350@dataclass 351class Playlist: 352 """Metadata of playlist to create and/or add video to""" 353 354 title: str 355 """Title. Max length 150""" 356 357 description: str = "" 358 """Description. Max length 5000""" 359 360 privacy: PrivacyEnum = PrivacyEnum.PUBLIC 361 """Privacy. Possible values: PUBLIC, UNLISTED, PRIVATE""" 362 363 create_if_title_exists: bool = False 364 """Whether to create playlist if a playlist with the same 365 title already exists on the channel""" 366 367 create_if_title_doesnt_exist: bool = True 368 """Whether to create playlist if there is no playlist with the same title"""
Metadata of playlist to create and/or add video to
Whether to create playlist if a playlist with the same title already exists on the channel
Whether to create playlist if there is no playlist with the same title
27 def to_json(self, 28 *, 29 skipkeys: bool = False, 30 ensure_ascii: bool = True, 31 check_circular: bool = True, 32 allow_nan: bool = True, 33 indent: Optional[Union[int, str]] = None, 34 separators: Optional[Tuple[str, str]] = None, 35 default: Optional[Callable] = None, 36 sort_keys: bool = False, 37 **kw) -> str: 38 return json.dumps(self.to_dict(encode_json=False), 39 cls=_ExtendedEncoder, 40 skipkeys=skipkeys, 41 ensure_ascii=ensure_ascii, 42 check_circular=check_circular, 43 allow_nan=allow_nan, 44 indent=indent, 45 separators=separators, 46 default=default, 47 sort_keys=sort_keys, 48 **kw)
50 @classmethod 51 def from_json(cls: Type[A], 52 s: JsonData, 53 *, 54 parse_float=None, 55 parse_int=None, 56 parse_constant=None, 57 infer_missing=False, 58 **kw) -> A: 59 kvs = json.loads(s, 60 parse_float=parse_float, 61 parse_int=parse_int, 62 parse_constant=parse_constant, 63 **kw) 64 return cls.from_dict(kvs, infer_missing=infer_missing)
76 @classmethod 77 def schema(cls: Type[A], 78 *, 79 infer_missing: bool = False, 80 only=None, 81 exclude=(), 82 many: bool = False, 83 context=None, 84 load_only=(), 85 dump_only=(), 86 partial: bool = False, 87 unknown=None) -> "SchemaType[A]": 88 Schema = build_schema(cls, DataClassJsonMixin, infer_missing, partial) 89 90 if unknown is None: 91 undefined_parameter_action = _undefined_parameter_action_safe(cls) 92 if undefined_parameter_action is not None: 93 # We can just make use of the same-named mm keywords 94 unknown = undefined_parameter_action.name.lower() 95 96 return Schema(only=only, 97 exclude=exclude, 98 many=many, 99 context=context, 100 load_only=load_only, 101 dump_only=dump_only, 102 partial=partial, 103 unknown=unknown)
371@dataclass_json 372@dataclass 373class CaptionsFile: 374 """Subtitles file""" 375 376 path: str 377 """Path to .srt file""" 378 379 language: Optional[LanguageEnum] = None 380 """Language of captions. If None, language will default to audio language"""
Subtitles file
27 def to_json(self, 28 *, 29 skipkeys: bool = False, 30 ensure_ascii: bool = True, 31 check_circular: bool = True, 32 allow_nan: bool = True, 33 indent: Optional[Union[int, str]] = None, 34 separators: Optional[Tuple[str, str]] = None, 35 default: Optional[Callable] = None, 36 sort_keys: bool = False, 37 **kw) -> str: 38 return json.dumps(self.to_dict(encode_json=False), 39 cls=_ExtendedEncoder, 40 skipkeys=skipkeys, 41 ensure_ascii=ensure_ascii, 42 check_circular=check_circular, 43 allow_nan=allow_nan, 44 indent=indent, 45 separators=separators, 46 default=default, 47 sort_keys=sort_keys, 48 **kw)
50 @classmethod 51 def from_json(cls: Type[A], 52 s: JsonData, 53 *, 54 parse_float=None, 55 parse_int=None, 56 parse_constant=None, 57 infer_missing=False, 58 **kw) -> A: 59 kvs = json.loads(s, 60 parse_float=parse_float, 61 parse_int=parse_int, 62 parse_constant=parse_constant, 63 **kw) 64 return cls.from_dict(kvs, infer_missing=infer_missing)
76 @classmethod 77 def schema(cls: Type[A], 78 *, 79 infer_missing: bool = False, 80 only=None, 81 exclude=(), 82 many: bool = False, 83 context=None, 84 load_only=(), 85 dump_only=(), 86 partial: bool = False, 87 unknown=None) -> "SchemaType[A]": 88 Schema = build_schema(cls, DataClassJsonMixin, infer_missing, partial) 89 90 if unknown is None: 91 undefined_parameter_action = _undefined_parameter_action_safe(cls) 92 if undefined_parameter_action is not None: 93 # We can just make use of the same-named mm keywords 94 unknown = undefined_parameter_action.name.lower() 95 96 return Schema(only=only, 97 exclude=exclude, 98 many=many, 99 context=context, 100 load_only=load_only, 101 dump_only=dump_only, 102 partial=partial, 103 unknown=unknown)
302class PremiereThemeEnum(str, Enum): 303 CLASSIC = "VIDEO_PREMIERE_INTRO_THEME_DEFAULT" 304 ALTERNATIVE = "VIDEO_PREMIERE_INTRO_THEME_ALTERNATIVE" 305 AMBIENT = "VIDEO_PREMIERE_INTRO_THEME_AMBIENT" 306 BRIGHT = "VIDEO_PREMIERE_INTRO_THEME_BRIGHT" 307 CALM = "VIDEO_PREMIERE_INTRO_THEME_CALM" 308 CINEMATIC = "VIDEO_PREMIERE_INTRO_THEME_CINEMATIC" 309 CONTEMPORARY = "VIDEO_PREMIERE_INTRO_THEME_CONTEMPORARY" 310 DRAMATIC = "VIDEO_PREMIERE_INTRO_THEME_DRAMATIC" 311 FUNKY = "VIDEO_PREMIERE_INTRO_THEME_FUNKY" 312 GENTLE = "VIDEO_PREMIERE_INTRO_THEME_GENTLE" 313 HAPPY = "VIDEO_PREMIERE_INTRO_THEME_HAPPY" 314 INSPIRATIONAL = "VIDEO_PREMIERE_INTRO_THEME_INSPIRATIONAL" 315 KIDS = "VIDEO_PREMIERE_INTRO_THEME_KIDS" 316 SCI_FI = "VIDEO_PREMIERE_INTRO_THEME_SCI_FI" 317 SPORTS = "VIDEO_PREMIERE_INTRO_THEME_SPORTS"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
294class PremiereDurationEnum(str, Enum): 295 ONE_MIN = "60" 296 TWO_MIN = "120" 297 THREE_MIN = "180" 298 FOUR_MIN = "240" 299 FIVE_MIN = "300"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
289class ThumbnailFormatEnum(str, Enum): 290 PNG = "CUSTOM_THUMBNAIL_IMAGE_FORMAT_PNG" 291 JPG = "CUSTOM_THUMBNAIL_IMAGE_FORMAT_JPEG"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
284class CommentsSortOrderEnum(str, Enum): 285 LATEST = "MDE_COMMENT_SORT_ORDER_LATEST" 286 TOP = "MDE_COMMENT_SORT_ORDER_TOP"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
277class AllowCommentsEnum(str, Enum): 278 ALL_COMMENTS = "ALL_COMMENTS" 279 HOLD_INAPPROPRIATE = "AUTOMATED_COMMENTS" 280 HOLD_INAPPROPRIATE_STRICT = "AUTO_MODERATED_COMMENTS_HOLD_MORE" 281 HOLD_ALL = "APPROVED_COMMENTS"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
272class LicenseEnum(str, Enum): 273 STANDARD = "standard" 274 CREATIVE_COMMONS = "creative_commons"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
35class LanguageEnum(str, Enum): 36 NOT_APPLICABLE = "zxx" 37 ABKHAZIAN = "ab" 38 AFAR = "aa" 39 AFRIKAANS = "af" 40 AKAN = "ak" 41 AKKADIAN = "akk" 42 ALBANIAN = "sq" 43 AMERICAN_SIGN_LANGUAGE = "ase" 44 AMHARIC = "am" 45 ARABIC = "ar" 46 ARAMAIC = "arc" 47 ARMENIAN = "hy" 48 ASSAMESE = "as" 49 AYMARA = "ay" 50 AZERBAIJANI = "az" 51 BAMBARA = "bm" 52 BANGLA = "bn" 53 BANGLA_INDIA = "bn-IN" 54 BASHKIR = "ba" 55 BASQUE = "eu" 56 BELARUSIAN = "be" 57 BHOJPURI = "bho" 58 BISLAMA = "bi" 59 BODO = "brx" 60 BOSNIAN = "bs" 61 BRETON = "br" 62 BULGARIAN = "bg" 63 BURMESE = "my" 64 CANTONESE = "yue" 65 CANTONESE_HONG_KONG = "yue-HK" 66 CATALAN = "ca" 67 CHEROKEE = "chr" 68 CHINESE = "zh" 69 CHINESE_CHINA = "zh-CN" 70 CHINESE_HONG_KONG = "zh-HK" 71 CHINESE_SIMPLIFIED = "zh-Hans" 72 CHINESE_SINGAPORE = "zh-SG" 73 CHINESE_TAIWAN = "zh-TW" 74 CHINESE_TRADITIONAL = "zh-Hant" 75 CHOCTAW = "cho" 76 COPTIC = "cop" 77 CORSICAN = "co" 78 CREE = "cr" 79 CROATIAN = "hr" 80 CZECH = "cs" 81 DANISH = "da" 82 DOGRI = "doi" 83 DUTCH = "nl" 84 DUTCH_BELGIUM = "nl-BE" 85 DUTCH_NETHERLANDS = "nl-NL" 86 DZONGKHA = "dz" 87 ENGLISH = "en" 88 ENGLISH_AUSTRALIA = "en-AU" 89 ENGLISH_CANADA = "en-CA" 90 ENGLISH_INDIA = "en-IN" 91 ENGLISH_IRELAND = "en-IE" 92 ENGLISH_UNITED_KINGDOM = "en-GB" 93 ENGLISH_UNITED_STATES = "en-US" 94 ESPERANTO = "eo" 95 ESTONIAN = "et" 96 EWE = "ee" 97 FAROESE = "fo" 98 FIJIAN = "fj" 99 FILIPINO = "fil" 100 FINNISH = "fi" 101 FRENCH = "fr" 102 FRENCH_BELGIUM = "fr-BE" 103 FRENCH_CANADA = "fr-CA" 104 FRENCH_FRANCE = "fr-FR" 105 FRENCH_SWITZERLAND = "fr-CH" 106 FULA = "ff" 107 GALICIAN = "gl" 108 GANDA = "lg" 109 GEORGIAN = "ka" 110 GERMAN = "de" 111 GERMAN_AUSTRIA = "de-AT" 112 GERMAN_GERMANY = "de-DE" 113 GERMAN_SWITZERLAND = "de-CH" 114 GREEK = "el" 115 GUARANI = "gn" 116 GUJARATI = "gu" 117 GUSII = "guz" 118 HAITIAN_CREOLE = "ht" 119 HAKKA_CHINESE = "hak" 120 HAKKA_CHINESE_TAIWAN = "hak-TW" 121 HARYANVI = "bgc" 122 HAUSA = "ha" 123 HAWAIIAN = "haw" 124 HEBREW = "he" 125 HINDI = "hi" 126 HINDI_LATIN = "hi-Latn" 127 HIRI_MOTU = "ho" 128 HUNGARIAN = "hu" 129 ICELANDIC = "is" 130 IGBO = "ig" 131 INDONESIAN = "id" 132 INTERLINGUA = "ia" 133 INTERLINGUE = "ie" 134 INUKTITUT = "iu" 135 INUPIAQ = "ik" 136 IRISH = "ga" 137 ITALIAN = "it" 138 JAPANESE = "ja" 139 JAVANESE = "jv" 140 KALAALLISUT = "kl" 141 KALENJIN = "kln" 142 KAMBA = "kam" 143 KANNADA = "kn" 144 KASHMIRI = "ks" 145 KAZAKH = "kk" 146 KHMER = "km" 147 KIKUYU = "ki" 148 KINYARWANDA = "rw" 149 KLINGON = "tlh" 150 KONKANI = "kok" 151 KOREAN = "ko" 152 KURDISH = "ku" 153 KYRGYZ = "ky" 154 LADINO = "lad" 155 LAO = "lo" 156 LATIN = "la" 157 LATVIAN = "lv" 158 LINGALA = "ln" 159 LITHUANIAN = "lt" 160 LUBA_KATANGA = "lu" 161 LUO = "luo" 162 LUXEMBOURGISH = "lb" 163 LUYIA = "luy" 164 MACEDONIAN = "mk" 165 MAITHILI = "mai" 166 MALAGASY = "mg" 167 MALAY = "ms" 168 MALAY_SINGAPORE = "ms-SG" 169 MALAYALAM = "ml" 170 MALTESE = "mt" 171 MANIPURI = "mni" 172 MAORI = "mi" 173 MARATHI = "mr" 174 MASAI = "mas" 175 MERU = "mer" 176 MIN_NAN_CHINESE = "nan" 177 MIN_NAN_CHINESE_TAIWAN = "nan-TW" 178 MIXE = "mco" 179 MIZO = "lus" 180 MONGOLIAN = "mn" 181 MONGOLIAN_MONGOLIAN = "mn-M" 182 NAURU = "na" 183 NAVAJO = "nv" 184 NEPALI = "ne" 185 NIGERIAN_PIDGIN = "pcm" 186 NORTH_NDEBELE = "nd" 187 NORTHERN_SOTHO = "nso" 188 NORWEGIAN = "no" 189 OCCITAN = "oc" 190 ODIA = "or" 191 OROMO = "om" 192 PAPIAMENTO = "pap" 193 PASHTO = "ps" 194 PERSIAN = "fa" 195 PERSIAN_AFGHANISTAN = "fa-AF" 196 PERSIAN_IRAN = "fa-IR" 197 POLISH = "pl" 198 PORTUGUESE = "pt" 199 PORTUGUESE_BRAZIL = "pt-BR" 200 PORTUGUESE_PORTUGAL = "pt-PT" 201 PUNJABI = "pa" 202 QUECHUA = "qu" 203 ROMANIAN = "ro" 204 ROMANIAN_MOLDOVA = "ro-MD" 205 ROMANSH = "rm" 206 RUNDI = "rn" 207 RUSSIAN = "ru" 208 RUSSIAN_LATIN = "ru-Latn" 209 SAMOAN = "sm" 210 SANGO = "sg" 211 SANSKRIT = "sa" 212 SANTALI = "sat" 213 SARDINIAN = "sc" 214 SCOTTISH_GAELIC = "gd" 215 SERBIAN = "sr" 216 SERBIAN_CYRILLIC = "hr-Cyrl" 217 SERBIAN_LATIN = "sr-Latn" 218 SERBO_CROATIAN = "sh" 219 SHERDUKPEN = "sdp" 220 SHONA = "sn" 221 SICILIAN = "scn" 222 SINDHI = "sd" 223 SINHALA = "si" 224 SLOVAK = "sk" 225 SLOVENIAN = "sl" 226 SOMALI = "so" 227 SOUTH_NDEBELE = "nr" 228 SOUTHERN_SOTHO = "st" 229 SPANISH = "es" 230 SPANISH_LATIN_AMERICA = "es-419" 231 SPANISH_MEXICO = "es-MX" 232 SPANISH_SPAIN = "es-ES" 233 SPANISH_UNITED_STATES = "es-US" 234 SUNDANESE = "su" 235 SWAHILI = "sw" 236 SWATI = "ss" 237 SWEDISH = "sv" 238 TAGALOG = "tl" 239 TAJIK = "tg" 240 TAMIL = "ta" 241 TATAR = "tt" 242 TELUGU = "te" 243 THAI = "th" 244 TIBETAN = "bo" 245 TIGRINYA = "ti" 246 TOK_PISIN = "tpi" 247 TOKI_PONA = "tok" 248 TONGAN = "to" 249 TSONGA = "ts" 250 TSWANA = "tn" 251 TURKISH = "tr" 252 TURKMEN = "tk" 253 TWI = "tw" 254 UKRAINIAN = "uk" 255 URDU = "ur" 256 UYGHUR = "ug" 257 UZBEK = "uz" 258 VENDA = "ve" 259 VIETNAMESE = "vi" 260 VOLAPÜK = "vo" 261 VÕRO = "vro" 262 WELSH = "cy" 263 WESTERN_FRISIAN = "fy" 264 WOLAYTTA = "wal" 265 WOLOF = "wo" 266 XHOSA = "xh" 267 YIDDISH = "yi" 268 YORUBA = "yo" 269 ZULU = "zu"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
17class CategoryEnum(int, Enum): 18 FILM_ANIMATION = 1 19 AUTOS_VEHICLES = 2 20 MUSIC = 10 21 PETS_ANIMALS = 15 22 SPORTS = 17 23 TRAVEL_EVENTS = 19 24 GAMING = 20 25 PEOPLE_BLOGS = 22 26 COMEDY = 23 27 ENTERTAINMENT = 24 28 NEWS_POLITICS = 25 29 HOWTO_STYLE = 26 30 EDUCATION = 27 31 SCIENCE_TECH = 28 32 NONPROFITS_ACTIVISM = 29
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- real
- imag
- numerator
- denominator
11class PrivacyEnum(str, Enum): 12 PRIVATE = "PRIVATE" 13 UNLISTED = "UNLISTED" 14 PUBLIC = "PUBLIC"
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans