Metadata-Version: 2.4
Name: fetchworks
Version: 0.1.0
Summary: YouTube transcripts — timestamped segments, plain text, SRT, VTT from videos, channels, playlists, and search. Thin stdlib client for the Fetchworks YouTube Transcript Scraper on Apify.
Author: Fetchworks
License: MIT
Project-URL: Homepage, https://apify.com/fetchworks/youtube-transcript-scraper
Project-URL: Repository, https://github.com/fetchworks/youtube-transcript-client-py
Keywords: youtube,transcript,captions,subtitles,srt,vtt,apify
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# fetchworks

YouTube transcripts for Python — timestamped segments, plain text, SRT, and VTT from videos, Shorts, channels, playlists, and search results.

This is a thin, pure-stdlib client (no dependencies, Python 3.9+) for the [Fetchworks YouTube Transcript Scraper](https://apify.com/fetchworks/youtube-transcript-scraper) on Apify. The extraction runs on Apify's infrastructure; you bring your own Apify token. Pricing is $2 per 1,000 transcripts — only delivered transcripts are billed. Failed videos (no captions, blocked, unavailable) cost nothing.

## Install

```bash
pip install fetchworks
```

## Quickstart

```python
import os
from fetchworks import FetchworksClient

client = FetchworksClient(os.environ["APIFY_TOKEN"])
item = client.get_transcript("https://www.youtube.com/watch?v=jNQXAC9IVRw")
print(item["status"])  # "ok"
print(item["text"])    # "All right, so here we are…"
```

Get a token by [signing up at apify.com](https://console.apify.com/sign-up) (free tier included), then copy it from [console.apify.com/settings/integrations](https://console.apify.com/settings/integrations).

## API

All methods return dataset items in the exact shape the actor emits — including an honest per-video `status` (`ok`, `no_captions`, `blocked`, `live_stream`, `age_restricted`, `unavailable`, `translation_unavailable`, `po_token_required`, `error`). A video without captions comes back as an item with `status: "no_captions"`, never a silent empty transcript.

```python
# One video (URL, Shorts/youtu.be/embed URL, or bare 11-char ID)
item = client.get_transcript("jNQXAC9IVRw", languages=["en", "de"])

# A batch of videos
items = client.get_transcripts(["url1", "url2"], output_formats=["text", "srt"])

# A channel's uploads, newest first
uploads = client.get_channel_transcripts("@3blue1brown", max_videos_per_channel=25)

# A playlist
playlist = client.get_playlist_transcripts("PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr")

# Top results for a YouTube search
results = client.search("neural networks explained", max_search_results=10)
```

Jobs expected to cover fewer than 60 videos run on Apify's synchronous endpoint and return in seconds. Larger jobs (big batches, whole channels, playlists) start an actor run and poll until it finishes — no code change needed on your side.

## Options

Every method accepts keyword options mirroring the actor input:

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `languages` | `list[str]` | `["en"]` | Language priority list (ISO codes). First available caption track wins; the item reports the actual `language` used. |
| `prefer_auto_generated` | `bool` | `False` | Prefer auto-generated (ASR) tracks when a manual track also exists. |
| `translate_to` | `str` | — | Target language for YouTube caption auto-translation. Best-effort; failures come back as `translation_unavailable` and are not billed. |
| `output_formats` | `list[str]` | `["segments", "text"]` | Any of `"segments"`, `"text"`, `"srt"`, `"vtt"`. |
| `include_metadata` | `bool` | `True` | Include title, channel, duration, views, publish date, etc. Free. |
| `include_chapters` | `bool` | `False` | Include video chapters (one extra request per video). |
| `max_videos_per_channel` | `int` | `100` | Channel method only: upper bound on videos taken, newest first. |
| `max_search_results` | `int` | `50` | Search method only: upper bound on videos taken per query. |

Client-level options: `base_url`, `poll_interval` (default 3.0 s), `max_wait` (default 30 min).

## Result shape

```python
{
    "videoId": "jNQXAC9IVRw",
    "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "status": "ok",
    "language": "en",
    "isAutoGenerated": False,
    "availableLanguages": [{"languageCode": "en", "kind": "manual", "name": "English"}],
    "segments": [{"start": 1.3, "dur": 3.4, "text": "All right, so here we are"}],
    "text": "All right, so here we are…",
    "srt": "…",   # when requested
    "vtt": "…",   # when requested
    "metadata": {"title": "Me at the zoo", "author": "jawed", "lengthSeconds": 19},
}
```

## Links

- Actor page and pricing: https://apify.com/fetchworks/youtube-transcript-scraper
- Apify API tokens: https://console.apify.com/settings/integrations

## License

MIT
