flashdata-youtube-transcript 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (17) hide show
  1. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/.gitignore +9 -9
  2. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/LICENSE +21 -21
  3. flashdata_youtube_transcript-0.2.0/PKG-INFO +92 -0
  4. flashdata_youtube_transcript-0.2.0/README.md +70 -0
  5. flashdata_youtube_transcript-0.2.0/flashdata_youtube_transcript_cli/__init__.py +1 -0
  6. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/flashdata_youtube_transcript_cli/_capability.py +2 -2
  7. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/flashdata_youtube_transcript_cli/_client.py +1 -1
  8. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/flashdata_youtube_transcript_cli/_inputs.py +21 -3
  9. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/flashdata_youtube_transcript_cli/cli.py +201 -201
  10. flashdata_youtube_transcript-0.2.0/input.schema.json +46 -0
  11. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/pyproject.toml +29 -29
  12. flashdata_youtube_transcript-0.1.1/PKG-INFO +0 -95
  13. flashdata_youtube_transcript-0.1.1/README.md +0 -73
  14. flashdata_youtube_transcript-0.1.1/flashdata_youtube_transcript_cli/__init__.py +0 -1
  15. flashdata_youtube_transcript-0.1.1/input.schema.json +0 -29
  16. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/flashdata_youtube_transcript_cli/_storage.py +0 -0
  17. {flashdata_youtube_transcript-0.1.1 → flashdata_youtube_transcript-0.2.0}/flashdata_youtube_transcript_cli/storage.schema.json +0 -0
