yb.youtube

YouTube publishing via the YouTube Data API v3.

Optionally installed (pip install 'yb[youtube]'). Needs an OAuth Desktop client (see the yb-setup skill) — set $YOUTUBE_CLIENT_SECRETS_FILE.

Create:

>>> from yb.youtube import prepare_and_publish
>>> prepare_and_publish("promo.fr.mp4", language="French", language_code="fr",
...                     audio_language_code="fr", privacy_status="unlisted")

Edit an existing video:

>>> from yb.youtube import update_video_fields, upsert_caption, set_chapters
>>> update_video_fields("VIDEO_ID", title="New title")
>>> upsert_caption("VIDEO_ID", "subs.fr.srt", language="fr", name="Français")

Functions

api_available(*[, api_key])

Whether the Data API can actually be called with the key we have.

watch_url(video_id)

thumbnail_url(video_id, *[, quality])

Thumbnail URL for a video.

rank_by_engagement(videos, *[, min_views, ...])

Order videos by a like-rate-and-reach score.

add_stats(videos, *[, api_key, backend])

Fill in engagement numbers on search results, in place-ish (returns them).

search_videos(query, *[, max_results, ...])

Search YouTube and return a list of video records.

start_paste_consent(*[, ...])

Begin paste-back consent and return the URL to open.

finish_paste_consent(authorization_response, *)

Complete a consent begun by start_paste_consent(); return credentials.

get_credentials(*[, client_secrets_file, ...])

Return OAuth user credentials, running the consent flow if needed.

get_service(*[, credentials])

Build a YouTube Data API v3 service object.

default_token_file()

Cached OAuth token location ($XDG_CONFIG_HOME or ~/.config).

default_client_secrets_file()

Default OAuth client-secrets location: <config dir>/client_secret.json.

list_my_playlists(*[, service])

Return all playlists owned by the authenticated channel (paginated).

find_playlist(title, *[, service])

Return the id of the caller's playlist titled title (or None).

create_playlist(title, *[, description, ...])

Create a playlist and return its id.

ensure_playlist(title, *[, create, ...])

Return the id of the playlist titled title, creating it if missing.

is_video_in_playlist(video_id, playlist_id, *)

Whether video_id is already an item of playlist_id (paginated).

add_to_playlist(video_id, playlist_id, *[, ...])

Add video_id to playlist_id (playlistItems.insert).

add_video_to_playlist(video_id, title, *[, ...])

Find-or-create the playlist named title and append video_id.

playlist_item_ids(video_id, playlist_id, *)

The playlistItems ids holding video_id in playlist_id.

first_position(video_id, playlist_id, *[, ...])

The 0-based position of video_id's first occurrence (None if absent).

playlists_containing(video_id, *[, service])

Ids of the authenticated channel's playlists that hold video_id.

remove_from_playlist(video_id, playlist_id, *)

Remove every occurrence of video_id from playlist_id.

update_video_status(video_id, *[, ...])

Change a video's privacy (or synthetic-media flag), keeping the rest.

supersede(old_id, new_id, *[, old_privacy, ...])

