1111
1212from __future__ import annotations
1313
14- from typing import Any , AsyncIterator , Iterable , Iterator , Literal , Optional , Protocol
14+ from typing import (
15+ Any ,
16+ AsyncIterator ,
17+ Iterable ,
18+ Iterator ,
19+ Literal ,
20+ Optional ,
21+ Protocol ,
22+ TypedDict ,
23+ Union ,
24+ )
1525
1626from .._transport import parse_usage as _usage
1727from ..models import ApiErrorBlock , BatchResponse , Transcript , VideoList
2535_JOB = "/api/v2/transcripts/jobs/{job_id}"
2636
2737
38+ class ChunkOptions (TypedDict , total = False ):
39+ """Sizes for ``chunks=``: ``max_tokens`` 100 to 2000 (default 500) and
40+ ``overlap_tokens`` 0 to 200, smaller than ``max_tokens`` (default 50)."""
41+
42+ max_tokens : int
43+ overlap_tokens : int
44+
45+
46+ Chunks = Union [bool , ChunkOptions ]
47+
48+
49+ def _formats (markdown : Optional [bool ], chunks : Optional [Chunks ]) -> dict [str , Any ]:
50+ """The LLM-ready output options, only the ones set. Both are free."""
51+ out : dict [str , Any ] = {}
52+ if markdown is not None :
53+ out ["markdown" ] = markdown
54+ if chunks is not None and chunks is not False :
55+ out ["chunks" ] = dict (chunks ) if isinstance (chunks , dict ) else True
56+ return out
57+
58+
2859# ── Requester protocols (avoid importing the client → no circular import) ─────
2960class _SyncRequester (Protocol ):
3061 def _request (
@@ -109,6 +140,8 @@ def video(
109140 * ,
110141 mode : Optional [Literal ["auto" , "captions" , "audio" ]] = None ,
111142 timestamps : Optional [bool ] = None ,
143+ markdown : Optional [bool ] = None ,
144+ chunks : Optional [Chunks ] = None ,
112145 callback_url : Optional [str ] = None ,
113146 idempotency_key : Optional [str ] = None ,
114147 ) -> Transcript :
@@ -117,6 +150,12 @@ def video(
117150 ``video`` is a YouTube, TikTok or Instagram URL, a direct media file
118151 URL, or a bare YouTube ID.
119152
153+ ``markdown=True`` adds :attr:`~transcriptfetch.Transcript.markdown`;
154+ ``chunks=True`` (or ``{"max_tokens": 800, "overlap_tokens": 80}``)
155+ adds :attr:`~transcriptfetch.Transcript.chunks`, pieces sized for an
156+ embedding model with a link to the moment each one starts. Both are
157+ free, and an audio-transcription job delivers them too.
158+
120159 When the source has no captions the API transcribes its audio and
121160 answers with a job instead: the returned
122161 :class:`~transcriptfetch.Transcript` then has ``status ==
@@ -134,7 +173,8 @@ def video(
134173 "callback_url" : callback_url ,
135174 }.items ()
136175 if v is not None
137- },
176+ }
177+ | _formats (markdown , chunks ),
138178 idempotent = True ,
139179 idempotency_key = idempotency_key ,
140180 )
@@ -204,6 +244,8 @@ def batch(
204244 video_ids : Iterable [str ],
205245 * ,
206246 mode : Optional [str ] = None ,
247+ markdown : Optional [bool ] = None ,
248+ chunks : Optional [Chunks ] = None ,
207249 idempotency_key : Optional [str ] = None ,
208250 ) -> BatchResponse :
209251 """Fetch up to 50 transcripts in one call (same inputs as :meth:`video`).
@@ -225,6 +267,7 @@ def batch(
225267 body : dict [str , Any ] = {"videoIds" : list (video_ids )}
226268 if mode is not None :
227269 body ["mode" ] = mode
270+ body .update (_formats (markdown , chunks ))
228271 env = self ._c ._request (
229272 "POST" ,
230273 _BATCH ,
@@ -278,6 +321,8 @@ async def video(
278321 * ,
279322 mode : Optional [Literal ["auto" , "captions" , "audio" ]] = None ,
280323 timestamps : Optional [bool ] = None ,
324+ markdown : Optional [bool ] = None ,
325+ chunks : Optional [Chunks ] = None ,
281326 callback_url : Optional [str ] = None ,
282327 idempotency_key : Optional [str ] = None ,
283328 ) -> Transcript :
@@ -294,7 +339,8 @@ async def video(
294339 "callback_url" : callback_url ,
295340 }.items ()
296341 if v is not None
297- },
342+ }
343+ | _formats (markdown , chunks ),
298344 idempotent = True ,
299345 idempotency_key = idempotency_key ,
300346 )
@@ -357,6 +403,8 @@ async def batch(
357403 video_ids : Iterable [str ],
358404 * ,
359405 mode : Optional [str ] = None ,
406+ markdown : Optional [bool ] = None ,
407+ chunks : Optional [Chunks ] = None ,
360408 idempotency_key : Optional [str ] = None ,
361409 ) -> BatchResponse :
362410 """Fetch up to 50 transcripts in one call. See :meth:`Transcripts.batch`
@@ -366,6 +414,7 @@ async def batch(
366414 body : dict [str , Any ] = {"videoIds" : list (video_ids )}
367415 if mode is not None :
368416 body ["mode" ] = mode
417+ body .update (_formats (markdown , chunks ))
369418 env = await self ._c ._request (
370419 "POST" ,
371420 _BATCH ,
0 commit comments