yb
yb — streamline media publishing: prepare once, publish to YouTube or podcast.
Layered by separation of concerns:
core (always available):
yb.content— build a platform-neutralPublicationContent(title, description, keywords, chapters, captions, thumbnail) from a media file, delegating transcription, chapter detection, and thumbnails to themixingpackage.adapters (optional extras): -
yb.youtube(pip install 'yb[youtube]') — upload and edit videos. -yb.music(pip install 'yb[music]') — turn songs into music videos(rendering via
muvid) and publish them, including a whole folder as an album. Uploading also needsyb[youtube].yb.podcast(pip install 'yb[podcast]') — show notes, chapter markers, cover-over-audio video (viamuvid), RSS episode item.yb.download(pip install 'yb[download]') — fetch videos/metadata.
The audio→video rendering itself lives in muvid.visualize (yb is the
publication layer); yb.music is the thin publish-facing facade over it.
The most common callables are re-exported here for convenience; the ones that
live in optional extras are imported lazily, so import yb never fails for a
missing extra — the error surfaces only when you actually use that feature.
Examples
>>> import yb
>>> r = yb.download_youtube_video("https://youtu.be/PRa9ciOe-us") # needs yb[download]
>>> content = yb.prepare_content(r.path, language="English", language_code="en")
>>> yb.prepare_and_publish(r.path, privacy_status="unlisted") # needs yb[youtube]
- class yb.ContentMetadata(title: str, description: str, keywords: list[str] = <factory>)[source]
Platform-neutral copy: a title, a description, and keywords.
- class yb.PublicationContent(media: Path, title: str = '', description: str = '', keywords: list[str] = <factory>, chapters: list[Chapter] = <factory>, language: str | None = None, audio_language: str | None = None, srt_path: Path | None = None, thumbnail: Path | None = None, duration: float | None = None)[source]
Everything needed to publish a piece of media, destination-agnostic.
- media
Path to the audio/video file.
- Type:
pathlib.Path
- title
Headline/title.
- Type:
str
- description
Long-form description / show notes body.
- Type:
str
- keywords
Topical keywords/tags.
- Type:
list[str]
- chapters
Ordered chapter markers (
mixing.chapters.Chapter).- Type:
list[mixing.chapters.Chapter]
- language
BCP-47 code of the metadata text (e.g.
"en").- Type:
str | None
- audio_language
BCP-47 code of the spoken audio (e.g.
"fr").- Type:
str | None
- srt_path
Path to the SRT subtitle/caption file, if any.
- Type:
pathlib.Path | None
- thumbnail
Path to a thumbnail/cover image, if any.
- Type:
pathlib.Path | None
- duration
Media duration in seconds, if known.
- Type:
float | None
- class yb.YbConfig(privacy_status: str = 'unlisted', playlist: str | None = None, create_playlist_if_missing: bool = True, playlist_privacy_status: str = 'private')[source]
Resolved publishing defaults.
- privacy_status
Privacy a new upload gets (
unlisted|private|public) when the call doesn’t override it.- Type:
str
- playlist
Title of a playlist every upload is added to (
None= none).- Type:
str | None
- create_playlist_if_missing
Create
playlistif no playlist of that title exists yet, rather than erroring.- Type:
bool
- playlist_privacy_status
Privacy for an auto-created playlist.
- Type:
str
- yb.add_video_to_playlist(video_id: str, title: str, *, create: bool = True, privacy_status: str = 'private', skip_if_present: bool = True, service=None, **cred_kwargs) dict[source]
Find-or-create the playlist named
titleand appendvideo_id.Idempotent: with
skip_if_present(default), a video already in the playlist is left alone instead of duplicated.- Returns:
{"playlist_id", "playlist_title", "added", "created"}whereaddedisFalseif the video was already present andcreatedisTrueif the playlist had to be made.- Raises:
KeyError – the playlist is absent and
create=False.
- yb.download_youtube_audio(url: str, *, download_dir: str | Path | None = None, audio_format: str | None = None, bitrate: str | None = None, keep_original: bool = False, on_error: Literal['raise', 'warn'] = 'raise', fmt: str = 'bestaudio/best', filename_template: str = '%(title)s (%(id)s).%(ext)s', write_info_json: bool = False, write_thumbnail: bool = False, write_description: bool = False, write_subtitles: bool = False, write_auto_subtitles: bool = False, subtitle_langs: tuple[str, ...] = ('en',), quiet: bool = True, extra_opts: dict | None = None) DownloadResult[source]
Download only a video’s audio (no video stream).
Simplest use:
download_youtube_audio(url)→ the best audio stream, kept in whatever format the source offers (YouTube’s is usually Opus in a.webmcontainer), namedTitle (video_id).webmin~/Downloads.Pass
audio_formatto get a specific format instead:>>> download_youtube_audio(url, audio_format="mp3")
- Parameters:
url – The video URL (or id).
download_dir – Destination directory. Defaults to
default_download_dir().audio_format – Target format as an extension (
"mp3",".wav", …). The defaultNonemeans no conversion — keep the downloaded bytes exactly as they came, which needs no ffmpeg and avoids re-encoding a lossy stream into another lossy format.bitrate – Bitrate for lossy targets (e.g.
"320k"). Ignored whenaudio_formatis lossless orNone.keep_original – When converting, also keep the originally downloaded file (by default it is removed once converted).
on_error – What to do when conversion fails (ffmpeg missing or erroring).
"raise"(default) propagatesAudioConversionError;"warn"emits a warning and returns the unconverted download. Either way the downloaded audio is left on disk — a failed conversion never costs you the download.fmt – yt-dlp format selector (default best audio-only stream).
filename_template – yt-dlp output template (default
"%(title)s (%(id)s).%(ext)s").write_info_json – Also save the full metadata as
*.info.json.write_thumbnail – Also save the thumbnail image.
write_description – Also save the description as
*.description.write_subtitles – Also save uploaded subtitles for
subtitle_langs.write_auto_subtitles – Also save auto-generated subtitles.
subtitle_langs – Subtitle languages to fetch when subtitles are enabled.
quiet – Suppress yt-dlp console output.
extra_opts – Any additional raw yt-dlp options (merged last, so they win).
- Returns:
A
DownloadResultwhosepathis the audio file — converted whenaudio_formatwas given and conversion succeeded.- Raises:
ValueError – If
on_erroris not"raise"or"warn".AudioConversionError – If conversion fails and
on_error="raise".
- yb.download_youtube_video(url: str, *, download_dir: str | Path | None = None, fmt: str = 'bestvideo+bestaudio/best', merge_to: str | None = 'mp4', filename_template: str = '%(title)s (%(id)s).%(ext)s', write_info_json: bool = False, write_thumbnail: bool = False, write_description: bool = False, write_subtitles: bool = False, write_auto_subtitles: bool = False, subtitle_langs: tuple[str, ...] = ('en',), quiet: bool = True, extra_opts: dict | None = None) DownloadResult[source]
Download a YouTube video to
download_dir(default~/Downloads).Simplest use:
download_youtube_video(url)→ best quality merged to mp4, namedTitle (video_id).mp4in~/Downloads.- Parameters:
url – The video URL (or id).
download_dir – Destination directory. Defaults to
default_download_dir().fmt – yt-dlp format selector (default best video + best audio).
merge_to – Container to merge video+audio into (default
"mp4"; requires ffmpeg). SetNoneto keep yt-dlp’s default.filename_template – yt-dlp output template (default
"%(title)s (%(id)s).%(ext)s").write_info_json – Also save the full metadata as
*.info.json.write_thumbnail – Also save the thumbnail image.
write_description – Also save the description as
*.description.write_subtitles – Also save uploaded subtitles for
subtitle_langs.write_auto_subtitles – Also save auto-generated subtitles.
subtitle_langs – Subtitle languages to fetch when subtitles are enabled.
quiet – Suppress yt-dlp console output.
extra_opts – Any additional raw yt-dlp options (merged last, so they win).
- Returns:
A
DownloadResultwith the media path, trimmedinfo, and anysidecarswritten.
- yb.format_chapter_lines(chapters: Sequence[Chapter]) str[source]
Render chapters as
M:SS Titlelines (the shared text convention).Uses
H:MM:SSwhen any chapter is at or beyond one hour. This is the text format both YouTube descriptions and podcast show notes accept.
- yb.generate_metadata(transcript: str, *, language: str = 'English', brand: str | None = None, extra_context: str | None = None, model: str | None = None) ContentMetadata[source]
Generate platform-neutral title/description/keywords from a transcript.
LLM-backed via
aix. Pass a readyContentMetadatatoprepare_content()instead if you want to skip generation.- Raises:
ImportError –
aixis not importable.
- yb.load_config(config_file: str | Path | None = None, **overrides) YbConfig[source]
Load publishing defaults from
config_file(or the default location).A missing file yields the built-in defaults.
overrides(typically the explicit keyword arguments a caller passed) take precedence when notNone, so call-site arguments always win over the file.
- yb.prepare_and_publish(media: str | Path, *, language: str = 'English', language_code: str | None = None, audio_language_code: str | None = None, brand: str | None = None, extra_context: str | None = None, with_chapters: bool = True, with_thumbnail: bool = True, privacy_status: str | None = None, playlist=<object object>, category_id: str = '28', config: YbConfig | None = None, client_secrets_file: str | Path | None = None, token_file: str | Path | None = None, progress: bool = True) dict[source]
One call: prepare publication content from
mediaand 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_statusandplaylistdefault to yourybconfig (seepublish_content()).
- yb.prepare_content(media: str | Path, *, language: str = 'English', language_code: str | None = None, audio_language_code: str | None = None, brand: str | None = None, extra_context: str | None = None, transcript: str | None = None, with_chapters: bool = True, with_thumbnail: bool = False, thumbnail_text: str | None = None, metadata: ContentMetadata | None = None, model: str | None = None, transcribe_kwargs: dict | None = None, chapters_kwargs: dict | None = None) PublicationContent[source]
Build a
PublicationContentformedia(destination-agnostic).Ensures a persisted SRT next to the media (transcribing once via
mixingif needed), writes LLM metadata, detects chapters (default on, auto-skipped for clips too short to host them), and optionally renders a thumbnail. Any precomputed pieces (transcript,metadata) are reused instead of recomputed.- Parameters:
media – Audio or video file.
language – Human-readable language to write metadata in.
audio_language_code (language_code /) – BCP-47 codes for the metadata text and the spoken audio.
brand – Product/brand name to keep verbatim in the copy.
extra_context – Extra guidance for the copywriter (audience, CTA…).
transcript – SRT text to use as-is (skips transcription).
with_chapters – Detect chapters (default
True).with_thumbnail – Render a thumbnail image (default
False).thumbnail_text – Overlay text for the thumbnail (defaults to the title).
metadata – Precomputed
ContentMetadatato reuse.model – LLM model override.
chapters_kwargs (transcribe_kwargs /) – Forwarded to the mixing calls.
- Returns:
A populated
PublicationContent.
- yb.prepare_podcast_episode(media: str | Path, output_dir: str | Path, *, content: PublicationContent | None = None, audio: str | Path | None = None, cover_image: str | Path | None = None, make_cover_video: bool = False, ken_burns: bool = False, embed_chapters: bool = True, language: str = 'English', brand: str | None = None, extra_context: str | None = None, audio_bitrate: str = '192k') PodcastEpisode[source]
Build a podcast episode bundle from
mediaintooutput_dir.- Parameters:
media – Source audio or video.
output_dir – Directory to write the episode assets into.
content – Precomputed
PublicationContent; built viayb.content.prepare_content()when omitted.audio – Explicit episode audio. When omitted,
mediais used if it is audio, else its audio track is extracted to MP3.cover_image – Cover art (required for
make_cover_video).make_cover_video – Also render a cover-over-audio mp4 (e.g. for YouTube).
ken_burns – Apply a Ken Burns pan/zoom to the cover video.
embed_chapters – Embed ID3 chapter frames into the episode MP3.
extra_context (language / brand /) – Forwarded to
prepare_content.audio_bitrate – Bitrate for extracted MP3 audio.
- Returns:
A
PodcastEpisodewith the asset paths.
- yb.publish_content(content: PublicationContent, *, privacy_status: str | None = None, playlist=<object object>, category_id: str = '28', with_chapters: bool = True, attach_caption: bool = True, set_thumb: bool = True, config: YbConfig | None = None, client_secrets_file: str | Path | None = None, token_file: str | Path | None = None, progress: bool = True, service=None) dict[source]
Upload a prepared
PublicationContentto YouTube.Maps the content to a YouTube snippet (chapters embedded in the description when present), uploads, attaches the SRT caption track (language =
content.audio_languageorcontent.language) and the thumbnail when available, and adds the video to the configured playlist.privacy_statusandplaylistdefault to yourybconfig (seeyb.config): unset means “use the config value”, so privacy falls back tounlistedand the video joins your configured playlist. Passplaylist=Noneto skip the playlist for one call, orplaylist="Name"to override the target.- Returns:
{"video_id", "url", "studio_url", "privacy_status", "captions", "thumbnail", "playlist"}.privacy_statusreflects what YouTube actually set (an unaudited project may force"private");playlistis theadd_video_to_playlist()result orNone.
- yb.set_chapters(video_id: str, chapters: Sequence[Chapter], *, header: str = 'Chapters:', service=None, **cred_kwargs) dict[source]
Insert/replace a chapters block in the video’s description.
Strips any prior block under
headerand appends the new one. YouTube renders interactive chapters when the first timestamp is0:00.
- yb.update_video_fields(video_id: str, *, title: str | None = None, description: str | None = None, tags: Sequence[str] | None = None, category_id: str | None = None, default_language: str | None = None, default_audio_language: str | None = None, service=None, **cred_kwargs) dict[source]
Patch selected snippet fields, preserving the rest.
Fetches the current snippet, overlays the provided fields, and updates.
categoryIdmust be present (kept from the existing snippet if not given).
- yb.upsert_caption(video_id: str, track: CaptionTrack | str | Path, *, language: str | None = None, name: str = '', is_draft: bool = False, replace: bool = True, service=None, **cred_kwargs) dict[source]
Insert a caption track, or update the existing same-language one.
- Parameters:
video_id – Target video.
track – A
CaptionTrack, or a path (thenlanguagerequired).replace – When an uploaded track in the same language exists, update it (
True, default) rather than inserting a duplicate.
- Returns:
The inserted/updated caption resource.