Retire an upload that new_id replaces (yb#19).

get_video(video_id, *[, part, service])

Fetch a video resource (raises KeyError if not found/visible).

upload_video(video_path, body, *[, service, ...])

Resumably upload a video with the given videos.insert body.

update_video(video_id, snippet, *[, service])

Update a video's snippet (videos.update).

update_video_fields(video_id, *[, title, ...])

Patch selected snippet fields, preserving the rest.

set_thumbnail(video_id, image_path, *[, service])

Set a custom thumbnail (thumbnails.set).

set_chapters(video_id, chapters, *[, ...])

Insert/replace a chapters block in the video's description.

video_metadata(video_id, *[, group, fields, ...])

Fetch a video's live metadata & engagement numbers.

flatten_video(resource)

Flatten a raw videos.list item into a friendly, typed, ordered dict.

select_fields(flat, *[, group, fields])

Return an ordered subset of flat per fields/group.

resolve_fields(*[, group, fields, available])

Resolve the ordered field list to show.

render_table(data, *[, fields])

Render metadata as an ASCII table.

list_captions(video_id, *[, service])

List caption tracks on a video (each item's snippet has language/name/trackKind/status).

insert_caption(video_id, path, *, language)

Insert a new caption track (captions.insert).

update_caption(caption_id, path, *[, ...])

Replace the content of an existing caption track (captions.update).

upsert_caption(video_id, track, *[, ...])

Insert a caption track, or update the existing same-language one.

download_caption(caption_id, *[, tfmt, service])

Download a caption track's content (captions.download), default SRT.

delete_caption(caption_id, *[, service])

Delete a caption track (captions.delete).

publish_content(content, *[, ...])

Upload a prepared PublicationContent to YouTube.

prepare_and_publish(media, *[, language, ...])

One call: prepare publication content from media and upload it.

publish_video(video_path, metadata, *[, ...])

Lower-level upload: explicit VideoMetadata + caption tracks.

Classes

VideoMetadata(title, description[, tags, ...])

YouTube-specific video metadata.

CaptionTrack(path, language[, name, is_draft])

A caption track to attach to a video.

Exceptions

ConsentRequired

Consent is needed and this process cannot obtain it.

ConsentPending(url, *, redirect_uri)

Consent was started, and now waits for the user to paste the redirect back.

VisibilityDropWarning

supersede() leaves the re-cut less visible than the original was.

PartialPublish(result, *, step, error)

The video was uploaded, then a later step failed (yb#21).

class yb.youtube.CaptionTrack(path, language, name='', is_draft=False)[source]

Bases: object

A caption track to attach to a video.

path

Path to an .srt (or .vtt) file.

language

BCP-47 language code, e.g. "en" or "fr".

name

Track name shown in the YouTube UI (e.g. "English").

is_draft

When True, uploaded but not published to viewers.

exception yb.youtube.ConsentPending(url, *, redirect_uri)[source]

Bases: ConsentRequired

Consent was started, and now waits for the user to paste the redirect back.

Not a failure: it is the normal result of the first call in paste mode. url is the address to open; the message says what to do with the result. Subclasses ConsentRequired so a caller that only handles “no credentials” still stops, while one that knows about paste mode can catch this and relay url.

exception yb.youtube.ConsentRequired[source]

Bases: RuntimeError

Consent is needed and this process cannot obtain it.

Raised instead of starting a consent flow that nobody can complete. The alternative is worse than an error: run_local_server prints a URL to a stdout nobody is reading and waits on localhost for a redirect that will never arrive, so an automated caller blocks rather than failing. That has happened — an upload stopped dead mid-script and read as a slow network for hours, when the underlying cause was one line of invalid_grant.

exception yb.youtube.PartialPublish(result, *, step, error)[source]

Bases: RuntimeError

The video was uploaded, then a later step failed (yb#21).

The upload is the expensive step (1,600 quota units); captions, thumbnail and playlist can each fail afterwards on quota, auth or a transient error. Raising the bare error lost the new video’s id, and a re-run uploaded a second copy. This carries what was done instead:

  • result: the dict the publish call would have returned, as far as it got (video_id, url, captions attached so far, thumbnail, playlist). Persist it.

  • step: the step that failed, e.g. "caption ja".

  • __cause__: the original error.

Finish later without re-uploading: pass resume=err.result to the same call (publish_video() or publish_content(); for prepare_and_publish(), call publish_content() with the content it prepared, rather than preparing it again). Steps already done are skipped. A resume trusts the saved result: it does not upload, so new metadata, video path or privacy in the resuming call are ignored, and the remaining steps target the saved video_id.

result is plain JSON-able data, and the exception pickles (so a worker process can hand it back); __cause__ does not survive pickling.

class yb.youtube.VideoMetadata(title, description, tags=<factory>, category_id='28', default_language=None, default_audio_language=None, contains_synthetic_media=None)[source]

Bases: object

YouTube-specific video metadata.

title

Title (<= 100 chars).

description

Description (<= 5000 chars), chapters already embedded.

tags

Keyword tags (combined length kept under 500 chars).

category_id

YouTube category id (default Science & Technology).

default_language

BCP-47 language of the metadata text.

default_audio_language

BCP-47 language of the audio.

contains_synthetic_media

Declare that the video contains realistic altered or synthetic content. YouTube’s disclosure rule turns on realism, not on whether AI was involved: it applies when a viewer could mistake the content for a real person, place, or event — not to stylised artwork or a visualizer. Left unset when None.

classmethod from_content(content, *, category_id='28', with_chapters=True)[source]

Build YouTube metadata from a platform-neutral content bundle.

Embeds the chapters block into the description (when present and with_chapters), so YouTube renders interactive chapters.

Return type:

VideoMetadata

insert_body(*, privacy_status='unlisted')[source]

Build the videos.insert body (snippet + status).

Return type:

dict

update_snippet()[source]

Build the snippet for videos.update (categoryId is required).

Return type:

dict

exception yb.youtube.VisibilityDropWarning[source]

Bases: UserWarning

supersede() leaves the re-cut less visible than the original was.

Emitted before anything is changed, so escalating it refuses the call:

warnings.simplefilter("error", VisibilityDropWarning)
yb.youtube.add_stats(videos, *, api_key=None, backend='auto')[source]

Fill in engagement numbers on search results, in place-ish (returns them).

Return type:

list

yb.youtube.add_to_playlist(video_id, playlist_id, *, position=None, service=None, **cred_kwargs)[source]

Add video_id to playlist_id (playlistItems.insert).

Appended at the end, or inserted at the 0-based position when given (YouTube refuses a position on a playlist not sorted manually).

Return type:

dict

yb.youtube.add_video_to_playlist(video_id, title, *, create=True, privacy_status='private', skip_if_present=True, service=None, **cred_kwargs)[source]

Find-or-create the playlist named title and append video_id.

Idempotent: with skip_if_present (default), a video already in the playlist is left alone instead of duplicated.

Return type:

dict

Returns:

{"playlist_id", "playlist_title", "added", "created"} where added is False if the video was already present and created is True if the playlist had to be made.

Raises:

KeyError – the playlist is absent and create=False.

yb.youtube.api_available(*, api_key=None)[source]

Whether the Data API can actually be called with the key we have.

A key that exists is not a key that works: the API has to be enabled on its Cloud project too, and that failure is a 403 at call time rather than anything visible up front.

Return type:

bool

yb.youtube.create_playlist(title, *, description='', privacy_status='private', service=None, **cred_kwargs)[source]

Create a playlist and return its id.

Return type:

str

yb.youtube.default_client_secrets_file()[source]

Default OAuth client-secrets location: <config dir>/client_secret.json.

Used when neither client_secrets_file= nor the env vars are set, so all of yb’s state can live in one directory next to the token.

Return type:

Path

yb.youtube.default_token_file()[source]

Cached OAuth token location ($XDG_CONFIG_HOME or ~/.config).

Return type:

Path

yb.youtube.delete_caption(caption_id, *, service=None, **cred_kwargs)[source]

Delete a caption track (captions.delete).

Return type:

None

yb.youtube.download_caption(caption_id, *, tfmt='srt', service=None, **cred_kwargs)[source]

Download a caption track’s content (captions.download), default SRT.

Return type:

bytes

yb.youtube.ensure_playlist(title, *, create=True, description='', privacy_status='private', service=None, **cred_kwargs)[source]

Return the id of the playlist titled title, creating it if missing.

Returns None only when the playlist is absent and create=False.

Return type:

str | None

yb.youtube.find_playlist(title, *, service=None, **cred_kwargs)[source]

Return the id of the caller’s playlist titled title (or None).

Matches the first playlist with an exact title; YouTube allows duplicate titles, so prefer unique playlist names.

Return type:

str | None

Complete a consent begun by start_paste_consent(); return credentials.

authorization_response is the redirected URL, its query string, or the bare code. The pending file is removed on success and left in place on failure, so a mistyped paste can simply be retried.

yb.youtube.first_position(video_id, playlist_id, *, service=None, **cred_kwargs)[source]

The 0-based position of video_id’s first occurrence (None if absent).

Return type:

int | None

yb.youtube.flatten_video(resource)[source]

Flatten a raw videos.list item into a friendly, typed, ordered dict.

Pure: pass the dict returned by the API. Missing pieces (from a partial part= request, disabled stats, or an unauthorized dislike count) become None rather than raising, so callers never have to guard the shape.

Return type:

dict

yb.youtube.get_credentials(*, client_secrets_file=None, token_file=None, scopes=['https://www.googleapis.com/auth/youtube.upload', 'https://www.googleapis.com/auth/youtube.force-ssl'], open_browser=True, port=0, timeout_seconds=300.0, interactive=True, consent='local', authorization_response=None)[source]

Return OAuth user credentials, running the consent flow if needed.

First use runs the installed-app consent flow and caches the token to token_file so later calls are non-interactive. Expired tokens are refreshed automatically.

Pass interactive=False when nobody can answer a consent prompt — a cron job, a queue worker, an agent. Consent is then never started; a token that cannot be refreshed raises ConsentRequired naming the cause and the fix. This is not an edge case: an OAuth client left in “Testing” rotates refresh tokens out after about seven days, so anything scheduled hits it weekly.

Even with the default interactive=True the call can no longer hang: consent is bounded by timeout_seconds (DEFAULT_CONSENT_TIMEOUT_S), and a timeout is reported as the same ConsentRequired, because “nobody answered” and “nobody could answer” want the same thing done about them.

Consent always goes through a temporary local web server on port (0 picks a free one), because Google retired the copy-paste “out-of-band” flow in 2022. open_browser=False only stops the browser from being launched: the authorization URL is printed instead, and the redirect must still reach that local server.

Headless recipe: pass interactive=True (there is no terminal, so the default would refuse), open_browser=False and a fixed port=, then forward that port from the machine holding the browser (ssh -L <port>:localhost:<port> <host>). Nothing needs registering in the Cloud console — the Desktop app client this module requires accepts any localhost port, which is also why the port=0 default works.

The call blocks until the redirect arrives, bounded by timeout_seconds (default DEFAULT_CONSENT_TIMEOUT_S; pass None for the library’s “wait indefinitely”, which is what this used to do).

**No shared localhost? Use** consent="paste". Consent then needs no running server and no waiting: the first call returns by raising ConsentPending carrying the URL to open (anywhere — a phone will do); the browser’s redirect to localhost fails to load, and the second call passes that address-bar URL (or just its code) as authorization_response= to finish and cache the token. The two calls may be separate turns or separate processes; yb auth is the command-line form. port only names the (unreachable) redirect.

These keywords ride **cred_kwargs through get_service() and the publishing helpers. The few entry points that take none (notably yb.music.publish.publish_folder()) still work headlessly: call this once to mint the token, after which nothing prompts again.

yb.youtube.get_service(*, credentials=None, **cred_kwargs)[source]

Build a YouTube Data API v3 service object.

cred_kwargs are forwarded to get_credentials() (token_file=, open_browser=, port=, …) unless credentials is given.

yb.youtube.get_video(video_id, *, part='snippet,status', service=None, **cred_kwargs)[source]

Fetch a video resource (raises KeyError if not found/visible).

Return type:

dict

yb.youtube.insert_caption(video_id, path, *, language, name='', is_draft=False, service=None, **cred_kwargs)[source]

Insert a new caption track (captions.insert).

Return type:

dict

yb.youtube.is_video_in_playlist(video_id, playlist_id, *, service=None, **cred_kwargs)[source]

Whether video_id is already an item of playlist_id (paginated).

Return type:

bool

yb.youtube.list_captions(video_id, *, service=None, **cred_kwargs)[source]

List caption tracks on a video (each item’s snippet has language/name/trackKind/status).

Return type:

list[dict]

yb.youtube.list_my_playlists(*, service=None, **cred_kwargs)[source]

Return all playlists owned by the authenticated channel (paginated).

Return type:

list[dict]

yb.youtube.playlist_item_ids(video_id, playlist_id, *, service=None, **cred_kwargs)[source]

The playlistItems ids holding video_id in playlist_id.

A list, because a video can sit in one playlist more than once.

Return type:

list[str]

yb.youtube.playlists_containing(video_id, *, service=None, **cred_kwargs)[source]

Ids of the authenticated channel’s playlists that hold video_id.

Reads every item page of every playlist you own (1 quota unit per page), plus the playlists.list pages: on a large channel, budget for it.

Return type:

list[str]

yb.youtube.prepare_and_publish(media, *, language='English', language_code=None, audio_language_code=None, brand=None, extra_context=None, with_chapters=True, with_thumbnail=True, privacy_status=None, playlist=<object object>, category_id='28', config=None, client_secrets_file=None, token_file=None, progress=True, **cred_kwargs)[source]

One call: prepare publication content from media and upload it.

Transcribes (persisting the SRT next to the media) if needed, writes LLM metadata, detects chapters, renders a thumbnail, then uploads with captions + thumbnail + chapters and adds the video to the configured playlist. privacy_status and playlist default to your yb config (see publish_content()).

cred_kwargs reach get_credentials() (e.g. open_browser=False, port=8080 for headless consent).

Return type:

dict

yb.youtube.publish_content(content, *, privacy_status=None, playlist=<object object>, category_id='28', with_chapters=True, attach_caption=True, set_thumb=True, config=None, client_secrets_file=None, token_file=None, progress=True, service=None, resume=None, **cred_kwargs)[source]

Upload a prepared PublicationContent to YouTube.

Maps the content to a YouTube snippet (chapters embedded in the description when present), uploads, attaches the SRT caption track (language = content.audio_language or content.language) and the thumbnail when available, and adds the video to the configured playlist.

privacy_status and playlist default to your yb config (see yb.config): unset means “use the config value”, so privacy falls back to unlisted and the video joins your configured playlist. Pass playlist=None to skip the playlist for one call, or playlist="Name" to override the target.

cred_kwargs reach get_credentials() (e.g. open_browser=False, port=8080 for headless consent).

Return type:

dict

Returns:

{"video_id", "url", "studio_url", "privacy_status", "captions", "thumbnail", "playlist"}. privacy_status reflects what YouTube actually set (an unaudited project may force "private"); playlist is the add_video_to_playlist() result or None.

Raises:

PartialPublish – the upload succeeded and a later step failed; err.result holds the video id and what was done. Pass resume=err.result to finish without uploading again (yb#21).

yb.youtube.publish_video(video_path, metadata, *, privacy_status=None, playlist=<object object>, captions=None, thumbnail=None, config=None, client_secrets_file=None, token_file=None, progress=True, service=None, resume=None, **cred_kwargs)[source]

Lower-level upload: explicit VideoMetadata + caption tracks.

privacy_status and playlist default to your yb config (see publish_content()).

If a step after the upload fails, PartialPublish is raised with the new video’s id and what was done so far (err.result). Pass resume=err.result to finish without uploading again (yb#21).

cred_kwargs reach get_credentials() (e.g. open_browser=False, port=8080 for headless consent).

Return type:

dict

yb.youtube.rank_by_engagement(videos, *, min_views=0, like_weight=1.0, view_weight=1.0)[source]

Order videos by a like-rate-and-reach score.

Raw view count alone rewards age and luck, and like count alone rewards the same. The score multiplies reach (log views, so an order of magnitude counts for a fixed amount) by approval (likes per thousand views), which ranks a well-liked mid-sized video above a big indifferent one. Videos missing numbers sort last rather than being dropped, since absent is not the same as bad.

Return type:

list

yb.youtube.remove_from_playlist(video_id, playlist_id, *, service=None, **cred_kwargs)[source]

Remove every occurrence of video_id from playlist_id.

Returns how many items were removed (0 when it was not there).

Return type:

int

yb.youtube.render_table(data, *, fields=None)[source]

Render metadata as an ASCII table.

A single flat dict renders as a two-column field | value table; a list of flat dicts renders one row per video with fields as columns.

Return type:

str

yb.youtube.resolve_fields(*, group=None, fields=None, available=None)[source]

Resolve the ordered field list to show.

Precedence: explicit fields > named group > every available field. An unknown group raises KeyError naming the valid options.

Return type:

list[str]

yb.youtube.search_videos(query, *, max_results=10, backend='auto', with_stats=False, api_key=None, language=None, region=None, **params)[source]

Search YouTube and return a list of video records.

language/region bias results ("en", "GB") rather than filter them — YouTube offers no hard language filter. Check what comes back if the language matters; a title can be auto-translated by the interface, which makes an English video look like a local one.

Return type:

list

yb.youtube.select_fields(flat, *, group=None, fields=None)[source]

Return an ordered subset of flat per fields/group.

Return type:

dict

yb.youtube.set_chapters(video_id, chapters, *, header='Chapters:', service=None, **cred_kwargs)[source]

Insert/replace a chapters block in the video’s description.

Strips any prior block under header and appends the new one. YouTube renders interactive chapters when the first timestamp is 0:00.

Return type:

dict

yb.youtube.set_thumbnail(video_id, image_path, *, service=None, **cred_kwargs)[source]

Set a custom thumbnail (thumbnails.set).

Return type:

dict

Begin paste-back consent and return the URL to open.

A consent already pending for the same client, scopes and port is reused (reuse_pending=True): re-printing the URL must not invalidate the one the user already has open on their phone.

Return type:

str

yb.youtube.supersede(old_id, new_id, *, old_privacy='private', new_privacy=None, move_playlists=True, service=None, **cred_kwargs)[source]

Retire an upload that new_id replaces (yb#19).

YouTube cannot replace a video’s file, so a re-cut is a new upload. This puts the new one where the old one was and takes the old one out of view:

  1. refuses, before changing anything, unless new_id has finished processing (uploadStatus == "processed"): retiring the old video for a failed or rejected upload would leave the public neither;

  2. sets new_id to new_privacy when given (e.g. "public");

  3. for every playlist of yours that holds old_id (when move_playlists): inserts new_id at the old one’s position (or appends, if the playlist is not sorted manually), THEN removes old_id, so a failure part-way never leaves a playlist with neither;

  4. sets old_id to old_privacy (default "private"), clearing any scheduled publish. It is never deleted.

The new video keeps its own privacy unless new_privacy is given, and an upload is unlisted by default: so the re-cut of a public video is NOT made public for you. When the result is less visible than the old video was (a public original, an unlisted re-cut), a VisibilityDropWarning is emitted before anything changes; pass new_privacy="public" to publish the re-cut, or turn the warning into an error to refuse such calls.

Captions and thumbnails are not copied: upload them with the new video (yb.youtube.publish_video()). Safe to re-run: every step checks before it acts. Quota: see yb.youtube.playlists_containing(); each write (insert, delete, update) is 50 units.

Return type:

dict

Returns:

{"old_id", "new_id", "playlists", "old_privacy", "new_privacy"} where playlists lists {"playlist_id", "added_new", "position", "removed_old"}.

yb.youtube.thumbnail_url(video_id, *, quality='mqdefault')[source]

Thumbnail URL for a video. Always available, no API call, no key.

Return type:

str

yb.youtube.update_caption(caption_id, path, *, is_draft=None, service=None, **cred_kwargs)[source]

Replace the content of an existing caption track (captions.update).

Return type:

dict

yb.youtube.update_video(video_id, snippet, *, service=None, **cred_kwargs)[source]

Update a video’s snippet (videos.update). categoryId is required.

Return type:

dict

yb.youtube.update_video_fields(video_id, *, title=None, description=None, tags=None, category_id=None, default_language=None, default_audio_language=None, service=None, **cred_kwargs)[source]

Patch selected snippet fields, preserving the rest.

Fetches the current snippet, overlays the provided fields, and updates. categoryId must be present (kept from the existing snippet if not given).

Return type:

dict

yb.youtube.update_video_status(video_id, *, privacy_status=None, contains_synthetic_media=None, keep_schedule=False, service=None, **cred_kwargs)[source]

Change a video’s privacy (or synthetic-media flag), keeping the rest.

Read-modify-write of the status part (yb#19): videos.update replaces the part wholesale, so the current writable fields are carried over and only the ones given change. Making a video private never deletes it.

Setting privacy_status clears a scheduled publish (publishAt): otherwise a video made private while scheduled would still go public at that time, and a past publishAt read back is refused by the API. Pass keep_schedule=True to keep it (only meaningful with "private").

Raises:

ValueError – privacy_status is not one of PRIVACY_STATUSES.

Return type:

dict

yb.youtube.upload_video(video_path, body, *, service=None, chunksize=8388608, progress=True, **cred_kwargs)[source]

Resumably upload a video with the given videos.insert body.

Returns the created video resource (includes id).

Return type:

dict

yb.youtube.upsert_caption(video_id, track, *, language=None, name='', is_draft=False, replace=True, service=None, **cred_kwargs)[source]

Insert a caption track, or update the existing same-language one.

Parameters:
  • video_id (str) – Target video.

  • track (CaptionTrack | str | Path) – A CaptionTrack, or a path (then language required).

  • replace (bool) – When an uploaded track in the same language exists, update it (True, default) rather than inserting a duplicate.

Return type:

dict

Returns:

The inserted/updated caption resource.

yb.youtube.video_metadata(video_id, *, group=None, fields=None, part='snippet,statistics,contentDetails,status', as_table=False, service=None, **cred_kwargs)[source]

Fetch a video’s live metadata & engagement numbers.

Parameters:
  • video_id (Union[str, Iterable[str]]) – A single video id or an iterable of ids (batched, ≤50/call).

  • group (str | None) – Name of a preset field set from FIELD_GROUPS (e.g. "engagement"). Ignored if fields is given.

  • fields (Optional[Sequence[str]]) – Explicit ordered field names to keep (overrides group). With neither group nor fields you get every field.

  • part (str) – videos.list parts to request (default covers all fields).

  • as_table (bool) – When True, return a ready-to-print ASCII table string instead of the dict/list.

  • service – An authenticated YouTube service; else built from cred_kwargs (e.g. token_file=...).

Return type:

dict | list[dict] | str

Returns:

A flat dict for one id / list[dict] for many — or an ASCII table str when as_table=True.

Modules

api

YouTube video operations: get, upload, update metadata, thumbnail, chapters.

auth

OAuth 2.0 plumbing for the YouTube Data API v3.

captions

Caption (subtitle) tracks on YouTube videos: list, insert, update, upsert.

metadata

YouTube video metadata ↔ API snippet mapping.

paste_consent

Paste-back OAuth consent: consent from a machine nobody is sitting at.

playlists

YouTube playlist operations: find/create a playlist and add videos to it.

publish

High-level YouTube publishing: prepare → upload → captions → thumbnail.

search

Search YouTube for videos, with engagement numbers to rank them by.

stats

Read live video metadata & engagement numbers from the YouTube Data API v3.