@@ -1,9 +1,9 @@
1
- .venv/
2
- __pycache__/
3
- .pytest_cache/
4
- .ruff_cache/
5
- packages/
6
- dist/
7
- evidence/
8
-
9
- publish-dist/
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ packages/
6
+ dist/
7
+ evidence/
8
+
9
+ publish-dist/
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 FlashData contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FlashData contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.5
2
+ Name: flashdata-youtube-transcript
3
+ Version: 0.2.0
4
+ Summary: Export timestamped automatic captions for retrieval with the FlashData JSON CLI
5
+ Project-URL: Homepage, https://flashdata.dev/docs/sources/youtube_transcript
6
+ Project-URL: Documentation, https://flashdata.dev/docs/sources/youtube_transcript
7
+ Project-URL: Pricing, https://flashdata.dev/pricing
8
+ Project-URL: Support, https://flashdata.dev
9
+ Project-URL: Source, https://github.com/flashdatadev/flashdata-mcp/tree/main/packages/python-cli
10
+ Author-email: FlashData <support@flashdata.dev>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: cli,flashdata,json,uvx,youtube-transcript
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: httpx<1,>=0.28.1
19
+ Requires-Dist: jsonschema<5,>=4.23
20
+ Requires-Dist: pydantic<3,>=2.7.4
21
+ Description-Content-Type: text/markdown
22
+
23
+ # YouTube Transcript CLI — FlashData
24
+
25
+ Export timestamped automatic captions for retrieval. This command calls the `youtube_transcript` API and returns JSON for your
26
+ shell script, data pipeline or agent tool runner. It does not invoke a language model.
27
+
28
+ Reads an existing track. No ASR, translation, or automatic fallback to uploader captions.
29
+
30
+ ## Install and run
31
+
32
+ Requires Python 3.10 or newer. [uv installation](https://docs.astral.sh/uv/getting-started/installation/)
33
+ provides `uvx`, which runs this package in an isolated environment.
34
+
35
+ 1. [Create a FlashData account](https://flashdata.dev/signup), obtain an API Key and fund Credits.
36
+ 2. Set `FLASHDATA_API_KEY` using your shell or secret manager. The CLI does not accept a key flag
37
+ or persist credentials. [Authentication and source documentation](https://flashdata.dev/docs/sources/youtube_transcript).
38
+ 3. Run this example after reviewing the input and cost:
39
+
40
+ ```sh
41
+ uvx flashdata-youtube-transcript@0.2.0 --video-id dQw4w9WgXcQ --options-json '{"language": "en", "auto": true}' --output result.json
42
+ ```
43
+
44
+ `--output` creates a new file with owner-only permissions where supported and refuses existing
45
+ files before making a paid request. Without it, the full JSON response goes to stdout. Errors
46
+ go to stderr. Exit 0 means the response was exported, 2 means local input/configuration failure,
47
+ and 1 means request or output failure. For downloads, exit 0 can describe an unfinished job.
48
+
49
+ For repeated use, install `uv tool install flashdata-youtube-transcript==0.2.0`; the command name is `flashdata-youtube-transcript`.
50
+ Alternatively use `python -m pip install flashdata-youtube-transcript==0.2.0` in your virtual environment.
51
+
52
+ ## Inputs and output
53
+
54
+ Use `--video-id` for the primary input. Additional options use
55
+ `--options-json` as a JSON object, with strict field/type/range validation. Source is fixed by
56
+ this package. Google count and locale options remain omitted unless supplied.
57
+
58
+ | Option | Meaning |
59
+ | --- | --- |
60
+ | `language` | One code or 1–10 distinct codes. Omit to prefer en-orig, then en. Lists return tracks and per-language errors. Default: `null`. |
61
+ | `auto` | True requests automatic captions; false requests manually authored captions. Default: `true`. |
62
+
63
+ Run `uvx flashdata-youtube-transcript@0.2.0 --schema` to inspect all fields, nested filters, allowed values and
64
+ defaults without a Key or an API call. `--help` and `--version` are also local-only.
65
+
66
+ The JSON output preserves the API result envelope and optional fields: results[]: single track with segments/fullText, or tracks and per-language errors for a language list; usage.credits.
67
+ Missing fields remain missing; empty results remain empty. No article text, translated captions,
68
+ model conclusions, rights grants or keyword volumes are invented. Exact source contracts are
69
+ documented [here](https://flashdata.dev/docs/sources/youtube_transcript).
70
+
71
+ ## Fees and failure handling
72
+
73
+ This MIT-licensed CLI is free to install. FlashData API requests consume paid Credits;
74
+ [current pricing](https://flashdata.dev/pricing) applies. Extended YouTube Search has a separate
75
+ rate. Media delivery is billed by delivered volume, and your cloud-storage costs are separate.
76
+ There is no automatic pagination, retry, polling, scheduled execution or model call.
77
+
78
+ Each HTTP request has a 120-second network timeout (15-second connect timeout) and a 20-MiB response
79
+ limit. A timeout or failure may occur after a paid request has been accepted. Check Jobs in
80
+ the [FlashData console](https://flashdata.dev) before resubmitting. HTTP 401 means check the Key;
81
+ 402 means insufficient Credits; 429 means rate/quota limit. A partial output file is retained
82
+ on failure. Do not retry a paid query just because output handling failed.
83
+
84
+ Input values and your API Key are sent to FlashData to perform the request. A completed
85
+ download-result read may fetch a signed JSON object from the result origin returned by
86
+ FlashData; the API Key is never sent to that object URL. Only process data appropriate for
87
+ your output destination. [Privacy policy](https://flashdata.dev/legal/privacy).
88
+
89
+ Source and MIT license are included in the source distribution.
90
+
91
+ [Source documentation](https://flashdata.dev/docs/sources/youtube_transcript) ·
92
+ [Pricing](https://flashdata.dev/pricing) · [Sign up](https://flashdata.dev/signup)
@@ -0,0 +1,70 @@
1
+ # YouTube Transcript CLI — FlashData
2
+
3
+ Export timestamped automatic captions for retrieval. This command calls the `youtube_transcript` API and returns JSON for your
4
+ shell script, data pipeline or agent tool runner. It does not invoke a language model.
5
+
6
+ Reads an existing track. No ASR, translation, or automatic fallback to uploader captions.
7
+
8
+ ## Install and run
9
+
10
+ Requires Python 3.10 or newer. [uv installation](https://docs.astral.sh/uv/getting-started/installation/)
11
+ provides `uvx`, which runs this package in an isolated environment.
12
+
13
+ 1. [Create a FlashData account](https://flashdata.dev/signup), obtain an API Key and fund Credits.
14
+ 2. Set `FLASHDATA_API_KEY` using your shell or secret manager. The CLI does not accept a key flag
15
+ or persist credentials. [Authentication and source documentation](https://flashdata.dev/docs/sources/youtube_transcript).
16
+ 3. Run this example after reviewing the input and cost:
17
+
18
+ ```sh
19
+ uvx flashdata-youtube-transcript@0.2.0 --video-id dQw4w9WgXcQ --options-json '{"language": "en", "auto": true}' --output result.json
20
+ ```
21
+
22
+ `--output` creates a new file with owner-only permissions where supported and refuses existing
23
+ files before making a paid request. Without it, the full JSON response goes to stdout. Errors
24
+ go to stderr. Exit 0 means the response was exported, 2 means local input/configuration failure,
25
+ and 1 means request or output failure. For downloads, exit 0 can describe an unfinished job.
26
+
27
+ For repeated use, install `uv tool install flashdata-youtube-transcript==0.2.0`; the command name is `flashdata-youtube-transcript`.
28
+ Alternatively use `python -m pip install flashdata-youtube-transcript==0.2.0` in your virtual environment.
29
+
30
+ ## Inputs and output
31
+
32
+ Use `--video-id` for the primary input. Additional options use
33
+ `--options-json` as a JSON object, with strict field/type/range validation. Source is fixed by
34
+ this package. Google count and locale options remain omitted unless supplied.
35
+
36
+ | Option | Meaning |
37
+ | --- | --- |
38
+ | `language` | One code or 1–10 distinct codes. Omit to prefer en-orig, then en. Lists return tracks and per-language errors. Default: `null`. |
39
+ | `auto` | True requests automatic captions; false requests manually authored captions. Default: `true`. |
40
+
41
+ Run `uvx flashdata-youtube-transcript@0.2.0 --schema` to inspect all fields, nested filters, allowed values and
42
+ defaults without a Key or an API call. `--help` and `--version` are also local-only.
43
+
44
+ The JSON output preserves the API result envelope and optional fields: results[]: single track with segments/fullText, or tracks and per-language errors for a language list; usage.credits.
45
+ Missing fields remain missing; empty results remain empty. No article text, translated captions,
46
+ model conclusions, rights grants or keyword volumes are invented. Exact source contracts are
47
+ documented [here](https://flashdata.dev/docs/sources/youtube_transcript).
48
+
49
+ ## Fees and failure handling
50
+
51
+ This MIT-licensed CLI is free to install. FlashData API requests consume paid Credits;
52
+ [current pricing](https://flashdata.dev/pricing) applies. Extended YouTube Search has a separate
53
+ rate. Media delivery is billed by delivered volume, and your cloud-storage costs are separate.
54
+ There is no automatic pagination, retry, polling, scheduled execution or model call.
55
+
56
+ Each HTTP request has a 120-second network timeout (15-second connect timeout) and a 20-MiB response
57
+ limit. A timeout or failure may occur after a paid request has been accepted. Check Jobs in
58
+ the [FlashData console](https://flashdata.dev) before resubmitting. HTTP 401 means check the Key;
59
+ 402 means insufficient Credits; 429 means rate/quota limit. A partial output file is retained
60
+ on failure. Do not retry a paid query just because output handling failed.
61
+
62
+ Input values and your API Key are sent to FlashData to perform the request. A completed
63
+ download-result read may fetch a signed JSON object from the result origin returned by
64
+ FlashData; the API Key is never sent to that object URL. Only process data appropriate for
65
+ your output destination. [Privacy policy](https://flashdata.dev/legal/privacy).
66
+
67
+ Source and MIT license are included in the source distribution.
68
+
69
+ [Source documentation](https://flashdata.dev/docs/sources/youtube_transcript) ·
70
+ [Pricing](https://flashdata.dev/pricing) · [Sign up](https://flashdata.dev/signup)
@@ -1,2 +1,2 @@
1
- SOURCE = 'youtube_transcript'
2
- COMMAND = 'flashdata-youtube-transcript'
1
+ SOURCE = 'youtube_transcript'
2
+ COMMAND = 'flashdata-youtube-transcript'
@@ -27,7 +27,7 @@ class Client:
27
27
  self.transport = transport
28
28
 
29
29
  def _request(self, method, url, payload=None, *, authenticated=True):
30
- headers = {"Accept": "application/json", "User-Agent": "flashdata-cli/0.1.1"}
30
+ headers = {"Accept": "application/json", "User-Agent": "flashdata-cli/0.2.0"}
31
31
  if authenticated:
32
32
  headers.update({"X-API-Key": self.api_key, "X-Request-Id": str(uuid4())})
33
33
  try:
@@ -125,9 +125,27 @@ class YouTubeMetadataInput(_VideoInput):
125
125
 
126
126
 
127
127
  class YouTubeTranscriptInput(_VideoInput):
128
- """YouTube transcript parameters; no implicit language or caption fallback."""
128
+ """Existing automatic captions, with optional language selection."""
129
+
130
+ language: (
131
+ Language
132
+ | Annotated[
133
+ list[Language],
134
+ Field(min_length=1, max_length=10, json_schema_extra={"uniqueItems": True}),
135
+ ]
136
+ | None
137
+ ) = Field(
138
+ default=None,
139
+ description="One code or 1–10 distinct codes. Omit to prefer en-orig, then en. Lists return tracks and per-language errors.",
140
+ )
141
+
142
+ @field_validator("language")
143
+ @classmethod
144
+ def distinct_languages(cls, value):
145
+ if isinstance(value, list) and len(set(value)) != len(value):
146
+ raise ValueError("language codes must be distinct")
147
+ return value
129
148
 
130
- language: Language = Field(default="en", description="Requested caption language code.")
131
149
  auto: bool = Field(
132
150
  default=True,
133
151
  description="True requests automatic captions; false requests manually authored captions.",
@@ -135,7 +153,7 @@ class YouTubeTranscriptInput(_VideoInput):
135
153
 
136
154
 
137
155
  class YouTubeCaptionsInput(YouTubeTranscriptInput):
138
- """Select uploader captions by default, without fallback."""
156
+ """Select uploaded captions without switching caption source."""
139
157
 
140
158
  auto: bool = Field(default=False, description="False selects manual; true automatic captions.")
141
159
 
@@ -1,201 +1,201 @@
1
- """CLI entry point shared at build time by fifteen independently named packages."""
2
-
3
- import argparse
4
- import json
5
- import os
6
- import sys
7
- from pathlib import Path
8
- from urllib.parse import urlsplit
9
-
10
- from pydantic import ValidationError
11
-
12
- from . import __version__
13
- from ._client import UNKNOWN_OUTCOME, Client, FlashDataError
14
- from ._inputs import (
15
- DownloadJobInput,
16
- GoogleInput,
17
- YouTubeCaptionsInput,
18
- YouTubeChannelInput,
19
- YouTubeDownloadInput,
20
- YouTubeExtendedSearchInput,
21
- YouTubeMetadataInput,
22
- YouTubeSearchInput,
23
- YouTubeSuggestionsInput,
24
- YouTubeTrainabilityInput,
25
- YouTubeTranscriptInput,
26
- )
27
- from ._storage import storage_config
28
-
29
- CONTRACTS = {
30
- **{
31
- f"google_{kind}": GoogleInput
32
- for kind in ("search", "news", "images", "shopping", "videos", "places")
33
- },
34
- "youtube_search": YouTubeSearchInput,
35
- "youtube_search_extended": YouTubeExtendedSearchInput,
36
- "youtube_metadata": YouTubeMetadataInput,
37
- "youtube_transcript": YouTubeTranscriptInput,
38
- "youtube_captions": YouTubeCaptionsInput,
39
- "youtube_trainability": YouTubeTrainabilityInput,
40
- "youtube_channel": YouTubeChannelInput,
41
- "youtube_suggestions": YouTubeSuggestionsInput,
42
- "youtube_download": YouTubeDownloadInput,
43
- }
44
-
45
-
46
- class Parser(argparse.ArgumentParser):
47
- def error(self, message):
48
- raise FlashDataError("Invalid command arguments. Use --help for supported options.")
49
-
50
-
51
- def parser_for(source, command):
52
- parser = Parser(
53
- prog=command,
54
- description=(
55
- f"Run the {source} FlashData API and export JSON. API calls use paid Credits. "
56
- "Read FLASHDATA_API_KEY from the environment; never pass credentials as arguments."
57
- ),
58
- )
59
- parser.add_argument("--version", action="version", version=f"{command} {__version__}")
60
- parser.add_argument(
61
- "--schema", action="store_true", help="Print the input schema; no API call."
62
- )
63
- parser.add_argument(
64
- "--output", type=Path, help="New JSON file (0600); existing files are refused."
65
- )
66
- primary = (
67
- "handle"
68
- if source == "youtube_channel"
69
- else "query"
70
- if (
71
- source.startswith("google_")
72
- or source in ("youtube_search", "youtube_search_extended", "youtube_suggestions")
73
- )
74
- else "video-id"
75
- )
76
- parser.add_argument("--" + primary, help="Primary query, channel handle, or YouTube ID/URL.")
77
- parser.add_argument(
78
- "--options-json", help="Source options as a JSON object, without credentials."
79
- )
80
- if source == "youtube_download":
81
- parser.add_argument("--operation", choices=["status", "result", "submit"], default="status")
82
- parser.add_argument("--job-id", help="Previously saved job ID for status/result.")
83
- parser.add_argument(
84
- "--allow-submit", action="store_true", help="Permit one paid storage write."
85
- )
86
- return parser
87
-
88
-
89
- def payload_for(source, args, environ):
90
- operation = getattr(args, "operation", "query")
91
- if operation in ("status", "result"):
92
- if args.video_id or args.options_json or args.allow_submit:
93
- raise FlashDataError("Status/result accepts --job-id, without submission inputs.")
94
- return operation, DownloadJobInput(job_id=args.job_id).model_dump()
95
- try:
96
- options = json.loads(args.options_json) if args.options_json is not None else {}
97
- except ValueError:
98
- raise FlashDataError("--options-json must be a JSON object.") from None
99
- if not isinstance(options, dict) or any(
100
- field in options for field in ("source", "query", "video_id", "handle", "storage")
101
- ):
102
- raise FlashDataError(
103
- "Use dedicated primary inputs; source is fixed and secrets use env vars."
104
- )
105
- values = dict(options)
106
- for primary in ("query", "video_id", "handle"):
107
- value = getattr(args, primary, None)
108
- if value is not None:
109
- values[primary] = value
110
- data = CONTRACTS[source].model_validate(values).model_dump(exclude_none=True, by_alias=True)
111
- primary = next(key for key in ("query", "video_id", "handle") if key in data)
112
- payload = {"source": source, primary: data.pop(primary), "params": data}
113
- if operation == "submit":
114
- if args.job_id or not args.allow_submit:
115
- raise FlashDataError(
116
- "New delivery requires --allow-submit; saved jobs use status/result."
117
- )
118
- storage = storage_config(environ.get("FLASHDATA_DOWNLOAD_STORAGE", ""))
119
- endpoint = storage.get("endpoint_url")
120
- if endpoint:
121
- url = urlsplit(endpoint)
122
- if (
123
- url.scheme != "https"
124
- or not url.hostname
125
- or url.username
126
- or url.password
127
- or url.query
128
- or url.fragment
129
- ):
130
- raise FlashDataError("Storage endpoint must be an HTTPS URL without credentials.")
131
- payload["params"]["storage"] = storage
132
- return operation, payload
133
-
134
-
135
- def run(
136
- source, command, argv=None, *, environ=None, stdout=None, stderr=None, client_factory=Client
137
- ):
138
- environ = os.environ if environ is None else environ
139
- stdout = sys.stdout if stdout is None else stdout
140
- stderr = sys.stderr if stderr is None else stderr
141
- output = None
142
- request_started = False
143
- output_complete = False
144
- try:
145
- args = parser_for(source, command).parse_args(argv)
146
- if args.schema:
147
- stdout.write(json.dumps(CONTRACTS[source].model_json_schema(), indent=2) + "\n")
148
- return 0
149
- operation, payload = payload_for(source, args, environ)
150
- key = environ.get("FLASHDATA_API_KEY", "").strip()
151
- if not key or any(ord(c) < 32 or ord(c) == 127 for c in key):
152
- raise FlashDataError("Set a valid FLASHDATA_API_KEY environment variable.")
153
- if args.output:
154
- # Reserve the new path before spending Credits, without replacing user files.
155
- fd = os.open(args.output, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
156
- output = os.fdopen(fd, "w", encoding="utf-8")
157
- client = client_factory(key)
158
- request_started = True
159
- result = (
160
- client.query(payload)
161
- if operation == "query"
162
- else client.submit(payload)
163
- if operation == "submit"
164
- else client.status(payload["job_id"])
165
- if operation == "status"
166
- else client.result(payload["job_id"])
167
- )
168
- destination = output if output else stdout
169
- destination.write(json.dumps(result, ensure_ascii=False, allow_nan=False, indent=2) + "\n")
170
- destination.flush()
171
- output_complete = True
172
- return 0
173
- except ValidationError:
174
- stderr.write("Invalid source input. See --schema for fields, types, ranges and defaults.\n")
175
- return 2
176
- except FlashDataError as error:
177
- stderr.write(str(error) + "\n")
178
- return 1 if request_started else 2
179
- except (OSError, ValueError, TypeError):
180
- stderr.write(
181
- (
182
- "Could not save the response. " + UNKNOWN_OUTCOME
183
- if request_started
184
- else "Could not prepare output or inputs. Check the new output path."
185
- )
186
- + "\n"
187
- )
188
- return 1 if request_started else 2
189
- finally:
190
- if output:
191
- output.close()
192
- if not output_complete:
193
- stderr.write(
194
- "The newly reserved output file is incomplete; check Jobs before retrying.\n"
195
- )
196
-
197
-
198
- def main():
199
- from ._capability import COMMAND, SOURCE
200
-
201
- raise SystemExit(run(SOURCE, COMMAND))
1
+ """CLI entry point shared at build time by fifteen independently named packages."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+ from urllib.parse import urlsplit
9
+
10
+ from pydantic import ValidationError
11
+
12
+ from . import __version__
13
+ from ._client import UNKNOWN_OUTCOME, Client, FlashDataError
14
+ from ._inputs import (
15
+ DownloadJobInput,
16
+ GoogleInput,
17
+ YouTubeCaptionsInput,
18
+ YouTubeChannelInput,
19
+ YouTubeDownloadInput,
20
+ YouTubeExtendedSearchInput,
21
+ YouTubeMetadataInput,
22
+ YouTubeSearchInput,
23
+ YouTubeSuggestionsInput,
24
+ YouTubeTrainabilityInput,
25
+ YouTubeTranscriptInput,
26
+ )
27
+ from ._storage import storage_config
28
+
29
+ CONTRACTS = {
30
+ **{
31
+ f"google_{kind}": GoogleInput
32
+ for kind in ("search", "news", "images", "shopping", "videos", "places")
33
+ },
34
+ "youtube_search": YouTubeSearchInput,
35
+ "youtube_search_extended": YouTubeExtendedSearchInput,
36
+ "youtube_metadata": YouTubeMetadataInput,
37
+ "youtube_transcript": YouTubeTranscriptInput,
38
+ "youtube_captions": YouTubeCaptionsInput,
39
+ "youtube_trainability": YouTubeTrainabilityInput,
40
+ "youtube_channel": YouTubeChannelInput,
41
+ "youtube_suggestions": YouTubeSuggestionsInput,
42
+ "youtube_download": YouTubeDownloadInput,
43
+ }
44
+
45
+
46
+ class Parser(argparse.ArgumentParser):
47
+ def error(self, message):
48
+ raise FlashDataError("Invalid command arguments. Use --help for supported options.")
49
+
50
+
51
+ def parser_for(source, command):
52
+ parser = Parser(
53
+ prog=command,
54
+ description=(
55
+ f"Run the {source} FlashData API and export JSON. API calls use paid Credits. "
56
+ "Read FLASHDATA_API_KEY from the environment; never pass credentials as arguments."
57
+ ),
58
+ )
59
+ parser.add_argument("--version", action="version", version=f"{command} {__version__}")
60
+ parser.add_argument(
61
+ "--schema", action="store_true", help="Print the input schema; no API call."
62
+ )
63
+ parser.add_argument(
64
+ "--output", type=Path, help="New JSON file (0600); existing files are refused."
65
+ )
66
+ primary = (
67
+ "handle"
68
+ if source == "youtube_channel"
69
+ else "query"
70
+ if (
71
+ source.startswith("google_")
72
+ or source in ("youtube_search", "youtube_search_extended", "youtube_suggestions")
73
+ )
74
+ else "video-id"
75
+ )
76
+ parser.add_argument("--" + primary, help="Primary query, channel handle, or YouTube ID/URL.")
77
+ parser.add_argument(
78
+ "--options-json", help="Source options as a JSON object, without credentials."
79
+ )
80
+ if source == "youtube_download":
81
+ parser.add_argument("--operation", choices=["status", "result", "submit"], default="status")
82
+ parser.add_argument("--job-id", help="Previously saved job ID for status/result.")
83
+ parser.add_argument(
84
+ "--allow-submit", action="store_true", help="Permit one paid storage write."
85
+ )
86
+ return parser
87
+
88
+
89
+ def payload_for(source, args, environ):
90
+ operation = getattr(args, "operation", "query")
91
+ if operation in ("status", "result"):
92
+ if args.video_id or args.options_json or args.allow_submit:
93
+ raise FlashDataError("Status/result accepts --job-id, without submission inputs.")
94
+ return operation, DownloadJobInput(job_id=args.job_id).model_dump()
95
+ try:
96
+ options = json.loads(args.options_json) if args.options_json is not None else {}
97
+ except ValueError:
98
+ raise FlashDataError("--options-json must be a JSON object.") from None
99
+ if not isinstance(options, dict) or any(
100
+ field in options for field in ("source", "query", "video_id", "handle", "storage")
101
+ ):
102
+ raise FlashDataError(
103
+ "Use dedicated primary inputs; source is fixed and secrets use env vars."
104
+ )
105
+ values = dict(options)
106
+ for primary in ("query", "video_id", "handle"):
107
+ value = getattr(args, primary, None)
108
+ if value is not None:
109
+ values[primary] = value
110
+ data = CONTRACTS[source].model_validate(values).model_dump(exclude_none=True, by_alias=True)
111
+ primary = next(key for key in ("query", "video_id", "handle") if key in data)
112
+ payload = {"source": source, primary: data.pop(primary), "params": data}
113
+ if operation == "submit":
114
+ if args.job_id or not args.allow_submit:
115
+ raise FlashDataError(
116
+ "New delivery requires --allow-submit; saved jobs use status/result."
117
+ )
118
+ storage = storage_config(environ.get("FLASHDATA_DOWNLOAD_STORAGE", ""))
119
+ endpoint = storage.get("endpoint_url")
120
+ if endpoint:
121
+ url = urlsplit(endpoint)
122
+ if (
123
+ url.scheme != "https"
124
+ or not url.hostname
125
+ or url.username
126
+ or url.password
127
+ or url.query
128
+ or url.fragment
129
+ ):
130
+ raise FlashDataError("Storage endpoint must be an HTTPS URL without credentials.")
131
+ payload["params"]["storage"] = storage
132
+ return operation, payload
133
+
134
+
135
+ def run(
136
+ source, command, argv=None, *, environ=None, stdout=None, stderr=None, client_factory=Client
137
+ ):
138
+ environ = os.environ if environ is None else environ
139
+ stdout = sys.stdout if stdout is None else stdout
140
+ stderr = sys.stderr if stderr is None else stderr
141
+ output = None
142
+ request_started = False
143
+ output_complete = False
144
+ try:
145
+ args = parser_for(source, command).parse_args(argv)
146
+ if args.schema:
147
+ stdout.write(json.dumps(CONTRACTS[source].model_json_schema(), indent=2) + "\n")
148
+ return 0
149
+ operation, payload = payload_for(source, args, environ)
150
+ key = environ.get("FLASHDATA_API_KEY", "").strip()
151
+ if not key or any(ord(c) < 32 or ord(c) == 127 for c in key):
152
+ raise FlashDataError("Set a valid FLASHDATA_API_KEY environment variable.")
153
+ if args.output:
154
+ # Reserve the new path before spending Credits, without replacing user files.
155
+ fd = os.open(args.output, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
156
+ output = os.fdopen(fd, "w", encoding="utf-8")
157
+ client = client_factory(key)
158
+ request_started = True
159
+ result = (
160
+ client.query(payload)
161
+ if operation == "query"
162
+ else client.submit(payload)
163
+ if operation == "submit"
164
+ else client.status(payload["job_id"])
165
+ if operation == "status"
166
+ else client.result(payload["job_id"])
167
+ )
168
+ destination = output if output else stdout
169
+ destination.write(json.dumps(result, ensure_ascii=False, allow_nan=False, indent=2) + "\n")
170
+ destination.flush()
171
+ output_complete = True
172
+ return 0
173
+ except ValidationError:
174
+ stderr.write("Invalid source input. See --schema for fields, types, ranges and defaults.\n")
175
+ return 2
176
+ except FlashDataError as error:
177
+ stderr.write(str(error) + "\n")
178
+ return 1 if request_started else 2
179
+ except (OSError, ValueError, TypeError):
180
+ stderr.write(
181
+ (
182
+ "Could not save the response. " + UNKNOWN_OUTCOME
183
+ if request_started
184
+ else "Could not prepare output or inputs. Check the new output path."
185
+ )
186
+ + "\n"
187
+ )
188
+ return 1 if request_started else 2
189
+ finally:
190
+ if output:
191
+ output.close()
192
+ if not output_complete:
193
+ stderr.write(
194
+ "The newly reserved output file is incomplete; check Jobs before retrying.\n"
195
+ )
196
+
197
+
198
+ def main():
199
+ from ._capability import COMMAND, SOURCE
200
+
201
+ raise SystemExit(run(SOURCE, COMMAND))
@@ -0,0 +1,46 @@
1
+ {
2
+ "additionalProperties": false,
3
+ "description": "Existing automatic captions, with optional language selection.",
4
+ "properties": {
5
+ "video_id": {
6
+ "description": "11-character YouTube video ID or a YouTube video URL.",
7
+ "title": "Video Id",
8
+ "type": "string"
9
+ },
10
+ "language": {
11
+ "anyOf": [
12
+ {
13
+ "pattern": "^[A-Za-z0-9-]{1,35}$",
14
+ "type": "string"
15
+ },
16
+ {
17
+ "items": {
18
+ "pattern": "^[A-Za-z0-9-]{1,35}$",
19
+ "type": "string"
20
+ },
21
+ "maxItems": 10,
22
+ "minItems": 1,
23
+ "type": "array",
24
+ "uniqueItems": true
25
+ },
26
+ {
27
+ "type": "null"
28
+ }
29
+ ],
30
+ "default": null,
31
+ "description": "One code or 1\u201310 distinct codes. Omit to prefer en-orig, then en. Lists return tracks and per-language errors.",
32
+ "title": "Language"
33
+ },
34
+ "auto": {
35
+ "default": true,
36
+ "description": "True requests automatic captions; false requests manually authored captions.",
37
+ "title": "Auto",
38
+ "type": "boolean"
39
+ }
40
+ },
41
+ "required": [
42
+ "video_id"
43
+ ],
44
+ "title": "YouTubeTranscriptInput",
45
+ "type": "object"
46
+ }
@@ -1,29 +1,29 @@
1
- [build-system]
2
- requires = ["hatchling>=1.27,<2"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "flashdata-youtube-transcript"
7
- version = "0.1.1"
8
- description = "Retrieve existing YouTube transcripts as timestamped segments and full text in JSON; automatic captions by default."
9
- readme = "README.md"
10
- requires-python = ">=3.10"
11
- license = "MIT"
12
- license-files = ["LICENSE"]
13
- authors = [{name = "FlashData", email = "support@flashdata.dev"}]
14
- keywords = ["youtube-transcript", "cli", "json", "uvx", "flashdata"]
15
- classifiers = ["Intended Audience :: Developers", "Programming Language :: Python :: 3", "Environment :: Console"]
16
- dependencies = ["httpx>=0.28.1,<1", "pydantic>=2.7.4,<3", "jsonschema>=4.23,<5"]
17
-
18
- [project.scripts]
19
- flashdata-youtube-transcript = "flashdata_youtube_transcript_cli.cli:main"
20
-
21
- [project.urls]
22
- Homepage = "https://flashdata.dev/docs/sources/youtube_transcript"
23
- Documentation = "https://flashdata.dev/docs/sources/youtube_transcript"
24
- Pricing = "https://flashdata.dev/pricing"
25
- Support = "https://flashdata.dev"
26
- Source = "https://github.com/rocosen/flashdata-mcp/tree/main/packages/python-cli"
27
-
28
- [tool.hatch.build.targets.wheel]
29
- packages = ["flashdata_youtube_transcript_cli"]
1
+ [build-system]
2
+ requires = ["hatchling>=1.27,<2"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "flashdata-youtube-transcript"
7
+ version = "0.2.0"
8
+ description = "Export timestamped automatic captions for retrieval with the FlashData JSON CLI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "FlashData", email = "support@flashdata.dev"}]
14
+ keywords = ["youtube-transcript", "cli", "json", "uvx", "flashdata"]
15
+ classifiers = ["Intended Audience :: Developers", "Programming Language :: Python :: 3", "Environment :: Console"]
16
+ dependencies = ["httpx>=0.28.1,<1", "pydantic>=2.7.4,<3", "jsonschema>=4.23,<5"]
17
+
18
+ [project.scripts]
19
+ flashdata-youtube-transcript = "flashdata_youtube_transcript_cli.cli:main"
20
+
21
+ [project.urls]
22
+ Homepage = "https://flashdata.dev/docs/sources/youtube_transcript"
23
+ Documentation = "https://flashdata.dev/docs/sources/youtube_transcript"
24
+ Pricing = "https://flashdata.dev/pricing"
25
+ Support = "https://flashdata.dev"
26
+ Source = "https://github.com/flashdatadev/flashdata-mcp/tree/main/packages/python-cli"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["flashdata_youtube_transcript_cli"]
@@ -1,95 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: flashdata-youtube-transcript
3
- Version: 0.1.1
4
- Summary: Retrieve existing YouTube transcripts as timestamped segments and full text in JSON; automatic captions by default.
5
- Project-URL: Homepage, https://flashdata.dev/docs/sources/youtube_transcript
6
- Project-URL: Documentation, https://flashdata.dev/docs/sources/youtube_transcript
7
- Project-URL: Pricing, https://flashdata.dev/pricing
8
- Project-URL: Support, https://flashdata.dev
9
- Project-URL: Source, https://github.com/rocosen/flashdata-mcp/tree/main/packages/python-cli
10
- Author-email: FlashData <support@flashdata.dev>
11
- License-Expression: MIT
12
- License-File: LICENSE
13
- Keywords: cli,flashdata,json,uvx,youtube-transcript
14
- Classifier: Environment :: Console
15
- Classifier: Intended Audience :: Developers
16
- Classifier: Programming Language :: Python :: 3
17
- Requires-Python: >=3.10
18
- Requires-Dist: httpx<1,>=0.28.1
19
- Requires-Dist: jsonschema<5,>=4.23
20
- Requires-Dist: pydantic<3,>=2.7.4
21
- Description-Content-Type: text/markdown
22
-
23
- # YouTube Transcript CLI
24
-
25
- Retrieve existing YouTube transcripts as timestamped segments and full text in JSON; automatic captions by default.
26
-
27
- Uses the [FlashData YouTube Transcript API](https://flashdata.dev/docs/sources/youtube_transcript).
28
-
29
- ## Installation
30
-
31
- Requires Python 3.10 or newer:
32
-
33
- ```sh
34
- python -m pip install flashdata-youtube-transcript==0.1.1
35
- ```
36
-
37
- Or run it without a persistent install using [uvx](https://docs.astral.sh/uv/guides/tools/),
38
- as in the examples below. The installed command is `flashdata-youtube-transcript`.
39
-
40
- ## Quick start
41
-
42
- Create a [FlashData API key](https://flashdata.dev/app/api-keys) with `youtube_transcript` access,
43
- then set `FLASHDATA_API_KEY` in your environment or secret manager. API calls use your
44
- FlashData Credits.
45
-
46
- ```sh
47
- export FLASHDATA_API_KEY='YOUR_API_KEY'
48
- uvx flashdata-youtube-transcript@0.1.1 --video-id dQw4w9WgXcQ --options-json '{"language": "en", "auto": true}' --output result.json
49
- ```
50
-
51
- Omit `--output` to write JSON to stdout. An output file must not already exist; the CLI
52
- checks this before making a request. API keys are read from the environment, not command flags.
53
-
54
- ## Inputs
55
-
56
- Use `--video-id` for the primary input. Pass additional fields through
57
- `--options-json`. Options are validated against this package's source schema.
58
-
59
- | Option | Meaning |
60
- | --- | --- |
61
- | `language` | Requested caption language code. Default: `"en"`. |
62
- | `auto` | True requests automatic captions; false requests manually authored captions. Default: `true`. |
63
-
64
- Inspect all fields, nested filters and defaults locally:
65
-
66
- ```sh
67
- uvx flashdata-youtube-transcript@0.1.1 --schema
68
- ```
69
-
70
- `--schema`, `--help` and `--version` do not require an API key or make an API call.
71
-
72
- ## Output and limits
73
-
74
- The JSON response preserves the API envelope and available fields: results[]: videoId, languageCode, isAutoGenerated, segments, fullText; usage.credits.
75
-
76
- Reads an existing track. No ASR, translation, or automatic fallback to uploader captions.
77
-
78
- ## Pricing and errors
79
-
80
- This MIT-licensed CLI is free to install. API calls use your own
81
- [FlashData Credits](https://flashdata.dev/pricing).
82
-
83
- Requests are not automatically retried. A timeout can occur after a request is accepted;
84
- check your FlashData query history before resubmitting. Each HTTP request has a 120-second
85
- network timeout, a 15-second connect timeout and a 20-MiB response limit.
86
-
87
- Errors go to stderr. Exit codes: `0` for exported output, `2` for local input/configuration
88
- errors, and `1` for request/output errors.
89
- A partial output file is retained on failure; check the request before retrying because of
90
- an output error. HTTP `401` indicates an API key issue, `402` insufficient Credits, and
91
- `429` a rate or quota limit.
92
-
93
- [API reference](https://flashdata.dev/docs/sources/youtube_transcript) ·
94
- [Privacy policy](https://flashdata.dev/legal/privacy) ·
95
- [Source code](https://github.com/rocosen/flashdata-mcp/tree/main/packages/python-cli)
@@ -1,73 +0,0 @@
1
- # YouTube Transcript CLI
2
-
3
- Retrieve existing YouTube transcripts as timestamped segments and full text in JSON; automatic captions by default.
4
-
5
- Uses the [FlashData YouTube Transcript API](https://flashdata.dev/docs/sources/youtube_transcript).
6
-
7
- ## Installation
8
-
9
- Requires Python 3.10 or newer:
10
-
11
- ```sh
12
- python -m pip install flashdata-youtube-transcript==0.1.1
13
- ```
14
-
15
- Or run it without a persistent install using [uvx](https://docs.astral.sh/uv/guides/tools/),
16
- as in the examples below. The installed command is `flashdata-youtube-transcript`.
17
-
18
- ## Quick start
19
-
20
- Create a [FlashData API key](https://flashdata.dev/app/api-keys) with `youtube_transcript` access,
21
- then set `FLASHDATA_API_KEY` in your environment or secret manager. API calls use your
22
- FlashData Credits.
23
-
24
- ```sh
25
- export FLASHDATA_API_KEY='YOUR_API_KEY'
26
- uvx flashdata-youtube-transcript@0.1.1 --video-id dQw4w9WgXcQ --options-json '{"language": "en", "auto": true}' --output result.json
27
- ```
28
-
29
- Omit `--output` to write JSON to stdout. An output file must not already exist; the CLI
30
- checks this before making a request. API keys are read from the environment, not command flags.
31
-
32
- ## Inputs
33
-
34
- Use `--video-id` for the primary input. Pass additional fields through
35
- `--options-json`. Options are validated against this package's source schema.
36
-
37
- | Option | Meaning |
38
- | --- | --- |
39
- | `language` | Requested caption language code. Default: `"en"`. |
40
- | `auto` | True requests automatic captions; false requests manually authored captions. Default: `true`. |
41
-
42
- Inspect all fields, nested filters and defaults locally:
43
-
44
- ```sh
45
- uvx flashdata-youtube-transcript@0.1.1 --schema
46
- ```
47
-
48
- `--schema`, `--help` and `--version` do not require an API key or make an API call.
49
-
50
- ## Output and limits
51
-
52
- The JSON response preserves the API envelope and available fields: results[]: videoId, languageCode, isAutoGenerated, segments, fullText; usage.credits.
53
-
54
- Reads an existing track. No ASR, translation, or automatic fallback to uploader captions.
55
-
56
- ## Pricing and errors
57
-
58
- This MIT-licensed CLI is free to install. API calls use your own
59
- [FlashData Credits](https://flashdata.dev/pricing).
60
-
61
- Requests are not automatically retried. A timeout can occur after a request is accepted;
62
- check your FlashData query history before resubmitting. Each HTTP request has a 120-second
63
- network timeout, a 15-second connect timeout and a 20-MiB response limit.
64
-
65
- Errors go to stderr. Exit codes: `0` for exported output, `2` for local input/configuration
66
- errors, and `1` for request/output errors.
67
- A partial output file is retained on failure; check the request before retrying because of
68
- an output error. HTTP `401` indicates an API key issue, `402` insufficient Credits, and
69
- `429` a rate or quota limit.
70
-
71
- [API reference](https://flashdata.dev/docs/sources/youtube_transcript) ·
72
- [Privacy policy](https://flashdata.dev/legal/privacy) ·
73
- [Source code](https://github.com/rocosen/flashdata-mcp/tree/main/packages/python-cli)
@@ -1,29 +0,0 @@
1
- {
2
- "additionalProperties": false,
3
- "description": "YouTube transcript parameters; no implicit language or caption fallback.",
4
- "properties": {
5
- "video_id": {
6
- "description": "11-character YouTube video ID or a YouTube video URL.",
7
- "title": "Video Id",
8
- "type": "string"
9
- },
10
- "language": {
11
- "default": "en",
12
- "description": "Requested caption language code.",
13
- "pattern": "^[A-Za-z0-9-]{1,35}$",
14
- "title": "Language",
15
- "type": "string"
16
- },
17
- "auto": {
18
- "default": true,
19
- "description": "True requests automatic captions; false requests manually authored captions.",
20
- "title": "Auto",
21
- "type": "boolean"
22
- }
23
- },
24
- "required": [
25
- "video_id"
26
- ],
27
- "title": "YouTubeTranscriptInput",
28
- "type": "object"
29
- }