ember-dl 0.9.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 (48) hide show
  1. ember_dl-0.9.0/LICENSE +21 -0
  2. ember_dl-0.9.0/PKG-INFO +291 -0
  3. ember_dl-0.9.0/README.md +247 -0
  4. ember_dl-0.9.0/ember/__init__.py +66 -0
  5. ember_dl-0.9.0/ember/__main__.py +497 -0
  6. ember_dl-0.9.0/ember/_browser_cookies.py +611 -0
  7. ember_dl-0.9.0/ember/cache.py +79 -0
  8. ember_dl-0.9.0/ember/cookies.py +179 -0
  9. ember_dl-0.9.0/ember/download.py +549 -0
  10. ember_dl-0.9.0/ember/errors.py +29 -0
  11. ember_dl-0.9.0/ember/hls.py +149 -0
  12. ember_dl-0.9.0/ember/http.py +121 -0
  13. ember_dl-0.9.0/ember/models.py +148 -0
  14. ember_dl-0.9.0/ember/py.typed +0 -0
  15. ember_dl-0.9.0/ember/router.py +233 -0
  16. ember_dl-0.9.0/ember/services/__init__.py +0 -0
  17. ember_dl-0.9.0/ember/services/bluesky.py +79 -0
  18. ember_dl-0.9.0/ember/services/facebook.py +74 -0
  19. ember_dl-0.9.0/ember/services/instagram.py +434 -0
  20. ember_dl-0.9.0/ember/services/newgrounds.py +76 -0
  21. ember_dl-0.9.0/ember/services/ok.py +78 -0
  22. ember_dl-0.9.0/ember/services/pinterest.py +110 -0
  23. ember_dl-0.9.0/ember/services/pornhub.py +72 -0
  24. ember_dl-0.9.0/ember/services/reddit.py +136 -0
  25. ember_dl-0.9.0/ember/services/redgifs.py +95 -0
  26. ember_dl-0.9.0/ember/services/rutube.py +90 -0
  27. ember_dl-0.9.0/ember/services/soundcloud.py +169 -0
  28. ember_dl-0.9.0/ember/services/tiktok.py +129 -0
  29. ember_dl-0.9.0/ember/services/tumblr.py +147 -0
  30. ember_dl-0.9.0/ember/services/twitch.py +110 -0
  31. ember_dl-0.9.0/ember/services/twitter.py +319 -0
  32. ember_dl-0.9.0/ember/services/vimeo.py +130 -0
  33. ember_dl-0.9.0/ember/services/vk.py +264 -0
  34. ember_dl-0.9.0/ember/services/xvideos.py +88 -0
  35. ember_dl-0.9.0/ember_dl.egg-info/PKG-INFO +291 -0
  36. ember_dl-0.9.0/ember_dl.egg-info/SOURCES.txt +46 -0
  37. ember_dl-0.9.0/ember_dl.egg-info/dependency_links.txt +1 -0
  38. ember_dl-0.9.0/ember_dl.egg-info/entry_points.txt +2 -0
  39. ember_dl-0.9.0/ember_dl.egg-info/requires.txt +7 -0
  40. ember_dl-0.9.0/ember_dl.egg-info/top_level.txt +1 -0
  41. ember_dl-0.9.0/pyproject.toml +39 -0
  42. ember_dl-0.9.0/setup.cfg +4 -0
  43. ember_dl-0.9.0/tests/test_cache.py +29 -0
  44. ember_dl-0.9.0/tests/test_cli.py +16 -0
  45. ember_dl-0.9.0/tests/test_crypto.py +51 -0
  46. ember_dl-0.9.0/tests/test_hls.py +104 -0
  47. ember_dl-0.9.0/tests/test_models.py +28 -0
  48. ember_dl-0.9.0/tests/test_routing.py +57 -0
