@zfadhli/tokrec 0.11.0 → 0.11.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 +78 -73
- package/dist/package.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
- **Stream reconnection** — TikTok stream segments are short-lived (30-60s). When one ends, `tokrec` transparently fetches a fresh URL and continues writing to the same file
|
|
28
28
|
- **Duration-limited recording** — record for a fixed time (`-d`) and auto-exit, or record indefinitely (default)
|
|
29
29
|
- **Time-aligned MP4 segments** — post-recording, optionally split the stream into configurable-length MP4 segments, each independently playable
|
|
30
|
-
- **Audio normalization** — EBU R128 two-pass loudness normalization via [peaknorm](https://github.com/
|
|
30
|
+
- **Audio normalization** — EBU R128 two-pass loudness normalization via [peaknorm](https://github.com/zfadhli/peaknorm)
|
|
31
31
|
- **Graceful shutdown** — responds to `SIGINT`/`SIGTERM`/`SIGHUP`. Aborts the download, flushes buffered data, converts to MP4, and exits cleanly
|
|
32
32
|
- **Firefox cookie auto-detection** — reads `sessionid_ss` from Firefox's SQLite cookie store automatically (no config file needed)
|
|
33
33
|
- **Live progress display** — animated spinner with real-time byte count, elapsed time, and download speed
|
|
34
34
|
- **Typed event system** — 15 events with typed payloads for lifecycle tracking
|
|
35
35
|
- **Proxy support** — route traffic through an HTTP proxy for regional access
|
|
36
36
|
- **FLV + HLS support** — automatically detects and handles both stream formats
|
|
37
|
-
- **
|
|
37
|
+
- **68+ passing tests** — unit tests with real TikTok HTML/API fixtures
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
@@ -75,11 +75,11 @@ npm install @zfadhli/tokrec
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
```ts
|
|
78
|
-
import { createRecorder } from
|
|
78
|
+
import { createRecorder } from "@zfadhli/tokrec";
|
|
79
79
|
|
|
80
|
-
const recorder = createRecorder({ user:
|
|
81
|
-
recorder.on(
|
|
82
|
-
await recorder.start()
|
|
80
|
+
const recorder = createRecorder({ user: "username" });
|
|
81
|
+
recorder.on("recording:end", (info) => console.log("Done:", info.file));
|
|
82
|
+
await recorder.start();
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
> [!TIP]
|
|
@@ -113,22 +113,22 @@ tokrec -u username --normalize
|
|
|
113
113
|
|
|
114
114
|
## CLI options
|
|
115
115
|
|
|
116
|
-
| Option
|
|
117
|
-
|
|
118
|
-
| `--user <name>`
|
|
119
|
-
| `--output <dir>`
|
|
120
|
-
| `--interval <minutes>`
|
|
121
|
-
| `--duration <minutes>`
|
|
122
|
-
| `--segment-minutes <minutes>` | `-s`
|
|
123
|
-
| `--proxy <url>`
|
|
124
|
-
| `--cookies <path>`
|
|
125
|
-
| `--log-level <level>`
|
|
126
|
-
| `--normalize`
|
|
127
|
-
| `--normalize-loudness <num>`
|
|
128
|
-
| `--normalize-codec <name>`
|
|
129
|
-
| `--normalize-bitrate <str>`
|
|
130
|
-
| `--rate <n>`
|
|
131
|
-
| `--debug`
|
|
116
|
+
| Option | Shorthand | Default | Description |
|
|
117
|
+
| ----------------------------- | --------- | ---------------- | ------------------------------------------------------------------------------------------ |
|
|
118
|
+
| `--user <name>` | `-u` | _(required)_ | TikTok username (with or without `@`) |
|
|
119
|
+
| `--output <dir>` | `-o` | `./recordings` | Output directory |
|
|
120
|
+
| `--interval <minutes>` | `-i` | `3` | Polling interval (min 1) |
|
|
121
|
+
| `--duration <minutes>` | `-d` | unlimited | Max recording duration. Implies one-shot (exit after recording) |
|
|
122
|
+
| `--segment-minutes <minutes>` | `-s` | disabled | Split recording into MP4 segments of this length |
|
|
123
|
+
| `--proxy <url>` | `-p` | none | HTTP proxy URL |
|
|
124
|
+
| `--cookies <path>` | `-c` | `./cookies.json` | Path to cookies JSON file |
|
|
125
|
+
| `--log-level <level>` | `-l` | `info` | One of: `debug`, `info`, `warn`, `error` |
|
|
126
|
+
| `--normalize` | _(none)_ | on | Enable EBU R128 audio normalization (enabled by default, pass `--no-normalize` to disable) |
|
|
127
|
+
| `--normalize-loudness <num>` | _(none)_ | `-14` | Target loudness in LUFS |
|
|
128
|
+
| `--normalize-codec <name>` | _(none)_ | `libopus` | Audio codec for normalized output (peaknorm default) |
|
|
129
|
+
| `--normalize-bitrate <str>` | _(none)_ | `96k` | Audio bitrate for normalized output (peaknorm default) |
|
|
130
|
+
| `--rate <n>` | _(none)_ | `5` | Max API requests/sec (0 = unlimited). Prevents WAF triggering |
|
|
131
|
+
| `--debug` | _(none)_ | off | Show API debug logging on stderr |
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
@@ -144,7 +144,12 @@ If you're logged into TikTok in Firefox, `tokrec` automatically reads the cookie
|
|
|
144
144
|
tokrec -u username
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
You will see:
|
|
147
|
+
You will see:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
tokrec v0.11.0
|
|
151
|
+
ℹ Firefox cookies loaded (30 cookies)
|
|
152
|
+
```
|
|
148
153
|
|
|
149
154
|
### Option 2: cookies.json
|
|
150
155
|
|
|
@@ -231,23 +236,23 @@ File naming: `{username}={YYYYMMDD}_{HHMMSS}[_partN].mp4`
|
|
|
231
236
|
|
|
232
237
|
The recorder emits typed events that you can subscribe to via `recorder.on()`:
|
|
233
238
|
|
|
234
|
-
| Event
|
|
235
|
-
|
|
236
|
-
| `checking`
|
|
237
|
-
| `tick`
|
|
238
|
-
| `recording:start`
|
|
239
|
-
| `download:progress`
|
|
240
|
-
| `download:end`
|
|
241
|
-
| `recording:end`
|
|
242
|
-
| `segmenting:start`
|
|
243
|
-
| `segmenting:end`
|
|
244
|
-
| `converting:start`
|
|
245
|
-
| `converted`
|
|
246
|
-
| `normalize:start`
|
|
247
|
-
| `normalize:progress` | `{ file, percent, phase }`
|
|
248
|
-
| `normalize:end`
|
|
249
|
-
| `normalize:error`
|
|
250
|
-
| `error`
|
|
239
|
+
| Event | Payload | When |
|
|
240
|
+
| -------------------- | --------------------------- | --------------------------------- |
|
|
241
|
+
| `checking` | `{ user }` | Before each poll tick |
|
|
242
|
+
| `tick` | `{ user, isLive, roomId? }` | After checking live status |
|
|
243
|
+
| `recording:start` | `{ user, file }` | When recording begins |
|
|
244
|
+
| `download:progress` | `{ bytes, elapsed, speed }` | During download (~1s intervals) |
|
|
245
|
+
| `download:end` | `{ file, duration, size }` | When download completes |
|
|
246
|
+
| `recording:end` | `{ file, duration, size }` | Per segment/file after conversion |
|
|
247
|
+
| `segmenting:start` | `{ input, outputPattern }` | Before FFmpeg segmenting |
|
|
248
|
+
| `segmenting:end` | `{ segments }` | After segmenting |
|
|
249
|
+
| `converting:start` | `{ input }` | Before simple conversion |
|
|
250
|
+
| `converted` | `{ input, output }` | After each converted file |
|
|
251
|
+
| `normalize:start` | `{ file }` | Before audio normalization |
|
|
252
|
+
| `normalize:progress` | `{ file, percent, phase }` | During normalization |
|
|
253
|
+
| `normalize:end` | `{ input, output }` | After normalization |
|
|
254
|
+
| `normalize:error` | `{ input, error }` | On normalization error |
|
|
255
|
+
| `error` | `err: TikTokError` | On non-fatal errors |
|
|
251
256
|
|
|
252
257
|
---
|
|
253
258
|
|
|
@@ -256,31 +261,31 @@ The recorder emits typed events that you can subscribe to via `recorder.on()`:
|
|
|
256
261
|
### `createRecorder(config)`
|
|
257
262
|
|
|
258
263
|
```ts
|
|
259
|
-
import { createRecorder } from
|
|
260
|
-
import type { RecorderConfig, RecorderController, RecorderEvent } from
|
|
264
|
+
import { createRecorder } from "@zfadhli/tokrec";
|
|
265
|
+
import type { RecorderConfig, RecorderController, RecorderEvent } from "@zfadhli/tokrec";
|
|
261
266
|
|
|
262
267
|
const recorder: RecorderController = createRecorder({
|
|
263
|
-
user:
|
|
264
|
-
outputDir:
|
|
265
|
-
interval: 3,
|
|
266
|
-
duration: 600,
|
|
267
|
-
segmentMinutes: 20,
|
|
268
|
-
proxy:
|
|
269
|
-
cookies: { sessionid_ss:
|
|
270
|
-
logLevel:
|
|
268
|
+
user: "username", // required
|
|
269
|
+
outputDir: "./videos", // default: './recordings'
|
|
270
|
+
interval: 3, // polling interval in minutes
|
|
271
|
+
duration: 600, // max recording duration in seconds
|
|
272
|
+
segmentMinutes: 20, // segment length in minutes
|
|
273
|
+
proxy: "http://127.0.0.1:8080",
|
|
274
|
+
cookies: { sessionid_ss: "..." },
|
|
275
|
+
logLevel: "info",
|
|
271
276
|
logConsole: true,
|
|
272
|
-
})
|
|
277
|
+
});
|
|
273
278
|
```
|
|
274
279
|
|
|
275
280
|
### `RecorderController`
|
|
276
281
|
|
|
277
282
|
```ts
|
|
278
283
|
interface RecorderController {
|
|
279
|
-
start(): Promise<void
|
|
280
|
-
stop(): Promise<void
|
|
281
|
-
getStatus(): RecorderStatus
|
|
282
|
-
on<E extends RecorderEvent>(event: E, handler: RecorderEventHandler[E]): void
|
|
283
|
-
off<E extends RecorderEvent>(event: E, handler: RecorderEventHandler[E]): void
|
|
284
|
+
start(): Promise<void>;
|
|
285
|
+
stop(): Promise<void>;
|
|
286
|
+
getStatus(): RecorderStatus;
|
|
287
|
+
on<E extends RecorderEvent>(event: E, handler: RecorderEventHandler[E]): void;
|
|
288
|
+
off<E extends RecorderEvent>(event: E, handler: RecorderEventHandler[E]): void;
|
|
284
289
|
}
|
|
285
290
|
```
|
|
286
291
|
|
|
@@ -288,35 +293,35 @@ interface RecorderController {
|
|
|
288
293
|
|
|
289
294
|
```ts
|
|
290
295
|
interface RecorderStatus {
|
|
291
|
-
state:
|
|
292
|
-
user: string
|
|
293
|
-
currentFile?: string
|
|
294
|
-
sessionDuration?: number
|
|
295
|
-
lastPollTime?: string
|
|
296
|
-
lastError?: string
|
|
296
|
+
state: "idle" | "polling" | "recording" | "converting" | "stopped";
|
|
297
|
+
user: string;
|
|
298
|
+
currentFile?: string;
|
|
299
|
+
sessionDuration?: number;
|
|
300
|
+
lastPollTime?: string;
|
|
301
|
+
lastError?: string;
|
|
297
302
|
}
|
|
298
303
|
```
|
|
299
304
|
|
|
300
305
|
### Example: build a notification bot
|
|
301
306
|
|
|
302
307
|
```ts
|
|
303
|
-
import { createRecorder } from
|
|
308
|
+
import { createRecorder } from "@zfadhli/tokrec";
|
|
304
309
|
|
|
305
|
-
const recorder = createRecorder({ user:
|
|
310
|
+
const recorder = createRecorder({ user: "username" });
|
|
306
311
|
|
|
307
|
-
recorder.on(
|
|
308
|
-
recorder.on(
|
|
312
|
+
recorder.on("checking", ({ user }) => console.log(`Checking @${user}...`));
|
|
313
|
+
recorder.on("tick", ({ isLive, roomId, user }) => {
|
|
309
314
|
if (isLive) {
|
|
310
|
-
console.log(`@${user} is LIVE! (room: ${roomId})`)
|
|
315
|
+
console.log(`@${user} is LIVE! (room: ${roomId})`);
|
|
311
316
|
// Send a Discord/Telegram notification here
|
|
312
317
|
}
|
|
313
|
-
})
|
|
314
|
-
recorder.on(
|
|
315
|
-
console.log(`Recorded ${file} (${Math.round(duration)}s)`)
|
|
316
|
-
})
|
|
317
|
-
recorder.on(
|
|
318
|
+
});
|
|
319
|
+
recorder.on("recording:end", ({ file, duration }) => {
|
|
320
|
+
console.log(`Recorded ${file} (${Math.round(duration)}s)`);
|
|
321
|
+
});
|
|
322
|
+
recorder.on("error", (err) => console.error(`[${err.kind}] ${err.message}`));
|
|
318
323
|
|
|
319
|
-
await recorder.start()
|
|
324
|
+
await recorder.start();
|
|
320
325
|
```
|
|
321
326
|
|
|
322
327
|
---
|
package/dist/package.mjs
CHANGED