Basic usage ########### This document shows the basic usage examples. .. contents:: Contents :depth: 1 :backlinks: top :local: Creating playback ***************** .. currentmodule:: ytpb.playback Default usage ============= First, let's create a :class:`Playback` for a YouTube video and fetch some essential information: .. code-block:: python from ytpb.playback import Playback playback = Playback(stream_url := "https://www.youtube.com/watch?v=...") playback.fetch_and_set_essential() Such information includes basic video details, :class:`~ytpb.info.YouTubeVideoInfo`, and a set of streams (MPEG-DASH representations), :class:`~ytpb.types.SetOfStreams`. By default, :class:`~ytpb.fetchers.YtpbInfoFetcher` is used to download and parse all of the information needed to further work. Another available fetcher is :class:`~ytpb.fetchers.YoutubeDLInfoFetcher`, which calls `yt-dlp `__ under the hood: .. code-block:: python from ytpb.fetchers import YoutubeDLInfoFetcher fetcher = YoutubeDLInfoFetcher(stream_url) playback = Playback(stream_url, fetcher=fetcher) playback.fetch_and_set_essential() *Note:* The default fetcher will be *perhaps* changed later to :class:`~ytpb.fetchers.YoutubeDLInfoFetcher` as supposed to be more reliable. :meth:`.Playback.from_url` can be used to create a playback in one step: .. code-block:: python playback = Playback.from_url(stream_url, fetcher=fetcher) After, the basic information and streams are available. Reading and writing to cache ============================ By default, cache is not touched during playback creation. With this, each execution downloads and parses a main HTML page and MPEG-DASH MPD file, which is not optimal. The :meth:`Playback.from_cache` can be used to create a playback from an existing cache item (it also involves writing to cache). If an item is not found or expired, :class:`~ytpb.errors.CachedItemNotFoundError` will be raised. .. code-block:: python from ytpb.errors import CachedItemNotFoundError try: playback = Playback.from_cache(stream_url) except CachedItemNotFoundError: playback = Playback.from_url(stream_url, write_to_cache=True) Another, more convenient way is to use :meth:`ytpb.get_playback`, which uses cache by default: .. code-block:: python from ytpb import get_playback playback = get_playback(stream_url_or_id) Locating moments and intervals ****************************** Once we have a playback ready to use, let's locate a rewind moment of :class:`.RewindMoment` by mapping a point of :class:`ytpb.types.AbsolutePointInStream` (a date or a sequence number) to a segment sequence number: .. code-block:: python >>> from datetime import datetime >>> playback.locate_moment(datetime(2024, 3, 28, 8)) RewindMoment(date=datetime.datetime(2024, 3, 28, 8, 0), sequence=93604, cut_at=4.660386085510254, is_end=False, falls_in_gap=False) >>> playback.locate_moment(93604) RewindMoment(date=datetime.datetime(2024, 3, 28, 4, 59, 55, 339614, tzinfo=datetime.timezone.utc), sequence=93604, cut_at=0, is_end=False, falls_in_gap=False) While locating a moment requires an absolute point in stream, locating an interval accepts both absolute and relative (:class:`~ytpb.types.RelativePointInStream`) points. For example, we can locate a 30-second interval starting on a specific date: >>> playback.locate_interval(datetime(2024, 3, 28, 8), timedelta(seconds=30)) RewindInterval(start=RewindMoment(date=datetime.datetime(2024, 3, 28, 8, 0), sequence=93604, cut_at=4.660386085510254, is_end=False, falls_in_gap=False), end=RewindMoment(date=datetime.datetime(2024, 3, 28, 8, 0, 30), sequence=93610, cut_at=4.387692928314209, is_end=True, falls_in_gap=False)) The instance of :class:`~ytpb.locate.SegmentLocator` is used to locate moments at lower level: .. code-block:: python >>> from ytpb.locate import SegmentLocator >>> base_url = next(iter(playback.streams)).base_url >>> sl = SegmentLocator(base_url, session=playback.session) >>> sl.find_sequence_by_time(1711602000.0) LocateResult(sequence=93604, time_difference=4.660386085510254, falls_in_gap=False, track=[(97742, -20700.796554088593), (93601, 19.838277101516724), (93604, 4.660386085510254)]) Selecting streams ***************** The information about audio and video streams are described by :class:`ytpb.types.AudioStream` and :class:`ytpb.types.VideoStream` types, respectively. These types are basically aliases to :class:`~ytpb.representations.AudioRepresentationInfo` and :class:`~ytpb.representations.VideoRepresentationInfo`, which in turn are referenced to MPEG-DASH `representations `__. The streams can be selected in different ways: unambiguously by the itag value or ambiguously by using a predicate function or :ref:`format spec`. .. code-block:: python >>> from ytpb.types import AudioOrVideoStream, VideoStream >>> # Get the audio stream by its itag: >>> playback.streams.get_by_itag("247") VideoRepresentationInfo(itag=247) >>> # Filtering streams by a predicate function: >>> video_streams: SetOfStreams = playback.streams.filter( ... lambda x: x.type == "video" ... ) >>> # Querying streams by a format spec: >>> queried: list[AudioOrVideoStream] = video_streams.query( ... "format eq webm and quality ge 720p" ... ) [ VideoRepresentationInfo(itag=248), VideoRepresentationInfo(itag=247), ] Making actions ************** Actions can be viewed as top-level functions. They come into play when a playback is ready to use and moments or intervals are located. There are built-in download, compose, and capture actions. Let's, for example, download a 30-seconds audio and video excerpt with showing download progress: .. code-block:: python from datetime import datetime, timedelta, timezone from pathlib import Path from ytpb.actions.download import download_excerpt, RichProgressReporter rewind_interval = playback.locate_interval( datetime(2024, 1, 2, 12, tzinfo=timezone.utc), timedelta(minutes=30), ) progress_reporter = RichProgressReporter() total_sequences = len(rewind_interval.sequences) progress_reporter.add_task("Audio", total=total_sequences) progress_reporter.add_task("Video", total=total_sequences) download_excerpt( rewind_interval, audio_stream=playback.streams.get_by_itag("140"), video_stream=playback.streams.query( "format eq webm and quality eq 720p | best" ), output_stem=Path("path/to/output"), segments_directory=Path("path/to/segments"), progress_reporter=progress_reporter, )