umedia 0.1.0 → 0.1.1
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.
- package/README.md +161 -63
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,77 +1,70 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://raw.githubusercontent.com/Anonymous20666/unified-media-api/main/web/og-image.jpg" alt="Unified Media API — one API for every media platform" width="760" />
|
|
4
|
+
|
|
1
5
|
# umedia
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
**One API for every media platform — in your code and in your terminal.**
|
|
8
|
+
|
|
9
|
+
Search, resolve and download media from YouTube · TikTok · Instagram · Reddit · X · iTunes · 1800+ sites
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/umedia)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
[](#-zero-dependencies)
|
|
14
|
+
[](LICENSE)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
5
17
|
|
|
6
|
-
|
|
7
|
-
- **Honest quality labels.** Every result reports `requestedQuality` vs `selectedQuality`
|
|
8
|
-
and a `fallback` flag. Quality is derived from the real source height — never manufactured.
|
|
9
|
-
- **Full galleries preserved.** A 100-photo post resolves to 100 items, in order.
|
|
10
|
-
`truncated` / `failedItems[]` are always explicit — never silent.
|
|
11
|
-
- **Real previews first.** 30s clips (`clip30s`), trailers, or official `embed`s —
|
|
12
|
-
`previewKind: "none"` when nothing honest exists.
|
|
18
|
+
---
|
|
13
19
|
|
|
14
|
-
##
|
|
20
|
+
## ⚡ From zero to media in 60 seconds
|
|
15
21
|
|
|
16
22
|
```bash
|
|
17
23
|
npm install umedia
|
|
18
24
|
```
|
|
19
25
|
|
|
20
|
-
## Quickstart
|
|
21
|
-
|
|
22
26
|
```js
|
|
23
27
|
import { UMedia } from "umedia";
|
|
24
28
|
|
|
25
29
|
const client = new UMedia({ apiKey: process.env.UMedia_API_KEY }); // key optional
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
const { data } = await client.search({ q: "afrobeats mix", type: "music", limit: 5 });
|
|
31
|
+
const { data } = await client.search({ q: "afrobeats", type: "music", limit: 3 });
|
|
29
32
|
for (const item of data.items) {
|
|
30
|
-
console.log(item.title, item.previewKind, item.previewUrl);
|
|
33
|
+
console.log(item.title, "—", item.previewKind, "→", item.previewUrl);
|
|
31
34
|
}
|
|
35
|
+
```
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
const { data: post } = await client.resolve("https://www.tiktok.com/@user/photo/7690630628697459990");
|
|
35
|
-
console.log(post.itemCount, post.counts, post.truncated);
|
|
36
|
-
for (const m of post.media) console.log(m.index, m.type, m.quality, m.url);
|
|
37
|
+
That's it. No SDK zoo, no six-service setup — one small client, the whole media web behind it.
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
const { data: handle } = await client.createDownload({ url: "https://youtu.be/jNQXAC9IVRw", quality: "best" });
|
|
40
|
-
// poll until done
|
|
41
|
-
let job;
|
|
42
|
-
do {
|
|
43
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
44
|
-
job = (await client.getDownload(handle.jobId)).data;
|
|
45
|
-
} while (job.status !== "done" && job.status !== "failed");
|
|
39
|
+
---
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
## 🎯 Why developers pick it
|
|
42
|
+
|
|
43
|
+
### 🏷️ Honest quality — the industry's favorite lie, removed
|
|
44
|
+
|
|
45
|
+
Every download reports what you **asked for** vs what the source **actually has**:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
const { data: handle } = await client.createDownload({ url: oldVideo, quality: "best" });
|
|
49
|
+
// … poll …
|
|
50
|
+
const r = (await client.getDownload(handle.jobId)).data.result;
|
|
51
|
+
console.log(r.requestedQuality, "→", r.selectedQuality); // "best" → "240p" ← real, never invented
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Quality is derived from the real source height. There is no "1080p" sticker on a 240p file. Ever.
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
`meta.requestId` is included in every response (quote it when reporting issues).
|
|
56
|
+
### 🖼️ Galleries arrive complete — 100 photos means 100 photos
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| `search({q, type, limit, adult})` | `GET /api/v1/social/search` | `type`: `video` \| `music` \| `movie` \| `short_video`, `limit ≤ 25` |
|
|
62
|
-
| `resolve(url)` | `POST /api/v1/social/resolve` | returns `ResolveResult` |
|
|
63
|
-
| `createDownload({url, type, quality})` | `POST /api/v1/download` | `quality`: `original` \| `best` \| `2160p` … `144p` \| `audio` |
|
|
64
|
-
| `getDownload(jobId)` | `GET /api/v1/download/{id}` | poll until `status` is `done` / `failed` / `cancelled` |
|
|
65
|
-
| `listDownloads()` | `GET /api/v1/download` | jobs for this key |
|
|
66
|
-
| `cancelDownload(jobId)` | `POST /api/v1/download/{id}/cancel` | |
|
|
67
|
-
| `fileUrl(jobId, index)` | `GET /api/v1/download/{id}/file?index=N` | single file streams; multi-item = ordered zip |
|
|
68
|
-
| `fetchFile(jobId, index)` | same | returns a `Response` (streamable) |
|
|
69
|
-
| `capabilities()` | `GET /api/v1/capabilities` | live platform registry |
|
|
70
|
-
| `status()` | `GET /api/v1/status` | real component + provider health |
|
|
58
|
+
A TikTok photo post with 30 slides resolves to **30 MediaObjects, in order**. A mixed post keeps
|
|
59
|
+
its sequence. If anything is dropped, it's named — `truncated: true`, `failedItems[]` — never silent.
|
|
71
60
|
|
|
72
|
-
|
|
61
|
+
### 🎧 Previews drop first
|
|
73
62
|
|
|
74
|
-
|
|
63
|
+
Music search leads with real **30-second clips** (`previewKind: "clip30s"`), movies with trailers,
|
|
64
|
+
social posts with official embeds. `previewKind: "none"` when nothing honest exists — we'd rather
|
|
65
|
+
say "none" than fake one.
|
|
66
|
+
|
|
67
|
+
### 🧯 Typed failures instead of mystery crashes
|
|
75
68
|
|
|
76
69
|
```js
|
|
77
70
|
import { UMediaError, Codes } from "umedia";
|
|
@@ -80,33 +73,138 @@ try {
|
|
|
80
73
|
await client.resolve(url);
|
|
81
74
|
} catch (e) {
|
|
82
75
|
if (e instanceof UMediaError) {
|
|
83
|
-
console.
|
|
84
|
-
if (e.code === Codes.RATE_LIMITED) await
|
|
85
|
-
if (e.code === Codes.PROVIDER_UNAVAILABLE) retryWithBackoff();
|
|
76
|
+
console.error(e.code, e.message, e.requestId); // e.g. PROVIDER_UNAVAILABLE, req_9f3a…
|
|
77
|
+
if (e.code === Codes.RATE_LIMITED) await sleep(60_000);
|
|
78
|
+
if (e.code === Codes.PROVIDER_UNAVAILABLE) await retryWithBackoff();
|
|
86
79
|
}
|
|
87
80
|
}
|
|
88
81
|
```
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
Every response — success or failure — carries a `meta.requestId`. Quote it and support can trace it.
|
|
84
|
+
|
|
85
|
+
### 📦 Zero dependencies
|
|
86
|
+
|
|
87
|
+
Node 18+, browsers, Deno, Bun. If you have `fetch`, you have `umedia`.
|
|
88
|
+
|
|
89
|
+
---
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
> failures upstream. What this client guarantees: **typed, honest errors with a
|
|
96
|
-
> `requestId` on every failure**, multi-provider attempts surfaced where available,
|
|
97
|
-
> and graceful degradation instead of breakage.
|
|
91
|
+
## 🧭 The tour
|
|
98
92
|
|
|
99
|
-
|
|
93
|
+
### Resolve a post → every photo, every video, in order
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const { data: post, engine } = await client.resolve(
|
|
97
|
+
"https://www.tiktok.com/@user/photo/7690630628697459990"
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log(post.itemCount); // 30
|
|
101
|
+
console.log(post.counts); // { images: 30, videos: 0, audios: 0, other: 0 }
|
|
102
|
+
console.log(post.truncated); // false — we tell you, always
|
|
103
|
+
|
|
104
|
+
for (const m of post.media) {
|
|
105
|
+
console.log(m.index, m.type, m.quality, m.url);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// who actually served this — multi-source transparency
|
|
109
|
+
console.log(engine.attempts, engine.finalProvider, engine.totalLatencyMs + "ms");
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Download — jobs, honest results, streaming files
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
const { data: handle } = await client.createDownload({
|
|
116
|
+
url: "https://youtu.be/jNQXAC9IVRw",
|
|
117
|
+
type: "video",
|
|
118
|
+
quality: "best", // original | best | 2160p … 144p | audio
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// poll the job
|
|
122
|
+
let job;
|
|
123
|
+
do {
|
|
124
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
125
|
+
job = (await client.getDownload(handle.jobId)).data;
|
|
126
|
+
} while (!["done", "failed", "cancelled"].includes(job.status));
|
|
127
|
+
|
|
128
|
+
// honest outcome
|
|
129
|
+
const r = job.result;
|
|
130
|
+
console.log(r.requestedQuality, "→", r.selectedQuality, r.fallback ? "(fallback)" : "");
|
|
131
|
+
|
|
132
|
+
// stream or save
|
|
133
|
+
const res = await client.fetchFile(handle.jobId, 0);
|
|
134
|
+
// multi-item job? files come as an ordered zip: "001 - name.jpg", "002 - …"
|
|
135
|
+
for (const f of r.files) console.log(f.index, f.name, client.fileUrl(handle.jobId, f.index));
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Platform metadata — real, not marketing
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const { data: caps } = await client.capabilities(); // live capability registry
|
|
142
|
+
const { data: st } = await client.status(); // real component + provider health
|
|
143
|
+
console.log(st.overall); // "operational" — checked, not hard-coded
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 📚 Reference
|
|
149
|
+
|
|
150
|
+
| Method | Endpoint | Notes |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `search({q, type, limit, adult})` | `GET /api/v1/social/search` | `type`: `video` \| `music` \| `movie` \| `short_video` · `limit ≤ 25` |
|
|
153
|
+
| `resolve(url)` | `POST /api/v1/social/resolve` | returns `{ data: ResolveResult, engine }` |
|
|
154
|
+
| `createDownload({url, type, quality})` | `POST /api/v1/download` | returns `{ jobId, status, progress, links }` |
|
|
155
|
+
| `getDownload(jobId)` | `GET /api/v1/download/{id}` | poll until `done` / `failed` / `cancelled` |
|
|
156
|
+
| `listDownloads()` | `GET /api/v1/download` | jobs for this key |
|
|
157
|
+
| `cancelDownload(jobId)` | `POST /api/v1/download/{id}/cancel` | |
|
|
158
|
+
| `fileUrl(jobId, index)` | `GET /api/v1/download/{id}/file?index=N` | single = file · multi = ordered zip |
|
|
159
|
+
| `fetchFile(jobId, index)` | same | returns a streamable `Response` |
|
|
160
|
+
| `capabilities()` | `GET /api/v1/capabilities` | live platform registry |
|
|
161
|
+
| `status()` | `GET /api/v1/status` | real health, refreshed continuously |
|
|
162
|
+
|
|
163
|
+
**Error codes** — `INVALID_REQUEST` · `INVALID_URL` · `SSRF_BLOCKED` · `AUTH_REQUIRED` ·
|
|
164
|
+
`AUTH_INVALID` · `RATE_LIMITED` · `MEDIA_NOT_FOUND` · `CONTENT_PRIVATE` · `ACCESS_RESTRICTED` ·
|
|
165
|
+
`QUALITY_UNAVAILABLE` · `PROVIDER_ERROR` · `PROVIDER_UNAVAILABLE` · `TIMEOUT` · `NOT_FOUND` · `INTERNAL`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 🖥️ The CLI
|
|
100
170
|
|
|
101
171
|
```bash
|
|
102
172
|
npx umedia search "lofi beats" --type music --limit 5
|
|
103
173
|
npx umedia resolve "https://www.tiktok.com/@user/photo/7690630628697459990"
|
|
104
174
|
npx umedia download "https://youtu.be/jNQXAC9IVRw" --quality best --wait
|
|
105
175
|
npx umedia status
|
|
176
|
+
npx umedia capabilities
|
|
106
177
|
```
|
|
107
178
|
|
|
108
|
-
Environment: `UMedia_API_KEY` (optional),
|
|
179
|
+
`--wait` polls the job and prints ready-to-use file URLs. Environment: `UMedia_API_KEY` (optional),
|
|
180
|
+
`UMedia_BASE_URL` (optional override).
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 🤝 The honest reliability contract
|
|
185
|
+
|
|
186
|
+
Let's be straight about guarantees, because the industry isn't:
|
|
187
|
+
|
|
188
|
+
- **Our layer** (auth, search, resolve, downloads, files): designed and measured for high
|
|
189
|
+
availability — hundreds of requests per second sustained on this platform, 4-worker jobs,
|
|
190
|
+
typed errors with `requestId` on every failure. A correct integration **degrades gracefully;
|
|
191
|
+
it does not break**.
|
|
192
|
+
- **Their layer** (the upstream platforms): YouTube, TikTok and friends change, throttle and
|
|
193
|
+
geo-block. **No media API on earth can honestly promise zero upstream failures** — and this one
|
|
194
|
+
won't insult you by pretending.
|
|
195
|
+
- **What you get instead**: real data or a typed, honest error. Never fake quality, never silent
|
|
196
|
+
truncation, never a `200` with garbage. `requestedQuality` vs `selectedQuality`, `truncated`,
|
|
197
|
+
`failedItems[]`, `engine.attempts[]` — the truth is in every payload.
|
|
198
|
+
|
|
199
|
+
Build against the contract and your users see graceful, explainable behavior — the thing that
|
|
200
|
+
*makes apps feel reliable*.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
<div align="center">
|
|
205
|
+
|
|
206
|
+
**`umedia` ships the media engine only** — account and key management live in the web platform.
|
|
109
207
|
|
|
110
|
-
|
|
208
|
+
MIT © 2026 · [Unified Media API](https://dev-pappy-api-downloader.duckdns.org)
|
|
111
209
|
|
|
112
|
-
|
|
210
|
+
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "umedia",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Official client for the Unified Media API — search, resolve and download media from YouTube, TikTok, Instagram, Reddit, X, iTunes and 1800+ sites through one REST API. Honest quality labels, multi-item galleries preserved, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
|
-
"umedia": "
|
|
15
|
+
"umedia": "bin/umedia.js"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"index.js",
|