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.
- ember_dl-0.9.0/LICENSE +21 -0
- ember_dl-0.9.0/PKG-INFO +291 -0
- ember_dl-0.9.0/README.md +247 -0
- ember_dl-0.9.0/ember/__init__.py +66 -0
- ember_dl-0.9.0/ember/__main__.py +497 -0
- ember_dl-0.9.0/ember/_browser_cookies.py +611 -0
- ember_dl-0.9.0/ember/cache.py +79 -0
- ember_dl-0.9.0/ember/cookies.py +179 -0
- ember_dl-0.9.0/ember/download.py +549 -0
- ember_dl-0.9.0/ember/errors.py +29 -0
- ember_dl-0.9.0/ember/hls.py +149 -0
- ember_dl-0.9.0/ember/http.py +121 -0
- ember_dl-0.9.0/ember/models.py +148 -0
- ember_dl-0.9.0/ember/py.typed +0 -0
- ember_dl-0.9.0/ember/router.py +233 -0
- ember_dl-0.9.0/ember/services/__init__.py +0 -0
- ember_dl-0.9.0/ember/services/bluesky.py +79 -0
- ember_dl-0.9.0/ember/services/facebook.py +74 -0
- ember_dl-0.9.0/ember/services/instagram.py +434 -0
- ember_dl-0.9.0/ember/services/newgrounds.py +76 -0
- ember_dl-0.9.0/ember/services/ok.py +78 -0
- ember_dl-0.9.0/ember/services/pinterest.py +110 -0
- ember_dl-0.9.0/ember/services/pornhub.py +72 -0
- ember_dl-0.9.0/ember/services/reddit.py +136 -0
- ember_dl-0.9.0/ember/services/redgifs.py +95 -0
- ember_dl-0.9.0/ember/services/rutube.py +90 -0
- ember_dl-0.9.0/ember/services/soundcloud.py +169 -0
- ember_dl-0.9.0/ember/services/tiktok.py +129 -0
- ember_dl-0.9.0/ember/services/tumblr.py +147 -0
- ember_dl-0.9.0/ember/services/twitch.py +110 -0
- ember_dl-0.9.0/ember/services/twitter.py +319 -0
- ember_dl-0.9.0/ember/services/vimeo.py +130 -0
- ember_dl-0.9.0/ember/services/vk.py +264 -0
- ember_dl-0.9.0/ember/services/xvideos.py +88 -0
- ember_dl-0.9.0/ember_dl.egg-info/PKG-INFO +291 -0
- ember_dl-0.9.0/ember_dl.egg-info/SOURCES.txt +46 -0
- ember_dl-0.9.0/ember_dl.egg-info/dependency_links.txt +1 -0
- ember_dl-0.9.0/ember_dl.egg-info/entry_points.txt +2 -0
- ember_dl-0.9.0/ember_dl.egg-info/requires.txt +7 -0
- ember_dl-0.9.0/ember_dl.egg-info/top_level.txt +1 -0
- ember_dl-0.9.0/pyproject.toml +39 -0
- ember_dl-0.9.0/setup.cfg +4 -0
- ember_dl-0.9.0/tests/test_cache.py +29 -0
- ember_dl-0.9.0/tests/test_cli.py +16 -0
- ember_dl-0.9.0/tests/test_crypto.py +51 -0
- ember_dl-0.9.0/tests/test_hls.py +104 -0
- ember_dl-0.9.0/tests/test_models.py +28 -0
- 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.
|
ember_dl-0.9.0/PKG-INFO
ADDED
|
@@ -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).
|
ember_dl-0.9.0/README.md
ADDED
|
@@ -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
|
+
]
|