getscript 0.12.0__tar.gz → 0.14.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.
- getscript-0.14.0/PKG-INFO +194 -0
- getscript-0.14.0/README.md +166 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript/__init__.py +1 -1
- {getscript-0.12.0 → getscript-0.14.0}/getscript/cli.py +62 -81
- {getscript-0.12.0 → getscript-0.14.0}/getscript/completions.py +10 -14
- {getscript-0.12.0 → getscript-0.14.0}/getscript/config.py +0 -6
- {getscript-0.12.0 → getscript-0.14.0}/getscript/detect.py +4 -23
- {getscript-0.12.0 → getscript-0.14.0}/getscript/search.py +1 -38
- {getscript-0.12.0 → getscript-0.14.0}/getscript/upload.py +1 -19
- getscript-0.14.0/getscript.egg-info/PKG-INFO +194 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript.egg-info/SOURCES.txt +1 -3
- getscript-0.14.0/getscript.egg-info/requires.txt +1 -0
- {getscript-0.12.0 → getscript-0.14.0}/pyproject.toml +3 -5
- {getscript-0.12.0 → getscript-0.14.0}/tests/test_detect.py +6 -32
- {getscript-0.12.0 → getscript-0.14.0}/tests/test_integration.py +68 -9
- {getscript-0.12.0 → getscript-0.14.0}/tests/test_output.py +9 -9
- getscript-0.14.0/tests/test_search.py +65 -0
- {getscript-0.12.0 → getscript-0.14.0}/tests/test_upload.py +16 -65
- getscript-0.12.0/PKG-INFO +0 -125
- getscript-0.12.0/README.md +0 -95
- getscript-0.12.0/getscript/youtube.py +0 -58
- getscript-0.12.0/getscript.egg-info/PKG-INFO +0 -125
- getscript-0.12.0/getscript.egg-info/requires.txt +0 -3
- getscript-0.12.0/tests/test_search.py +0 -125
- getscript-0.12.0/tests/test_youtube.py +0 -109
- {getscript-0.12.0 → getscript-0.14.0}/LICENSE +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript/apple.py +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript/output.py +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript/picker.py +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript/progress.py +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript.egg-info/dependency_links.txt +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript.egg-info/entry_points.txt +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/getscript.egg-info/top_level.txt +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/setup.cfg +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/tests/test_apple.py +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/tests/test_config.py +0 -0
- {getscript-0.12.0 → getscript-0.14.0}/tests/test_picker.py +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: getscript
|
|
3
|
+
Version: 0.14.0
|
|
4
|
+
Summary: Fast, Unix-friendly CLI for fetching transcripts from Apple Podcasts
|
|
5
|
+
Author: Voxly
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/outerbanks73/cli-tools
|
|
8
|
+
Project-URL: Documentation, https://voxlytranscribes.com/docs/getscript
|
|
9
|
+
Project-URL: Repository, https://github.com/outerbanks73/cli-tools
|
|
10
|
+
Project-URL: Issues, https://github.com/outerbanks73/cli-tools/issues
|
|
11
|
+
Keywords: transcript,podcast,apple-podcasts,cli
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: defusedxml>=0.7.1
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# getscript
|
|
30
|
+
|
|
31
|
+
A fast, Unix-friendly CLI for fetching transcripts from Apple Podcasts. You don't need to transcribe much of anything nowadays
|
|
32
|
+
because Apple is transcribing everything for us. 'getscript' lets you use the transcripts in a more human / text friendly manner.
|
|
33
|
+
The idea behind getscript is to put the transcripts to use in a meaningful way.
|
|
34
|
+
|
|
35
|
+
For example - let's say you're curious about AI Slop as a podcast topic:
|
|
36
|
+
|
|
37
|
+
$ getscript --search "AI Slop" --list
|
|
38
|
+
<removing list of 10 podcasts and their podcast id's>
|
|
39
|
+
$ getscript 1000730374732 | claude -p "Summarize the top 5 Points"
|
|
40
|
+
|
|
41
|
+
Of course you can also export to TTML, JSON, markdown and run from cron if you want to schedule things.
|
|
42
|
+
|
|
43
|
+
[](https://pypi.org/project/getscript/)
|
|
44
|
+
[](https://github.com/outerbanks73/cli-tools/actions/workflows/test.yml)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
[](https://www.python.org)
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install getscript
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or via Homebrew:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
brew install outerbanks73/tap/getscript
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Requires Python 3.10+. Apple Podcasts transcripts require macOS 15.5+ with Xcode CLI tools.
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Fetch from an episode URL
|
|
66
|
+
getscript "https://podcasts.apple.com/us/podcast/the-daily/id1200361736?i=1000753754819"
|
|
67
|
+
|
|
68
|
+
# Fetch from a bare episode ID
|
|
69
|
+
getscript 1000753754819
|
|
70
|
+
|
|
71
|
+
# Raw TTML XML output
|
|
72
|
+
getscript 1000753754819 --ttml
|
|
73
|
+
|
|
74
|
+
# Search Apple Podcasts interactively (requires fzf)
|
|
75
|
+
getscript --search "artificial intelligence"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Output Formats
|
|
79
|
+
|
|
80
|
+
Output formats are mutually exclusive (`--json`, `--ttml`, `--markdown`). The `--timestamps` flag can be combined with any format.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# JSON piped to jq
|
|
84
|
+
getscript EPISODE_ID --json | jq '.segments[].text'
|
|
85
|
+
|
|
86
|
+
# Markdown with timestamps
|
|
87
|
+
getscript EPISODE_ID --markdown --timestamps > notes.md
|
|
88
|
+
|
|
89
|
+
# Write to file
|
|
90
|
+
getscript EPISODE_ID -o transcript.txt
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Search
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Search Apple Podcasts (requires fzf)
|
|
97
|
+
getscript --search "climate change"
|
|
98
|
+
|
|
99
|
+
# List results without fzf
|
|
100
|
+
getscript --search "topic" --list --limit 20
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Stdin & Batch Processing
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Read episode ID from stdin
|
|
107
|
+
echo "1000753754819" | getscript -
|
|
108
|
+
|
|
109
|
+
# Batch process a list of IDs
|
|
110
|
+
cat ids.txt | xargs -n1 getscript --no-upload --quiet
|
|
111
|
+
|
|
112
|
+
# Disable upload via environment variable
|
|
113
|
+
GETSCRIPT_UPLOAD=0 getscript EPISODE_ID
|
|
114
|
+
|
|
115
|
+
# Silent file output for scripting
|
|
116
|
+
getscript EPISODE_ID --quiet --no-upload -o out.txt
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Shared Transcript Library
|
|
120
|
+
|
|
121
|
+
Fetched transcripts are automatically submitted to [voxlytranscribes.com](https://voxlytranscribes.com) for indexing and enrichment. To disable:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
getscript EPISODE_ID --no-upload
|
|
125
|
+
GETSCRIPT_UPLOAD=0 getscript EPISODE_ID
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Shell Completions
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
getscript --completions bash >> ~/.bashrc
|
|
132
|
+
getscript --completions zsh >> ~/.zshrc
|
|
133
|
+
getscript --completions fish > ~/.config/fish/completions/getscript.fish
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
Config file: `~/.config/getscript/config.json`
|
|
139
|
+
|
|
140
|
+
| Key | Type | Default | Description |
|
|
141
|
+
|-----|------|---------|-------------|
|
|
142
|
+
| `output_format` | string | `text` | Default format: `text`, `json`, `markdown`, `ttml` |
|
|
143
|
+
| `timestamps` | bool | `false` | Include timestamps in text output |
|
|
144
|
+
| `search_limit` | int | `10` | Number of search results |
|
|
145
|
+
| `no_upload` | bool | `false` | Disable shared library submissions |
|
|
146
|
+
| `quiet` | bool | `false` | Suppress progress and upload status messages |
|
|
147
|
+
|
|
148
|
+
Environment variables:
|
|
149
|
+
|
|
150
|
+
| Variable | Description |
|
|
151
|
+
|----------|-------------|
|
|
152
|
+
| `GETSCRIPT_UPLOAD` | Set to `0` to disable submissions |
|
|
153
|
+
| `NO_COLOR` | Disable colored output |
|
|
154
|
+
|
|
155
|
+
Precedence: config file < environment variables < CLI flags.
|
|
156
|
+
|
|
157
|
+
See [examples/config.example.md](examples/config.example.md) for a full annotated reference.
|
|
158
|
+
|
|
159
|
+
## How It Works
|
|
160
|
+
|
|
161
|
+
Compiles a small Obj-C helper that calls Apple's private AMSMescal framework (FairPlay DRM) to generate a bearer token. That token authenticates requests to the AMP API, which returns TTML transcripts. The token is cached at `~/.cache/getscript/apple_token` for 30 days. This is the only open-source tool that can fetch Apple Podcasts transcripts programmatically.
|
|
162
|
+
|
|
163
|
+
## Shared Transcript Library
|
|
164
|
+
|
|
165
|
+
Every fetch contributes to the shared library at [voxlytranscribes.com](https://voxlytranscribes.com). Submissions go through a quarantine pipeline: server-side re-fetch, content hash verification, and provenance tracking before promotion to the canonical library. See the [technical docs](https://voxlytranscribes.com/docs/getscript) for details.
|
|
166
|
+
|
|
167
|
+
## Exit Codes
|
|
168
|
+
|
|
169
|
+
| Code | Meaning |
|
|
170
|
+
|------|---------|
|
|
171
|
+
| `0` | Success |
|
|
172
|
+
| `1` | Runtime error (network, auth, missing transcript) |
|
|
173
|
+
| `2` | Usage error (bad arguments, unrecognized URL) |
|
|
174
|
+
| `130` | Interrupted (Ctrl-C) |
|
|
175
|
+
|
|
176
|
+
## Testing
|
|
177
|
+
|
|
178
|
+
87 tests across 9 modules. CI matrix: Python 3.10–3.13 on Ubuntu and macOS.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pytest -v
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
187
|
+
|
|
188
|
+
## Changelog
|
|
189
|
+
|
|
190
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# getscript
|
|
2
|
+
|
|
3
|
+
A fast, Unix-friendly CLI for fetching transcripts from Apple Podcasts. You don't need to transcribe much of anything nowadays
|
|
4
|
+
because Apple is transcribing everything for us. 'getscript' lets you use the transcripts in a more human / text friendly manner.
|
|
5
|
+
The idea behind getscript is to put the transcripts to use in a meaningful way.
|
|
6
|
+
|
|
7
|
+
For example - let's say you're curious about AI Slop as a podcast topic:
|
|
8
|
+
|
|
9
|
+
$ getscript --search "AI Slop" --list
|
|
10
|
+
<removing list of 10 podcasts and their podcast id's>
|
|
11
|
+
$ getscript 1000730374732 | claude -p "Summarize the top 5 Points"
|
|
12
|
+
|
|
13
|
+
Of course you can also export to TTML, JSON, markdown and run from cron if you want to schedule things.
|
|
14
|
+
|
|
15
|
+
[](https://pypi.org/project/getscript/)
|
|
16
|
+
[](https://github.com/outerbanks73/cli-tools/actions/workflows/test.yml)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
[](https://www.python.org)
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install getscript
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or via Homebrew:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
brew install outerbanks73/tap/getscript
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires Python 3.10+. Apple Podcasts transcripts require macOS 15.5+ with Xcode CLI tools.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Fetch from an episode URL
|
|
38
|
+
getscript "https://podcasts.apple.com/us/podcast/the-daily/id1200361736?i=1000753754819"
|
|
39
|
+
|
|
40
|
+
# Fetch from a bare episode ID
|
|
41
|
+
getscript 1000753754819
|
|
42
|
+
|
|
43
|
+
# Raw TTML XML output
|
|
44
|
+
getscript 1000753754819 --ttml
|
|
45
|
+
|
|
46
|
+
# Search Apple Podcasts interactively (requires fzf)
|
|
47
|
+
getscript --search "artificial intelligence"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Output Formats
|
|
51
|
+
|
|
52
|
+
Output formats are mutually exclusive (`--json`, `--ttml`, `--markdown`). The `--timestamps` flag can be combined with any format.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# JSON piped to jq
|
|
56
|
+
getscript EPISODE_ID --json | jq '.segments[].text'
|
|
57
|
+
|
|
58
|
+
# Markdown with timestamps
|
|
59
|
+
getscript EPISODE_ID --markdown --timestamps > notes.md
|
|
60
|
+
|
|
61
|
+
# Write to file
|
|
62
|
+
getscript EPISODE_ID -o transcript.txt
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Search
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Search Apple Podcasts (requires fzf)
|
|
69
|
+
getscript --search "climate change"
|
|
70
|
+
|
|
71
|
+
# List results without fzf
|
|
72
|
+
getscript --search "topic" --list --limit 20
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Stdin & Batch Processing
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Read episode ID from stdin
|
|
79
|
+
echo "1000753754819" | getscript -
|
|
80
|
+
|
|
81
|
+
# Batch process a list of IDs
|
|
82
|
+
cat ids.txt | xargs -n1 getscript --no-upload --quiet
|
|
83
|
+
|
|
84
|
+
# Disable upload via environment variable
|
|
85
|
+
GETSCRIPT_UPLOAD=0 getscript EPISODE_ID
|
|
86
|
+
|
|
87
|
+
# Silent file output for scripting
|
|
88
|
+
getscript EPISODE_ID --quiet --no-upload -o out.txt
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Shared Transcript Library
|
|
92
|
+
|
|
93
|
+
Fetched transcripts are automatically submitted to [voxlytranscribes.com](https://voxlytranscribes.com) for indexing and enrichment. To disable:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
getscript EPISODE_ID --no-upload
|
|
97
|
+
GETSCRIPT_UPLOAD=0 getscript EPISODE_ID
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Shell Completions
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
getscript --completions bash >> ~/.bashrc
|
|
104
|
+
getscript --completions zsh >> ~/.zshrc
|
|
105
|
+
getscript --completions fish > ~/.config/fish/completions/getscript.fish
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Configuration
|
|
109
|
+
|
|
110
|
+
Config file: `~/.config/getscript/config.json`
|
|
111
|
+
|
|
112
|
+
| Key | Type | Default | Description |
|
|
113
|
+
|-----|------|---------|-------------|
|
|
114
|
+
| `output_format` | string | `text` | Default format: `text`, `json`, `markdown`, `ttml` |
|
|
115
|
+
| `timestamps` | bool | `false` | Include timestamps in text output |
|
|
116
|
+
| `search_limit` | int | `10` | Number of search results |
|
|
117
|
+
| `no_upload` | bool | `false` | Disable shared library submissions |
|
|
118
|
+
| `quiet` | bool | `false` | Suppress progress and upload status messages |
|
|
119
|
+
|
|
120
|
+
Environment variables:
|
|
121
|
+
|
|
122
|
+
| Variable | Description |
|
|
123
|
+
|----------|-------------|
|
|
124
|
+
| `GETSCRIPT_UPLOAD` | Set to `0` to disable submissions |
|
|
125
|
+
| `NO_COLOR` | Disable colored output |
|
|
126
|
+
|
|
127
|
+
Precedence: config file < environment variables < CLI flags.
|
|
128
|
+
|
|
129
|
+
See [examples/config.example.md](examples/config.example.md) for a full annotated reference.
|
|
130
|
+
|
|
131
|
+
## How It Works
|
|
132
|
+
|
|
133
|
+
Compiles a small Obj-C helper that calls Apple's private AMSMescal framework (FairPlay DRM) to generate a bearer token. That token authenticates requests to the AMP API, which returns TTML transcripts. The token is cached at `~/.cache/getscript/apple_token` for 30 days. This is the only open-source tool that can fetch Apple Podcasts transcripts programmatically.
|
|
134
|
+
|
|
135
|
+
## Shared Transcript Library
|
|
136
|
+
|
|
137
|
+
Every fetch contributes to the shared library at [voxlytranscribes.com](https://voxlytranscribes.com). Submissions go through a quarantine pipeline: server-side re-fetch, content hash verification, and provenance tracking before promotion to the canonical library. See the [technical docs](https://voxlytranscribes.com/docs/getscript) for details.
|
|
138
|
+
|
|
139
|
+
## Exit Codes
|
|
140
|
+
|
|
141
|
+
| Code | Meaning |
|
|
142
|
+
|------|---------|
|
|
143
|
+
| `0` | Success |
|
|
144
|
+
| `1` | Runtime error (network, auth, missing transcript) |
|
|
145
|
+
| `2` | Usage error (bad arguments, unrecognized URL) |
|
|
146
|
+
| `130` | Interrupted (Ctrl-C) |
|
|
147
|
+
|
|
148
|
+
## Testing
|
|
149
|
+
|
|
150
|
+
87 tests across 9 modules. CI matrix: Python 3.10–3.13 on Ubuntu and macOS.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pytest -v
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Contributing
|
|
157
|
+
|
|
158
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
159
|
+
|
|
160
|
+
## Changelog
|
|
161
|
+
|
|
162
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -3,4 +3,4 @@ from importlib.metadata import version, PackageNotFoundError
|
|
|
3
3
|
try:
|
|
4
4
|
__version__ = version("getscript")
|
|
5
5
|
except PackageNotFoundError:
|
|
6
|
-
__version__ = "0.
|
|
6
|
+
__version__ = "0.14.0" # fallback for editable installs without metadata
|
|
@@ -12,41 +12,45 @@ from getscript.progress import Progress
|
|
|
12
12
|
|
|
13
13
|
EXAMPLES = """\
|
|
14
14
|
examples:
|
|
15
|
-
getscript "https://youtube.com/watch?v=dQw4w9WgXcQ"
|
|
16
|
-
getscript "https://youtu.be/dQw4w9WgXcQ" --timestamps
|
|
17
|
-
getscript "https://youtube.com/watch?v=dQw4w9WgXcQ" --json | jq .
|
|
18
|
-
getscript "https://youtube.com/watch?v=dQw4w9WgXcQ" --markdown > notes.md
|
|
19
15
|
getscript 1000753754819 # Apple episode ID
|
|
20
16
|
getscript 1000753754819 --ttml # raw TTML XML
|
|
17
|
+
getscript 1000753754819 --timestamps # include timestamps
|
|
21
18
|
getscript "https://podcasts.apple.com/...?i=12345"
|
|
22
|
-
getscript
|
|
23
|
-
getscript --
|
|
24
|
-
getscript
|
|
19
|
+
getscript EPISODE_ID --json | jq . # JSON piped to jq
|
|
20
|
+
getscript EPISODE_ID --markdown > notes.md
|
|
21
|
+
getscript EPISODE_ID -o transcript.txt
|
|
22
|
+
getscript EPISODE_ID --no-upload # skip shared library indexing
|
|
23
|
+
getscript --search "artificial intelligence" # search Apple Podcasts, pick via fzf
|
|
25
24
|
getscript --search "topic" --list # print results, no fzf
|
|
26
25
|
getscript --search "topic" --limit 20 # control result count
|
|
27
|
-
getscript
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
getscript
|
|
26
|
+
getscript --completions zsh >> ~/.zshrc
|
|
27
|
+
|
|
28
|
+
batch & scripting:
|
|
29
|
+
echo "1000753754819" | getscript - # read ID from stdin
|
|
30
|
+
cat ids.txt | xargs -n1 getscript --no-upload --quiet # batch process, no noise
|
|
31
|
+
GETSCRIPT_UPLOAD=0 getscript EPISODE_ID # env var to skip upload
|
|
32
|
+
getscript EPISODE_ID --quiet --no-upload -o out.txt # silent file output
|
|
33
|
+
|
|
34
|
+
exit codes:
|
|
35
|
+
0 success
|
|
36
|
+
1 runtime error (network, auth, missing transcript)
|
|
37
|
+
2 usage error (bad arguments, unrecognized URL)
|
|
38
|
+
130 interrupted (Ctrl-C)"""
|
|
31
39
|
|
|
32
40
|
|
|
33
41
|
def build_parser() -> argparse.ArgumentParser:
|
|
34
42
|
parser = argparse.ArgumentParser(
|
|
35
43
|
prog="getscript",
|
|
36
|
-
description="Fetch transcripts from
|
|
44
|
+
description="Fetch transcripts from Apple Podcasts.",
|
|
37
45
|
epilog=EXAMPLES,
|
|
38
46
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
39
47
|
)
|
|
40
|
-
parser.add_argument("input", nargs="?", help="URL or ID
|
|
48
|
+
parser.add_argument("input", nargs="?", help="Apple Podcasts URL or episode ID")
|
|
41
49
|
parser.add_argument(
|
|
42
50
|
"-o", "--output", metavar="FILE", help="write output to file"
|
|
43
51
|
)
|
|
44
52
|
parser.add_argument(
|
|
45
|
-
"--search", metavar="QUERY", help="search
|
|
46
|
-
)
|
|
47
|
-
parser.add_argument(
|
|
48
|
-
"--apple", action="store_true", default=False,
|
|
49
|
-
help="search Apple Podcasts instead of YouTube",
|
|
53
|
+
"--search", metavar="QUERY", help="search Apple Podcasts by topic or creator"
|
|
50
54
|
)
|
|
51
55
|
parser.add_argument(
|
|
52
56
|
"--limit", type=int, default=None, metavar="N",
|
|
@@ -56,25 +60,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
56
60
|
"--list", action="store_true", default=False,
|
|
57
61
|
help="print search results without interactive selection",
|
|
58
62
|
)
|
|
59
|
-
parser.
|
|
63
|
+
fmt_group = parser.add_mutually_exclusive_group()
|
|
64
|
+
fmt_group.add_argument(
|
|
60
65
|
"--json", action="store_true", default=None, help="structured JSON output"
|
|
61
66
|
)
|
|
62
|
-
|
|
63
|
-
"--ttml", action="store_true", default=None, help="raw TTML XML
|
|
67
|
+
fmt_group.add_argument(
|
|
68
|
+
"--ttml", action="store_true", default=None, help="raw TTML XML output"
|
|
64
69
|
)
|
|
65
|
-
|
|
66
|
-
"--timestamps", action="store_true", default=None, help="include timestamps"
|
|
67
|
-
)
|
|
68
|
-
parser.add_argument(
|
|
70
|
+
fmt_group.add_argument(
|
|
69
71
|
"--markdown", action="store_true", default=None, help="Markdown output"
|
|
70
72
|
)
|
|
71
73
|
parser.add_argument(
|
|
72
|
-
"--
|
|
73
|
-
help="proxy URL for YouTube requests (e.g. socks5://host:port)",
|
|
74
|
-
)
|
|
75
|
-
parser.add_argument(
|
|
76
|
-
"--cookies", metavar="FILE", default=None,
|
|
77
|
-
help="Netscape cookie file for YouTube auth (e.g. cookies.txt)",
|
|
74
|
+
"--timestamps", action="store_true", default=None, help="include timestamps"
|
|
78
75
|
)
|
|
79
76
|
parser.add_argument(
|
|
80
77
|
"--no-upload", action="store_true", default=None,
|
|
@@ -84,7 +81,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
84
81
|
"--no-color", action="store_true", default=None, help="disable colors"
|
|
85
82
|
)
|
|
86
83
|
parser.add_argument(
|
|
87
|
-
"--quiet", action="store_true", default=None, help="suppress progress
|
|
84
|
+
"--quiet", action="store_true", default=None, help="suppress progress and upload status messages"
|
|
88
85
|
)
|
|
89
86
|
parser.add_argument(
|
|
90
87
|
"--verbose", action="store_true", default=None, help="show detailed errors"
|
|
@@ -104,14 +101,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
104
101
|
def _handle_search(args, config) -> int:
|
|
105
102
|
"""Handle --search mode: search, select, then fetch transcript."""
|
|
106
103
|
from getscript.picker import format_list, pick_result
|
|
107
|
-
from getscript.search import search_apple
|
|
104
|
+
from getscript.search import search_apple
|
|
108
105
|
|
|
109
106
|
verbose = config.get("verbose", False)
|
|
110
107
|
quiet = config.get("quiet", False)
|
|
111
108
|
limit = args.limit or config.get("search_limit", 10)
|
|
112
109
|
|
|
113
110
|
# Apple transcript fetch requires macOS — warn before searching unless --list
|
|
114
|
-
if
|
|
111
|
+
if not args.list:
|
|
115
112
|
if sys.platform != "darwin":
|
|
116
113
|
print(
|
|
117
114
|
"Apple Podcasts transcripts require macOS 15.5+ with Xcode CLI tools.\n"
|
|
@@ -123,22 +120,8 @@ def _handle_search(args, config) -> int:
|
|
|
123
120
|
progress = Progress(quiet=quiet)
|
|
124
121
|
|
|
125
122
|
try:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
results = search_apple(args.search, limit=limit)
|
|
129
|
-
else:
|
|
130
|
-
api_key = config.get("youtube_api_key")
|
|
131
|
-
if not api_key:
|
|
132
|
-
print(
|
|
133
|
-
"YouTube API key required for --search.\n"
|
|
134
|
-
"Set GETSCRIPT_YOUTUBE_API_KEY env var or add "
|
|
135
|
-
'"youtube_api_key" to ~/.config/getscript/config.json\n'
|
|
136
|
-
"Get a key: https://console.cloud.google.com/apis/credentials",
|
|
137
|
-
file=sys.stderr,
|
|
138
|
-
)
|
|
139
|
-
return 1
|
|
140
|
-
progress.update("Searching YouTube...")
|
|
141
|
-
results = search_youtube(args.search, api_key, limit=limit)
|
|
123
|
+
progress.update("Searching Apple Podcasts...")
|
|
124
|
+
results = search_apple(args.search, limit=limit)
|
|
142
125
|
|
|
143
126
|
progress.done()
|
|
144
127
|
|
|
@@ -166,7 +149,7 @@ def _handle_search(args, config) -> int:
|
|
|
166
149
|
except KeyboardInterrupt:
|
|
167
150
|
progress.done()
|
|
168
151
|
print("\nInterrupted.", file=sys.stderr)
|
|
169
|
-
return
|
|
152
|
+
return 130
|
|
170
153
|
except Exception as e:
|
|
171
154
|
progress.done()
|
|
172
155
|
if verbose:
|
|
@@ -207,34 +190,24 @@ def _fetch_transcript(args, config) -> int:
|
|
|
207
190
|
progress.update("Detecting source...")
|
|
208
191
|
source, source_id = detect_source(args.input)
|
|
209
192
|
|
|
210
|
-
|
|
193
|
+
from getscript.apple import fetch_ttml, get_bearer_token, ttml_to_segments
|
|
211
194
|
|
|
212
|
-
|
|
213
|
-
progress.update("Fetching YouTube transcript...")
|
|
214
|
-
from getscript.youtube import fetch_transcript
|
|
195
|
+
cache_dir = get_cache_dir()
|
|
215
196
|
|
|
216
|
-
|
|
197
|
+
progress.update("Authenticating with Apple...")
|
|
198
|
+
token = get_bearer_token(cache_dir)
|
|
199
|
+
if not token:
|
|
217
200
|
progress.done()
|
|
201
|
+
print(
|
|
202
|
+
"Failed to get Apple bearer token. Requires macOS 15.5+.",
|
|
203
|
+
file=sys.stderr,
|
|
204
|
+
)
|
|
205
|
+
return 1
|
|
218
206
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
progress.update("Authenticating with Apple...")
|
|
225
|
-
token = get_bearer_token(cache_dir)
|
|
226
|
-
if not token:
|
|
227
|
-
progress.done()
|
|
228
|
-
print(
|
|
229
|
-
"Failed to get Apple bearer token. Requires macOS 15.5+.",
|
|
230
|
-
file=sys.stderr,
|
|
231
|
-
)
|
|
232
|
-
return 1
|
|
233
|
-
|
|
234
|
-
progress.update("Fetching Apple Podcasts transcript...")
|
|
235
|
-
ttml_raw = fetch_ttml(source_id, token)
|
|
236
|
-
segments = ttml_to_segments(ttml_raw)
|
|
237
|
-
progress.done()
|
|
207
|
+
progress.update("Fetching Apple Podcasts transcript...")
|
|
208
|
+
ttml_raw = fetch_ttml(source_id, token)
|
|
209
|
+
segments = ttml_to_segments(ttml_raw)
|
|
210
|
+
progress.done()
|
|
238
211
|
|
|
239
212
|
# Format output
|
|
240
213
|
result = format_output(
|
|
@@ -256,11 +229,9 @@ def _fetch_transcript(args, config) -> int:
|
|
|
256
229
|
|
|
257
230
|
# Upload to shared library (on by default, disable with --no-upload)
|
|
258
231
|
if not config.get("no_upload"):
|
|
259
|
-
from getscript.upload import
|
|
232
|
+
from getscript.upload import upload_transcript
|
|
260
233
|
|
|
261
234
|
title = getattr(args, "_title", None)
|
|
262
|
-
if not title:
|
|
263
|
-
title = fetch_title(source, source_id)
|
|
264
235
|
resp = upload_transcript(source, source_id, segments, title, config)
|
|
265
236
|
if resp and not quiet:
|
|
266
237
|
status = resp.get("status", "unknown")
|
|
@@ -284,7 +255,7 @@ def _fetch_transcript(args, config) -> int:
|
|
|
284
255
|
except KeyboardInterrupt:
|
|
285
256
|
progress.done()
|
|
286
257
|
print("\nInterrupted.", file=sys.stderr)
|
|
287
|
-
return
|
|
258
|
+
return 130
|
|
288
259
|
except Exception as e:
|
|
289
260
|
progress.done()
|
|
290
261
|
if verbose:
|
|
@@ -305,6 +276,18 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
305
276
|
print(generate_completions(args.completions))
|
|
306
277
|
return 0
|
|
307
278
|
|
|
279
|
+
# Read from stdin if "-" is given
|
|
280
|
+
if args.input == "-":
|
|
281
|
+
if sys.stdin.isatty():
|
|
282
|
+
print("Error: stdin is a terminal. Pipe a URL/ID or use a positional argument.",
|
|
283
|
+
file=sys.stderr)
|
|
284
|
+
return 2
|
|
285
|
+
line = sys.stdin.readline().strip()
|
|
286
|
+
if not line:
|
|
287
|
+
print("Error: no input received on stdin.", file=sys.stderr)
|
|
288
|
+
return 2
|
|
289
|
+
args.input = line
|
|
290
|
+
|
|
308
291
|
# No input provided and no search
|
|
309
292
|
if not args.input and not args.search:
|
|
310
293
|
parser.print_help(sys.stderr)
|
|
@@ -326,8 +309,6 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
326
309
|
"no_color": args.no_color,
|
|
327
310
|
"quiet": args.quiet,
|
|
328
311
|
"verbose": args.verbose,
|
|
329
|
-
"proxy": args.proxy,
|
|
330
|
-
"cookie_file": args.cookies,
|
|
331
312
|
"no_upload": args.no_upload,
|
|
332
313
|
}
|
|
333
314
|
config = merge_config(file_config, cli_flags)
|
|
@@ -20,7 +20,7 @@ _getscript() {
|
|
|
20
20
|
local cur opts
|
|
21
21
|
COMPREPLY=()
|
|
22
22
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
23
|
-
opts="--search --
|
|
23
|
+
opts="--search --limit --list --json --ttml --timestamps --markdown --no-upload --no-color --quiet --verbose -o --output --completions -h --help --version"
|
|
24
24
|
|
|
25
25
|
if [[ ${cur} == -* ]]; then
|
|
26
26
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
@@ -36,19 +36,17 @@ def _zsh() -> str:
|
|
|
36
36
|
|
|
37
37
|
_getscript() {
|
|
38
38
|
_arguments \\
|
|
39
|
-
'1:
|
|
40
|
-
'--search[Search
|
|
41
|
-
'--apple[Search Apple Podcasts instead of YouTube]' \\
|
|
39
|
+
'1:Apple Podcasts URL or episode ID:' \\
|
|
40
|
+
'--search[Search Apple Podcasts by topic or creator]:query:' \\
|
|
42
41
|
'--limit[Number of search results]:count:' \\
|
|
43
42
|
'--list[Print search results without interactive selection]' \\
|
|
44
43
|
'--json[Output as JSON]' \\
|
|
45
|
-
'--ttml[Output raw TTML XML
|
|
44
|
+
'--ttml[Output raw TTML XML]' \\
|
|
46
45
|
'--timestamps[Include timestamps]' \\
|
|
47
46
|
'--markdown[Output as Markdown]' \\
|
|
48
|
-
'--
|
|
49
|
-
'--cookies[Netscape cookie file for YouTube auth]:cookie file:_files' \\
|
|
47
|
+
'--no-upload[Disable shared library submission]' \\
|
|
50
48
|
'--no-color[Disable colors]' \\
|
|
51
|
-
'--quiet[Suppress progress
|
|
49
|
+
'--quiet[Suppress progress and upload status messages]' \\
|
|
52
50
|
'--verbose[Show detailed errors]' \\
|
|
53
51
|
{-o,--output}'[Write to file]:output file:_files' \\
|
|
54
52
|
'--completions[Generate shell completions]:shell:(bash zsh fish)' \\
|
|
@@ -62,18 +60,16 @@ _getscript "$@"
|
|
|
62
60
|
|
|
63
61
|
def _fish() -> str:
|
|
64
62
|
return """\
|
|
65
|
-
complete -c getscript -l search -d 'Search
|
|
66
|
-
complete -c getscript -l apple -d 'Search Apple Podcasts instead of YouTube'
|
|
63
|
+
complete -c getscript -l search -d 'Search Apple Podcasts by topic or creator' -r
|
|
67
64
|
complete -c getscript -l limit -d 'Number of search results' -r
|
|
68
65
|
complete -c getscript -l list -d 'Print search results without interactive selection'
|
|
69
66
|
complete -c getscript -l json -d 'Output as JSON'
|
|
70
|
-
complete -c getscript -l ttml -d 'Output raw TTML XML
|
|
67
|
+
complete -c getscript -l ttml -d 'Output raw TTML XML'
|
|
71
68
|
complete -c getscript -l timestamps -d 'Include timestamps'
|
|
72
69
|
complete -c getscript -l markdown -d 'Output as Markdown'
|
|
73
|
-
complete -c getscript -l
|
|
74
|
-
complete -c getscript -l cookies -d 'Netscape cookie file for YouTube auth' -r -F
|
|
70
|
+
complete -c getscript -l no-upload -d 'Disable shared library submission'
|
|
75
71
|
complete -c getscript -l no-color -d 'Disable colors'
|
|
76
|
-
complete -c getscript -l quiet -d 'Suppress progress
|
|
72
|
+
complete -c getscript -l quiet -d 'Suppress progress and upload status messages'
|
|
77
73
|
complete -c getscript -l verbose -d 'Show detailed errors'
|
|
78
74
|
complete -c getscript -s o -l output -d 'Write to file' -r -F
|
|
79
75
|
complete -c getscript -l completions -d 'Generate shell completions' -r -fa 'bash zsh fish'
|