ember_dl-0.9.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smeshidojoe
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,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: ember-dl
3
+ Version: 0.9.0
4
+ Summary: Extract and download media from social media
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 smeshidojoe
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Project-URL: Homepage, https://smeshidojoe.github.io/Ember/
28
+ Project-URL: Documentation, https://smeshidojoe.github.io/Ember/docs/
29
+ Project-URL: Source, https://github.com/smeshidojoe/Ember
30
+ Project-URL: Issues, https://github.com/smeshidojoe/Ember/issues
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Operating System :: OS Independent
34
+ Classifier: Topic :: Multimedia :: Video
35
+ Requires-Python: >=3.9
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: requests>=2.25
39
+ Provides-Extra: browser
40
+ Requires-Dist: yt-dlp; extra == "browser"
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=7; extra == "dev"
43
+ Dynamic: license-file
44
+
45
+ **[🇷🇺 Русский](https://github.com/smeshidojoe/Ember/blob/main/README.RU.md)** · **[🇬🇧 English](https://github.com/smeshidojoe/Ember/blob/main/README.md)**
46
+
47
+ # Ember
48
+
49
+ An embeddable Python library and CLI for extracting and downloading media from social
50
+ platforms — a compact alternative to [cobalt](https://github.com/imputnet/cobalt). Given a
51
+ post URL it returns **direct media URLs + metadata** and can **download by itself**
52
+ (including HLS), without requiring yt-dlp.
53
+
54
+ The only required dependency is `requests`. Python 3.9+.
55
+
56
+ ## Supported services
57
+
58
+ **18 services:**
59
+
60
+ | Service | Extracts | Notes |
61
+ |---|---|---|
62
+ | TikTok | videos, photo posts, music | |
63
+ | Twitter/X | videos, GIFs, photos | NSFW tweets need cookies |
64
+ | Instagram | posts, Reels, carousels, Stories, highlights | anonymously often preview-only; Stories/highlights need cookies |
65
+ | Reddit | videos, GIFs, images, galleries | may be IP-blocked on VPN/datacenter |
66
+ | Vimeo | videos (mp4/HLS) | |
67
+ | SoundCloud | tracks, sets | premium tracks give a 30s preview anonymously |
68
+ | Pinterest | video/image pins | |
69
+ | Tumblr | video, audio, photos | |
70
+ | Bluesky | video (HLS), images, GIFs | |
71
+ | Newgrounds | video, audio | anti-bot on some IPs |
72
+ | Rutube | videos (HLS) | |
73
+ | OK.ru | videos | may need a normal (non-datacenter) IP |
74
+ | VK / VK Video | videos, clips | closed groups / private videos need cookies |
75
+ | Facebook | videos, Reels | usually needs cookies |
76
+ | Twitch | clips only | not VODs/streams |
77
+ | Pornhub | videos | age wall bypassed automatically |
78
+ | XVideos | videos (HLS) | mirror domains supported |
79
+ | RedGifs | videos | |
80
+
81
+ > Reddit, Newgrounds and OK.ru block anonymous requests from datacenter/VPN addresses
82
+ > (they work on a normal home IP); Instagram and Facebook may require cookies for full
83
+ > access. See "Limitations".
84
+
85
+ ## Installation
86
+
87
+ ```bash
88
+ pip install ember-dl
89
+ ```
90
+
91
+ The distribution is named `ember-dl`, the import name stays `ember`:
92
+
93
+ ```python
94
+ import ember
95
+ ```
96
+
97
+ Latest development version straight from Git:
98
+
99
+ ```bash
100
+ pip install git+https://github.com/smeshidojoe/Ember.git
101
+ ```
102
+
103
+ After installation both the Python API (`import ember`) and the `ember` command are available.
104
+
105
+ `--cookies-from-browser` works out of the box (no extra deps) for **Firefox** (any OS)
106
+ and **Chromium-family browsers on Windows** (Vivaldi, Opera, and non-ABE Chrome/Edge/Brave).
107
+ Installing `pip install yt-dlp` is only an optional fallback for cases the built-in reader
108
+ doesn't cover (e.g. Chromium on macOS/Linux).
109
+
110
+ Downloading HLS with separate tracks, muxing video+audio, embedding metadata and
111
+ audio-only mode require **ffmpeg** in `PATH` (not needed for direct mp4 or plain HLS).
112
+
113
+ ## Quick start — Python
114
+
115
+ ```python
116
+ import ember
117
+
118
+ # 1) get direct links and metadata
119
+ result = ember.extract("https://vimeo.com/76979871")
120
+ print(result.title, result.author, result.thumbnail)
121
+ for m in result.media:
122
+ print(m.kind, m.quality, m.url)
123
+
124
+ # 2) download with Ember itself (no yt-dlp needed)
125
+ def on_progress(p: ember.DownloadProgress):
126
+ if p.fraction is not None:
127
+ print(f"{p.fraction*100:.0f}%")
128
+
129
+ paths = ember.download(
130
+ result, "downloads/",
131
+ max_height=720, # cap quality
132
+ concurrency=6, # parallel HLS segments
133
+ on_progress=on_progress,
134
+ embed_metadata=True, # write title/author (ffmpeg)
135
+ )
136
+ ```
137
+
138
+ Link check and quality list:
139
+
140
+ ```python
141
+ ember.can_extract(url) # is the link supported
142
+ ember.available_qualities(result.media[0]) # e.g. [1080, 720, 480]
143
+ ember.ffmpeg_available() # is ffmpeg present
144
+
145
+ # building a quality selector: hide rows and choose the order
146
+ ember.available_qualities(result.media[0], exclude=[360, 480]) # drop those rows
147
+ ember.available_qualities(result.media[0], ascending=True) # low -> high
148
+ ```
149
+
150
+ Playlists (SoundCloud sets for now):
151
+
152
+ ```python
153
+ if ember.supports_playlist(url):
154
+ for entry in ember.extract_playlist(url).entries:
155
+ ember.download(entry, "downloads/")
156
+ ```
157
+
158
+ Author timeline — latest posts by profile/channel URL (Twitter/X, Instagram,
159
+ Vimeo, SoundCloud, Pinterest, Tumblr, Rutube, VK, Twitch):
160
+
161
+ ```python
162
+ if ember.supports_timeline(url): # e.g. "https://x.com/nasa"
163
+ for entry in ember.extract_timeline(url, limit=30).entries:
164
+ ember.download(entry, "downloads/")
165
+ ```
166
+
167
+ Instagram story highlights of a profile (one entry per collection, needs cookies):
168
+
169
+ ```python
170
+ pl = ember.extract_highlights("https://www.instagram.com/USER/",
171
+ cookies_from_browser="firefox")
172
+ for entry in pl.entries:
173
+ print(entry.title, len(entry.media))
174
+ ember.download(entry, "downloads/")
175
+ ```
176
+
177
+ Long batches — don't redo finished files, and stay polite on the network:
178
+
179
+ ```python
180
+ ember.download(result, "downloads/",
181
+ skip_existing=True, # keep what is already on disk
182
+ rate_limit=1_000_000) # ~1 MB/s total, shared by all threads
183
+ ```
184
+
185
+ ## Quick start — command line
186
+
187
+ Quote the URL. **Without `-d` the command only prints links and metadata — no download
188
+ starts.** Flag order does not matter.
189
+
190
+ ```bash
191
+ # show direct links and metadata
192
+ ember "https://x.com/user/status/123456789"
193
+
194
+ # same, as JSON
195
+ ember "https://www.tiktok.com/@user/video/7123456789" --json
196
+
197
+ # DOWNLOAD: -d enables it (name from the site, current folder)
198
+ ember -d "https://vimeo.com/76979871"
199
+
200
+ # custom file name (-o) and folder (-p)
201
+ ember -d -o myclip -p downloads "https://vimeo.com/76979871"
202
+
203
+ # cap quality, fetch HLS in 6 threads
204
+ ember -d -p downloads --max-height 720 --concurrency 6 "https://rutube.ru/video/<id>/"
205
+
206
+ # audio only + write metadata into the file (needs ffmpeg)
207
+ ember -d --audio-only --embed-metadata "https://soundcloud.com/user/track"
208
+
209
+ # a whole playlist / set
210
+ ember -d --playlist "https://soundcloud.com/user/sets/name"
211
+
212
+ # cookies (NSFW tweets, private Instagram, closed VK groups)
213
+ ember "https://x.com/user/status/123" --cookies "auth_token=...; ct0=..."
214
+ ember "https://x.com/user/status/123" --cookies-from-browser firefox
215
+
216
+ # Instagram story highlights of a profile (needs cookies)
217
+ ember --highlights --cookies-from-browser firefox "https://www.instagram.com/USER/"
218
+
219
+ # resume a batch politely: skip finished files, cap speed at ~1 MB/s
220
+ ember -d -a links.txt --skip-existing --rate-limit 1000000
221
+ ```
222
+
223
+ ## Flags cheat sheet
224
+
225
+ | Flag | Meaning |
226
+ |---|---|
227
+ | `-d`, `--download` | enable downloading (without it — only print links) |
228
+ | `-o`, `--output NAME` | file name without extension, or a template: `%(title)s`/`%(author)s`/`%(service)s`/`%(id)s`; implies download |
229
+ | `-p`, `--path DIR` | target folder (default: current folder); implies download |
230
+ | `-a`, `--batch-file FILE` | read links from a file (one per line, `#` comments; `-` = stdin) |
231
+ | `-F`, `--list-formats` | list available qualities and exit (no download) |
232
+ | `-v`, `--verbose` | log to stderr; `-vv` for debug |
233
+ | `--json` | print metadata as JSON |
234
+ | `--max-height N` | cap quality by height (e.g. `720`) |
235
+ | `--audio-only` | keep audio only (needs ffmpeg); implies download |
236
+ | `--subs` | also download subtitle tracks; implies download |
237
+ | `--concurrency N` | parallel HLS segments (default `1`) |
238
+ | `--embed-metadata` (`--metadata`) | write title/author into the file (needs ffmpeg); implies download |
239
+ | `--thumbnail` | also save the cover image; implies download |
240
+ | `--write-info` | save a `{name}.info.json` sidecar with all metadata; implies download |
241
+ | `--skip-existing` | keep files that are already on disk instead of re-downloading |
242
+ | `--rate-limit N` | cap total speed in bytes/sec (e.g. `1000000` ≈ 1 MB/s), shared across threads |
243
+ | `--size` | show file size before downloading (one extra request) |
244
+ | `--playlist` | treat as a set (SoundCloud sets) |
245
+ | `--timeline` | treat the URL as a profile/channel, list latest posts |
246
+ | `--highlights` | treat the URL as a profile, list its story highlights (Instagram; needs cookies) |
247
+ | `--limit N` | max items for `--timeline` / `--highlights` (default `30`) |
248
+ | `--proxy URL` | proxy for all requests, e.g. `http://host:port` (helps with IP-blocked sites) |
249
+ | `--timeout SEC` | per-request timeout, seconds (default `15`) |
250
+ | `--cookies "a=1; b=2"` | cookies as a string |
251
+ | `--cookies-file FILE` | cookies.txt in Netscape format (like yt-dlp) |
252
+ | `--cookies-from-browser B` | cookies from a browser: brave/chrome/chromium/edge/firefox/opera/safari/vivaldi/whale |
253
+ | `--browser-profile P` | browser profile for the previous flag |
254
+ | `--list-services` | print supported services and exit |
255
+ | `--version` | print the version and exit |
256
+ | `-h`, `--help` | show help and exit |
257
+
258
+ Terminal help — `ember --help` or `ember -h`:
259
+
260
+ ```
261
+ usage: ember [-h] [--version] [--list-services] [-a FILE] [--json] [-F] [-v]
262
+ [--timeout SEC] [-d] [-o NAME] [-p DIR] [--max-height N]
263
+ [--audio-only] [--subs] [--thumbnail] [--write-info] [--size]
264
+ [--concurrency N] [--skip-existing] [--rate-limit N]
265
+ [--embed-metadata] [--playlist] [--timeline] [--highlights]
266
+ [--limit N] [--proxy URL] [--cookies "name=value; ..."]
267
+ [--cookies-file cookies.txt] [--cookies-from-browser BROWSER]
268
+ [--browser-profile PROFILE] [url]
269
+ ```
270
+
271
+ ## How it works
272
+
273
+ Ember calls the same "internal" service APIs that the site itself uses in the browser,
274
+ pulls direct media links and metadata out of the response, and packs them into a `Result`
275
+ object. Each service is a small standalone module in `ember/services/`. HLS manifests are
276
+ parsed by a built-in parser, segments are fetched and assembled into a file (with ffmpeg —
277
+ remuxed into `.mp4`).
278
+
279
+ ## Limitations
280
+
281
+ - **Instagram** — anonymously often returns only a preview; pass cookies for full quality.
282
+ - **Reddit, Newgrounds, OK.ru** — block anonymous requests from datacenter/VPN IPs; they
283
+ work on a normal home IP. `proxies=` / a different IP helps.
284
+ - **Facebook** — public video usually requires cookies.
285
+ - **Twitter/X** — NSFW tweets require account cookies (`auth_token` and `ct0`).
286
+ - Full YouTube support is out of scope — use yt-dlp for it.
287
+
288
+ ## Credits
289
+
290
+ Extraction methods follow the approaches of
291
+ [imputnet/cobalt](https://github.com/imputnet/cobalt).
@@ -0,0 +1,247 @@
1
+ **[🇷🇺 Русский](https://github.com/smeshidojoe/Ember/blob/main/README.RU.md)** · **[🇬🇧 English](https://github.com/smeshidojoe/Ember/blob/main/README.md)**
2
+
3
+ # Ember
4
+
5
+ An embeddable Python library and CLI for extracting and downloading media from social
6
+ platforms — a compact alternative to [cobalt](https://github.com/imputnet/cobalt). Given a
7
+ post URL it returns **direct media URLs + metadata** and can **download by itself**
8
+ (including HLS), without requiring yt-dlp.
9
+
10
+ The only required dependency is `requests`. Python 3.9+.
11
+
12
+ ## Supported services
13
+
14
+ **18 services:**
15
+
16
+ | Service | Extracts | Notes |
17
+ |---|---|---|
18
+ | TikTok | videos, photo posts, music | |
19
+ | Twitter/X | videos, GIFs, photos | NSFW tweets need cookies |
20
+ | Instagram | posts, Reels, carousels, Stories, highlights | anonymously often preview-only; Stories/highlights need cookies |
21
+ | Reddit | videos, GIFs, images, galleries | may be IP-blocked on VPN/datacenter |
22
+ | Vimeo | videos (mp4/HLS) | |
23
+ | SoundCloud | tracks, sets | premium tracks give a 30s preview anonymously |
24
+ | Pinterest | video/image pins | |
25
+ | Tumblr | video, audio, photos | |
26
+ | Bluesky | video (HLS), images, GIFs | |
27
+ | Newgrounds | video, audio | anti-bot on some IPs |
28
+ | Rutube | videos (HLS) | |
29
+ | OK.ru | videos | may need a normal (non-datacenter) IP |
30
+ | VK / VK Video | videos, clips | closed groups / private videos need cookies |
31
+ | Facebook | videos, Reels | usually needs cookies |
32
+ | Twitch | clips only | not VODs/streams |
33
+ | Pornhub | videos | age wall bypassed automatically |
34
+ | XVideos | videos (HLS) | mirror domains supported |
35
+ | RedGifs | videos | |
36
+
37
+ > Reddit, Newgrounds and OK.ru block anonymous requests from datacenter/VPN addresses
38
+ > (they work on a normal home IP); Instagram and Facebook may require cookies for full
39
+ > access. See "Limitations".
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install ember-dl
45
+ ```
46
+
47
+ The distribution is named `ember-dl`, the import name stays `ember`:
48
+
49
+ ```python
50
+ import ember
51
+ ```
52
+
53
+ Latest development version straight from Git:
54
+
55
+ ```bash
56
+ pip install git+https://github.com/smeshidojoe/Ember.git
57
+ ```
58
+
59
+ After installation both the Python API (`import ember`) and the `ember` command are available.
60
+
61
+ `--cookies-from-browser` works out of the box (no extra deps) for **Firefox** (any OS)
62
+ and **Chromium-family browsers on Windows** (Vivaldi, Opera, and non-ABE Chrome/Edge/Brave).
63
+ Installing `pip install yt-dlp` is only an optional fallback for cases the built-in reader
64
+ doesn't cover (e.g. Chromium on macOS/Linux).
65
+
66
+ Downloading HLS with separate tracks, muxing video+audio, embedding metadata and
67
+ audio-only mode require **ffmpeg** in `PATH` (not needed for direct mp4 or plain HLS).
68
+
69
+ ## Quick start — Python
70
+
71
+ ```python
72
+ import ember
73
+
74
+ # 1) get direct links and metadata
75
+ result = ember.extract("https://vimeo.com/76979871")
76
+ print(result.title, result.author, result.thumbnail)
77
+ for m in result.media:
78
+ print(m.kind, m.quality, m.url)
79
+
80
+ # 2) download with Ember itself (no yt-dlp needed)
81
+ def on_progress(p: ember.DownloadProgress):
82
+ if p.fraction is not None:
83
+ print(f"{p.fraction*100:.0f}%")
84
+
85
+ paths = ember.download(
86
+ result, "downloads/",
87
+ max_height=720, # cap quality
88
+ concurrency=6, # parallel HLS segments
89
+ on_progress=on_progress,
90
+ embed_metadata=True, # write title/author (ffmpeg)
91
+ )
92
+ ```
93
+
94
+ Link check and quality list:
95
+
96
+ ```python
97
+ ember.can_extract(url) # is the link supported
98
+ ember.available_qualities(result.media[0]) # e.g. [1080, 720, 480]
99
+ ember.ffmpeg_available() # is ffmpeg present
100
+
101
+ # building a quality selector: hide rows and choose the order
102
+ ember.available_qualities(result.media[0], exclude=[360, 480]) # drop those rows
103
+ ember.available_qualities(result.media[0], ascending=True) # low -> high
104
+ ```
105
+
106
+ Playlists (SoundCloud sets for now):
107
+
108
+ ```python
109
+ if ember.supports_playlist(url):
110
+ for entry in ember.extract_playlist(url).entries:
111
+ ember.download(entry, "downloads/")
112
+ ```
113
+
114
+ Author timeline — latest posts by profile/channel URL (Twitter/X, Instagram,
115
+ Vimeo, SoundCloud, Pinterest, Tumblr, Rutube, VK, Twitch):
116
+
117
+ ```python
118
+ if ember.supports_timeline(url): # e.g. "https://x.com/nasa"
119
+ for entry in ember.extract_timeline(url, limit=30).entries:
120
+ ember.download(entry, "downloads/")
121
+ ```
122
+
123
+ Instagram story highlights of a profile (one entry per collection, needs cookies):
124
+
125
+ ```python
126
+ pl = ember.extract_highlights("https://www.instagram.com/USER/",
127
+ cookies_from_browser="firefox")
128
+ for entry in pl.entries:
129
+ print(entry.title, len(entry.media))
130
+ ember.download(entry, "downloads/")
131
+ ```
132
+
133
+ Long batches — don't redo finished files, and stay polite on the network:
134
+
135
+ ```python
136
+ ember.download(result, "downloads/",
137
+ skip_existing=True, # keep what is already on disk
138
+ rate_limit=1_000_000) # ~1 MB/s total, shared by all threads
139
+ ```
140
+
141
+ ## Quick start — command line
142
+
143
+ Quote the URL. **Without `-d` the command only prints links and metadata — no download
144
+ starts.** Flag order does not matter.
145
+
146
+ ```bash
147
+ # show direct links and metadata
148
+ ember "https://x.com/user/status/123456789"
149
+
150
+ # same, as JSON
151
+ ember "https://www.tiktok.com/@user/video/7123456789" --json
152
+
153
+ # DOWNLOAD: -d enables it (name from the site, current folder)
154
+ ember -d "https://vimeo.com/76979871"
155
+
156
+ # custom file name (-o) and folder (-p)
157
+ ember -d -o myclip -p downloads "https://vimeo.com/76979871"
158
+
159
+ # cap quality, fetch HLS in 6 threads
160
+ ember -d -p downloads --max-height 720 --concurrency 6 "https://rutube.ru/video/<id>/"
161
+
162
+ # audio only + write metadata into the file (needs ffmpeg)
163
+ ember -d --audio-only --embed-metadata "https://soundcloud.com/user/track"
164
+
165
+ # a whole playlist / set
166
+ ember -d --playlist "https://soundcloud.com/user/sets/name"
167
+
168
+ # cookies (NSFW tweets, private Instagram, closed VK groups)
169
+ ember "https://x.com/user/status/123" --cookies "auth_token=...; ct0=..."
170
+ ember "https://x.com/user/status/123" --cookies-from-browser firefox
171
+
172
+ # Instagram story highlights of a profile (needs cookies)
173
+ ember --highlights --cookies-from-browser firefox "https://www.instagram.com/USER/"
174
+
175
+ # resume a batch politely: skip finished files, cap speed at ~1 MB/s
176
+ ember -d -a links.txt --skip-existing --rate-limit 1000000
177
+ ```
178
+
179
+ ## Flags cheat sheet
180
+
181
+ | Flag | Meaning |
182
+ |---|---|
183
+ | `-d`, `--download` | enable downloading (without it — only print links) |
184
+ | `-o`, `--output NAME` | file name without extension, or a template: `%(title)s`/`%(author)s`/`%(service)s`/`%(id)s`; implies download |
185
+ | `-p`, `--path DIR` | target folder (default: current folder); implies download |
186
+ | `-a`, `--batch-file FILE` | read links from a file (one per line, `#` comments; `-` = stdin) |
187
+ | `-F`, `--list-formats` | list available qualities and exit (no download) |
188
+ | `-v`, `--verbose` | log to stderr; `-vv` for debug |
189
+ | `--json` | print metadata as JSON |
190
+ | `--max-height N` | cap quality by height (e.g. `720`) |
191
+ | `--audio-only` | keep audio only (needs ffmpeg); implies download |
192
+ | `--subs` | also download subtitle tracks; implies download |
193
+ | `--concurrency N` | parallel HLS segments (default `1`) |
194
+ | `--embed-metadata` (`--metadata`) | write title/author into the file (needs ffmpeg); implies download |
195
+ | `--thumbnail` | also save the cover image; implies download |
196
+ | `--write-info` | save a `{name}.info.json` sidecar with all metadata; implies download |
197
+ | `--skip-existing` | keep files that are already on disk instead of re-downloading |
198
+ | `--rate-limit N` | cap total speed in bytes/sec (e.g. `1000000` ≈ 1 MB/s), shared across threads |
199
+ | `--size` | show file size before downloading (one extra request) |
200
+ | `--playlist` | treat as a set (SoundCloud sets) |
201
+ | `--timeline` | treat the URL as a profile/channel, list latest posts |
202
+ | `--highlights` | treat the URL as a profile, list its story highlights (Instagram; needs cookies) |
203
+ | `--limit N` | max items for `--timeline` / `--highlights` (default `30`) |
204
+ | `--proxy URL` | proxy for all requests, e.g. `http://host:port` (helps with IP-blocked sites) |
205
+ | `--timeout SEC` | per-request timeout, seconds (default `15`) |
206
+ | `--cookies "a=1; b=2"` | cookies as a string |
207
+ | `--cookies-file FILE` | cookies.txt in Netscape format (like yt-dlp) |
208
+ | `--cookies-from-browser B` | cookies from a browser: brave/chrome/chromium/edge/firefox/opera/safari/vivaldi/whale |
209
+ | `--browser-profile P` | browser profile for the previous flag |
210
+ | `--list-services` | print supported services and exit |
211
+ | `--version` | print the version and exit |
212
+ | `-h`, `--help` | show help and exit |
213
+
214
+ Terminal help — `ember --help` or `ember -h`:
215
+
216
+ ```
217
+ usage: ember [-h] [--version] [--list-services] [-a FILE] [--json] [-F] [-v]
218
+ [--timeout SEC] [-d] [-o NAME] [-p DIR] [--max-height N]
219
+ [--audio-only] [--subs] [--thumbnail] [--write-info] [--size]
220
+ [--concurrency N] [--skip-existing] [--rate-limit N]
221
+ [--embed-metadata] [--playlist] [--timeline] [--highlights]
222
+ [--limit N] [--proxy URL] [--cookies "name=value; ..."]
223
+ [--cookies-file cookies.txt] [--cookies-from-browser BROWSER]
224
+ [--browser-profile PROFILE] [url]
225
+ ```
226
+
227
+ ## How it works
228
+
229
+ Ember calls the same "internal" service APIs that the site itself uses in the browser,
230
+ pulls direct media links and metadata out of the response, and packs them into a `Result`
231
+ object. Each service is a small standalone module in `ember/services/`. HLS manifests are
232
+ parsed by a built-in parser, segments are fetched and assembled into a file (with ffmpeg —
233
+ remuxed into `.mp4`).
234
+
235
+ ## Limitations
236
+
237
+ - **Instagram** — anonymously often returns only a preview; pass cookies for full quality.
238
+ - **Reddit, Newgrounds, OK.ru** — block anonymous requests from datacenter/VPN IPs; they
239
+ work on a normal home IP. `proxies=` / a different IP helps.
240
+ - **Facebook** — public video usually requires cookies.
241
+ - **Twitter/X** — NSFW tweets require account cookies (`auth_token` and `ct0`).
242
+ - Full YouTube support is out of scope — use yt-dlp for it.
243
+
244
+ ## Credits
245
+
246
+ Extraction methods follow the approaches of
247
+ [imputnet/cobalt](https://github.com/imputnet/cobalt).
@@ -0,0 +1,66 @@
1
+ """Ember — extract direct media links from social platforms.
2
+
3
+ An embeddable, cobalt-like (imputnet/cobalt) Python library.
4
+
5
+ Usage:
6
+ import ember
7
+ result = ember.extract("https://www.tiktok.com/@user/video/123...")
8
+ for m in result.media:
9
+ print(m.kind, m.url)
10
+
11
+ Logging: the package logs to the "ember" logger and is silent by default
12
+ (NullHandler). To see logs from your app:
13
+ import logging; logging.basicConfig(); logging.getLogger("ember").setLevel(logging.INFO)
14
+ """
15
+
16
+ import logging as _logging
17
+
18
+ # best practice: a library must not force output — silent by default
19
+ _logging.getLogger("ember").addHandler(_logging.NullHandler())
20
+
21
+ from .errors import (
22
+ EmberError,
23
+ ExtractionError,
24
+ NetworkError,
25
+ UnsupportedUrlError,
26
+ )
27
+ from .cookies import cookies_from_browser, cookies_from_file
28
+ from .download import (DownloadProgress, RateLimiter, available_qualities,
29
+ download, download_media, ffmpeg_available, probe_size)
30
+ from .models import Media, MediaVariant, Playlist, Result, Subtitle
31
+ from .router import (can_extract, extract, extract_highlights, extract_playlist,
32
+ extract_timeline, supported_services, supports_highlights,
33
+ supports_playlist, supports_timeline)
34
+
35
+ __version__ = "0.9.0"
36
+
37
+ __all__ = [
38
+ "extract",
39
+ "extract_playlist",
40
+ "extract_timeline",
41
+ "extract_highlights",
42
+ "can_extract",
43
+ "supports_playlist",
44
+ "supports_timeline",
45
+ "supports_highlights",
46
+ "supported_services",
47
+ "cookies_from_browser",
48
+ "cookies_from_file",
49
+ "download",
50
+ "download_media",
51
+ "available_qualities",
52
+ "ffmpeg_available",
53
+ "probe_size",
54
+ "DownloadProgress",
55
+ "RateLimiter",
56
+ "MediaVariant",
57
+ "Playlist",
58
+ "Result",
59
+ "Media",
60
+ "Subtitle",
61
+ "EmberError",
62
+ "UnsupportedUrlError",
63
+ "NetworkError",
64
+ "ExtractionError",
65
+ "__version__",
66
+ ]