recker 1.0.20-next.de24f7d → 1.0.20
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/dist/core/client.d.ts +0 -2
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +0 -4
- package/dist/plugins/hls.d.ts +17 -90
- package/dist/plugins/hls.d.ts.map +1 -1
- package/dist/plugins/hls.js +173 -343
- package/package.json +1 -1
package/dist/core/client.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { DedupOptions } from '../plugins/dedup.js';
|
|
|
7
7
|
import { ReckerWebSocket, type WebSocketOptions } from '../websocket/client.js';
|
|
8
8
|
import { type WhoisOptions, type WhoisResult } from '../utils/whois.js';
|
|
9
9
|
import { type ScrapePromise } from '../plugins/scrape.js';
|
|
10
|
-
import { HlsPromise, type HlsOptions } from '../plugins/hls.js';
|
|
11
10
|
interface ClientCacheConfig extends Omit<CacheOptions, 'storage'> {
|
|
12
11
|
storage?: CacheStorage;
|
|
13
12
|
driver?: 'memory' | 'file';
|
|
@@ -112,7 +111,6 @@ export declare class Client {
|
|
|
112
111
|
ws(path: string, options?: WebSocketOptions): ReckerWebSocket;
|
|
113
112
|
whois(query: string, options?: WhoisOptions): Promise<WhoisResult>;
|
|
114
113
|
isDomainAvailable(domain: string, options?: WhoisOptions): Promise<boolean>;
|
|
115
|
-
hls(manifestUrl: string, options?: HlsOptions): HlsPromise;
|
|
116
114
|
}
|
|
117
115
|
export declare function createClient(options?: ExtendedClientOptions): Client;
|
|
118
116
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAgB,aAAa,EAAE,cAAc,EAAE,cAAc,EAAa,YAAY,EAAyC,UAAU,EAAmD,MAAM,mBAAmB,CAAC;AAIxP,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAStD,OAAO,EAAY,iBAAiB,EAAe,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAS,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAS,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAS,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAO1D,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAA4C,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElH,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"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"}
|
package/dist/core/client.js
CHANGED
|
@@ -22,7 +22,6 @@ import { ReckerWebSocket } from '../websocket/client.js';
|
|
|
22
22
|
import { whois as performWhois, isDomainAvailable } from '../utils/whois.js';
|
|
23
23
|
import { MemoryCookieJar } from '../cookies/memory-cookie-jar.js';
|
|
24
24
|
import { scrape as scrapeHelper } from '../plugins/scrape.js';
|
|
25
|
-
import { HlsPromise } from '../plugins/hls.js';
|
|
26
25
|
export class Client {
|
|
27
26
|
baseUrl;
|
|
28
27
|
middlewares;
|
|
@@ -658,9 +657,6 @@ export class Client {
|
|
|
658
657
|
async isDomainAvailable(domain, options) {
|
|
659
658
|
return isDomainAvailable(domain, options);
|
|
660
659
|
}
|
|
661
|
-
hls(manifestUrl, options = {}) {
|
|
662
|
-
return new HlsPromise(this, manifestUrl, options);
|
|
663
|
-
}
|
|
664
660
|
}
|
|
665
661
|
export function createClient(options = {}) {
|
|
666
662
|
return new Client(options);
|
package/dist/plugins/hls.d.ts
CHANGED
|
@@ -1,106 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { Writable } from 'node:stream';
|
|
4
|
-
export interface HlsVariant {
|
|
1
|
+
import { Client } from '../core/client.js';
|
|
2
|
+
export interface Variant {
|
|
5
3
|
url: string;
|
|
6
4
|
bandwidth?: number;
|
|
7
5
|
resolution?: string;
|
|
8
|
-
codecs?: string;
|
|
9
|
-
name?: string;
|
|
10
6
|
}
|
|
11
|
-
export interface
|
|
7
|
+
export interface Segment {
|
|
12
8
|
url: string;
|
|
13
9
|
duration: number;
|
|
14
10
|
sequence: number;
|
|
15
|
-
key?:
|
|
16
|
-
discontinuity?: boolean;
|
|
17
|
-
programDateTime?: Date;
|
|
11
|
+
key?: KeyInfo;
|
|
18
12
|
}
|
|
19
|
-
export interface
|
|
20
|
-
method:
|
|
13
|
+
export interface KeyInfo {
|
|
14
|
+
method: string;
|
|
21
15
|
uri?: string;
|
|
22
16
|
iv?: string;
|
|
23
17
|
}
|
|
24
|
-
export interface
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
playlistType?: 'VOD' | 'EVENT';
|
|
30
|
-
discontinuitySequence: number;
|
|
18
|
+
export interface HlsHooks {
|
|
19
|
+
onManifest?: (manifest: string, url: string) => string | void;
|
|
20
|
+
onVariantSelected?: (variants: Variant[], defaultSelected: Variant) => Variant | void;
|
|
21
|
+
onSegment?: (segment: Segment) => Segment | void | null;
|
|
22
|
+
onKey?: (key: KeyInfo) => KeyInfo | void;
|
|
31
23
|
}
|
|
32
|
-
export interface
|
|
33
|
-
variants: HlsVariant[];
|
|
34
|
-
isMaster: true;
|
|
35
|
-
}
|
|
36
|
-
export interface SegmentData {
|
|
37
|
-
sequence: number;
|
|
38
|
-
duration: number;
|
|
39
|
-
data: Uint8Array;
|
|
40
|
-
url: string;
|
|
41
|
-
downloadedAt: Date;
|
|
42
|
-
}
|
|
43
|
-
export interface HlsProgress {
|
|
44
|
-
downloadedSegments: number;
|
|
45
|
-
totalSegments?: number;
|
|
46
|
-
downloadedBytes: number;
|
|
47
|
-
currentSegment: number;
|
|
48
|
-
isLive: boolean;
|
|
49
|
-
elapsed: number;
|
|
50
|
-
}
|
|
51
|
-
export interface HlsOptions {
|
|
52
|
-
mode?: 'merge' | 'chunks';
|
|
53
|
-
format?: 'ts' | 'mp4' | 'mkv';
|
|
54
|
-
live?: boolean | {
|
|
55
|
-
duration: number;
|
|
56
|
-
};
|
|
57
|
-
quality?: 'highest' | 'lowest' | {
|
|
58
|
-
bandwidth?: number;
|
|
59
|
-
resolution?: string;
|
|
60
|
-
};
|
|
24
|
+
export interface HlsOptions extends HlsHooks {
|
|
61
25
|
concurrency?: number;
|
|
62
|
-
|
|
63
|
-
|
|
26
|
+
merge?: boolean;
|
|
27
|
+
live?: boolean;
|
|
28
|
+
duration?: number;
|
|
29
|
+
onInfo?: (message: string) => void;
|
|
64
30
|
onError?: (error: Error) => void;
|
|
65
|
-
headers?: Record<string, string>;
|
|
66
|
-
}
|
|
67
|
-
type DownloadDest = string | ((segment: HlsSegment) => string);
|
|
68
|
-
export declare class HlsPromise implements Promise<void> {
|
|
69
|
-
private client;
|
|
70
|
-
private manifestUrl;
|
|
71
|
-
private options;
|
|
72
|
-
private seenSequences;
|
|
73
|
-
private downloadedBytes;
|
|
74
|
-
private downloadedSegments;
|
|
75
|
-
private startTime;
|
|
76
|
-
private aborted;
|
|
77
|
-
private abortController;
|
|
78
|
-
constructor(client: Client, manifestUrl: string, options?: HlsOptions);
|
|
79
|
-
get [Symbol.toStringTag](): string;
|
|
80
|
-
then<TResult1 = void, TResult2 = never>(onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
81
|
-
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<void | TResult>;
|
|
82
|
-
finally(onfinally?: (() => void) | null): Promise<void>;
|
|
83
|
-
cancel(): void;
|
|
84
|
-
download(dest: DownloadDest): Promise<void>;
|
|
85
|
-
stream(): AsyncGenerator<SegmentData>;
|
|
86
|
-
pipe(writable: Writable | WriteStream): Promise<void>;
|
|
87
|
-
info(): Promise<{
|
|
88
|
-
master?: HlsMasterPlaylist;
|
|
89
|
-
playlist?: HlsPlaylist;
|
|
90
|
-
selectedVariant?: HlsVariant;
|
|
91
|
-
isLive: boolean;
|
|
92
|
-
totalDuration?: number;
|
|
93
|
-
}>;
|
|
94
|
-
private resolveMediaPlaylist;
|
|
95
|
-
private fetchMediaPlaylist;
|
|
96
|
-
private downloadSegment;
|
|
97
|
-
private downloadMerged;
|
|
98
|
-
private downloadChunks;
|
|
99
|
-
private isLiveMode;
|
|
100
|
-
private getMaxDuration;
|
|
101
|
-
private emitProgress;
|
|
102
|
-
private sleep;
|
|
103
31
|
}
|
|
104
|
-
export declare function
|
|
105
|
-
export type { HlsVariant as Variant, HlsSegment as Segment, HlsKeyInfo as KeyInfo, };
|
|
32
|
+
export declare function downloadHls(client: Client, manifestUrl: string, outputPath: string, options?: HlsOptions): Promise<void>;
|
|
106
33
|
//# sourceMappingURL=hls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hls.d.ts","sourceRoot":"","sources":["../../src/plugins/hls.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hls.d.ts","sourceRoot":"","sources":["../../src/plugins/hls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAK3C,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,QAAQ;IAEvB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAE9D,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAAC;IAEtF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC;IAExD,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAmHD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,UAAe,iBA+JzB"}
|
package/dist/plugins/hls.js
CHANGED
|
@@ -1,395 +1,225 @@
|
|
|
1
1
|
import { createWriteStream } from 'node:fs';
|
|
2
|
-
import { mkdir } from 'node:fs/promises';
|
|
3
2
|
import { dirname, join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const content = line.substring(prefix.length);
|
|
7
|
-
const regex = /([A-Z0-9-]+)=(?:"([^"]*)"|([^,]*))/g;
|
|
8
|
-
let match;
|
|
9
|
-
while ((match = regex.exec(content)) !== null) {
|
|
10
|
-
attrs[match[1]] = match[2] ?? match[3];
|
|
11
|
-
}
|
|
12
|
-
return attrs;
|
|
13
|
-
}
|
|
14
|
-
function resolveUrl(url, baseUrl) {
|
|
15
|
-
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
16
|
-
return url;
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
const base = new URL(baseUrl);
|
|
20
|
-
const basePath = base.pathname.substring(0, base.pathname.lastIndexOf('/') + 1);
|
|
21
|
-
return new URL(url, base.origin + basePath).toString();
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return url;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function parseMasterPlaylist(content, baseUrl) {
|
|
28
|
-
const lines = content.split('\n');
|
|
29
|
-
const variants = [];
|
|
30
|
-
let pendingVariant = {};
|
|
31
|
-
for (const rawLine of lines) {
|
|
32
|
-
const line = rawLine.trim();
|
|
33
|
-
if (!line)
|
|
34
|
-
continue;
|
|
35
|
-
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
|
36
|
-
const attrs = parseAttributes(line, '#EXT-X-STREAM-INF:');
|
|
37
|
-
pendingVariant = {
|
|
38
|
-
bandwidth: attrs.BANDWIDTH ? parseInt(attrs.BANDWIDTH, 10) : undefined,
|
|
39
|
-
resolution: attrs.RESOLUTION,
|
|
40
|
-
codecs: attrs.CODECS,
|
|
41
|
-
name: attrs.NAME,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
else if (!line.startsWith('#') && pendingVariant.bandwidth !== undefined) {
|
|
45
|
-
variants.push({
|
|
46
|
-
...pendingVariant,
|
|
47
|
-
url: resolveUrl(line, baseUrl),
|
|
48
|
-
});
|
|
49
|
-
pendingVariant = {};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return { variants, isMaster: true };
|
|
53
|
-
}
|
|
54
|
-
function parseMediaPlaylist(content, baseUrl) {
|
|
3
|
+
import { mkdir, readdir, readFile, rm } from 'node:fs/promises';
|
|
4
|
+
function parseM3u8(content, baseUrl) {
|
|
55
5
|
const lines = content.split('\n');
|
|
56
6
|
const segments = [];
|
|
57
|
-
let
|
|
7
|
+
let currentDuration = 0;
|
|
58
8
|
let mediaSequence = 0;
|
|
59
|
-
let
|
|
9
|
+
let targetDuration = 5;
|
|
60
10
|
let endList = false;
|
|
61
|
-
let playlistType;
|
|
62
|
-
let currentDuration = 0;
|
|
63
11
|
let currentKey;
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
let segmentIndex = 0;
|
|
67
|
-
for (const rawLine of lines) {
|
|
68
|
-
const line = rawLine.trim();
|
|
12
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13
|
+
const line = lines[i].trim();
|
|
69
14
|
if (!line)
|
|
70
15
|
continue;
|
|
71
16
|
if (line.startsWith('#EXT-X-TARGETDURATION:')) {
|
|
72
|
-
targetDuration =
|
|
17
|
+
targetDuration = parseFloat(line.split(':')[1]);
|
|
73
18
|
}
|
|
74
19
|
else if (line.startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
|
75
20
|
mediaSequence = parseInt(line.split(':')[1], 10);
|
|
76
21
|
}
|
|
77
|
-
else if (line.startsWith('#EXT-X-
|
|
78
|
-
discontinuitySequence = parseInt(line.split(':')[1], 10);
|
|
79
|
-
}
|
|
80
|
-
else if (line.startsWith('#EXT-X-PLAYLIST-TYPE:')) {
|
|
81
|
-
playlistType = line.split(':')[1];
|
|
82
|
-
}
|
|
83
|
-
else if (line === '#EXT-X-ENDLIST') {
|
|
22
|
+
else if (line.startsWith('#EXT-X-ENDLIST')) {
|
|
84
23
|
endList = true;
|
|
85
24
|
}
|
|
86
|
-
else if (line === '#EXT-X-DISCONTINUITY') {
|
|
87
|
-
currentDiscontinuity = true;
|
|
88
|
-
}
|
|
89
|
-
else if (line.startsWith('#EXT-X-PROGRAM-DATE-TIME:')) {
|
|
90
|
-
currentProgramDateTime = new Date(line.split(':').slice(1).join(':'));
|
|
91
|
-
}
|
|
92
25
|
else if (line.startsWith('#EXT-X-KEY:')) {
|
|
93
|
-
const attrs =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
26
|
+
const attrs = line.substring(11);
|
|
27
|
+
const methodMatch = attrs.match(/METHOD=([^,]+)/);
|
|
28
|
+
const uriMatch = attrs.match(/URI="([^"]+)"/);
|
|
29
|
+
const ivMatch = attrs.match(/IV=([^,]+)/);
|
|
30
|
+
if (methodMatch) {
|
|
98
31
|
currentKey = {
|
|
99
|
-
method:
|
|
100
|
-
uri:
|
|
101
|
-
iv:
|
|
32
|
+
method: methodMatch[1],
|
|
33
|
+
uri: uriMatch ? uriMatch[1] : undefined,
|
|
34
|
+
iv: ivMatch ? ivMatch[1] : undefined
|
|
102
35
|
};
|
|
103
36
|
}
|
|
104
37
|
}
|
|
105
38
|
else if (line.startsWith('#EXTINF:')) {
|
|
106
|
-
const durationStr = line.substring(8).split(',')[0];
|
|
39
|
+
const durationStr = line.substring(8).split(',', 1)[0];
|
|
107
40
|
currentDuration = parseFloat(durationStr);
|
|
108
41
|
}
|
|
109
42
|
else if (!line.startsWith('#')) {
|
|
43
|
+
let url = line;
|
|
44
|
+
if (!url.startsWith('http')) {
|
|
45
|
+
try {
|
|
46
|
+
const base = new URL(baseUrl);
|
|
47
|
+
const basePath = base.pathname.substring(0, base.pathname.lastIndexOf('/') + 1);
|
|
48
|
+
url = new URL(url, base.origin + basePath).toString();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
110
53
|
segments.push({
|
|
111
|
-
url
|
|
54
|
+
url,
|
|
112
55
|
duration: currentDuration,
|
|
113
|
-
sequence:
|
|
114
|
-
key: currentKey
|
|
115
|
-
discontinuity: currentDiscontinuity,
|
|
116
|
-
programDateTime: currentProgramDateTime,
|
|
56
|
+
sequence: 0,
|
|
57
|
+
key: currentKey
|
|
117
58
|
});
|
|
118
|
-
segmentIndex++;
|
|
119
|
-
currentDiscontinuity = false;
|
|
120
|
-
currentProgramDateTime = undefined;
|
|
121
59
|
}
|
|
122
60
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
endList,
|
|
128
|
-
playlistType,
|
|
129
|
-
discontinuitySequence,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
function isMasterPlaylist(content) {
|
|
133
|
-
return content.includes('#EXT-X-STREAM-INF');
|
|
61
|
+
segments.forEach((seg, idx) => {
|
|
62
|
+
seg.sequence = mediaSequence + idx;
|
|
63
|
+
});
|
|
64
|
+
return { segments, targetDuration, endList, mediaSequence };
|
|
134
65
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
66
|
+
function parseVariants(content, baseUrl) {
|
|
67
|
+
const lines = content.split('\n');
|
|
68
|
+
const variants = [];
|
|
69
|
+
let currentBandwidth;
|
|
70
|
+
let currentResolution;
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i].trim();
|
|
73
|
+
if (!line)
|
|
74
|
+
continue;
|
|
75
|
+
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
|
76
|
+
const bwMatch = line.match(/BANDWIDTH=(\d+)/);
|
|
77
|
+
const resMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
|
78
|
+
if (bwMatch)
|
|
79
|
+
currentBandwidth = parseInt(bwMatch[1], 10);
|
|
80
|
+
if (resMatch)
|
|
81
|
+
currentResolution = resMatch[1];
|
|
82
|
+
}
|
|
83
|
+
else if (!line.startsWith('#')) {
|
|
84
|
+
let url = line;
|
|
85
|
+
if (!url.startsWith('http')) {
|
|
86
|
+
try {
|
|
87
|
+
const base = new URL(baseUrl);
|
|
88
|
+
const basePath = base.pathname.substring(0, base.pathname.lastIndexOf('/') + 1);
|
|
89
|
+
url = new URL(url, base.origin + basePath).toString();
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
}
|
|
93
|
+
variants.push({
|
|
94
|
+
url,
|
|
95
|
+
bandwidth: currentBandwidth,
|
|
96
|
+
resolution: currentResolution
|
|
97
|
+
});
|
|
98
|
+
}
|
|
158
99
|
}
|
|
159
|
-
return
|
|
100
|
+
return variants;
|
|
160
101
|
}
|
|
161
|
-
export
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
options;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
102
|
+
export async function downloadHls(client, manifestUrl, outputPath, options = {}) {
|
|
103
|
+
const merge = options.merge !== false;
|
|
104
|
+
const concurrency = options.concurrency || 5;
|
|
105
|
+
const isLive = options.live === true;
|
|
106
|
+
const maxDuration = options.duration || (isLive ? Infinity : 0);
|
|
107
|
+
const info = options.onInfo || (() => { });
|
|
108
|
+
const error = options.onError || ((err) => { throw err; });
|
|
109
|
+
const seenSegments = new Set();
|
|
110
|
+
const outputDir = merge ? dirname(outputPath) : outputPath;
|
|
111
|
+
await mkdir(outputDir, { recursive: true });
|
|
112
|
+
const tempDir = join(outputDir, `.tmp_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
113
|
+
if (merge) {
|
|
114
|
+
await mkdir(tempDir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
let currentManifestUrl = manifestUrl;
|
|
117
|
+
try {
|
|
118
|
+
let initialManifest = await client.get(currentManifestUrl).text();
|
|
119
|
+
if (options.onManifest) {
|
|
120
|
+
const modified = options.onManifest(initialManifest, currentManifestUrl);
|
|
121
|
+
if (typeof modified === 'string')
|
|
122
|
+
initialManifest = modified;
|
|
123
|
+
}
|
|
124
|
+
if (initialManifest.includes('#EXT-X-STREAM-INF')) {
|
|
125
|
+
const variants = parseVariants(initialManifest, currentManifestUrl);
|
|
126
|
+
if (variants.length > 0) {
|
|
127
|
+
let selected = variants[variants.length - 1];
|
|
128
|
+
if (options.onVariantSelected) {
|
|
129
|
+
const userSelected = options.onVariantSelected(variants, selected);
|
|
130
|
+
if (userSelected)
|
|
131
|
+
selected = userSelected;
|
|
132
|
+
}
|
|
133
|
+
currentManifestUrl = selected.url;
|
|
134
|
+
info(`Master playlist detected. Switching to variant: ${currentManifestUrl}`);
|
|
135
|
+
}
|
|
182
136
|
}
|
|
183
137
|
}
|
|
184
|
-
|
|
185
|
-
|
|
138
|
+
catch (err) {
|
|
139
|
+
error(new Error(`Failed to fetch initial manifest: ${err}`));
|
|
140
|
+
return;
|
|
186
141
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
cancel() {
|
|
201
|
-
this.aborted = true;
|
|
202
|
-
this.abortController.abort();
|
|
203
|
-
}
|
|
204
|
-
async download(dest) {
|
|
205
|
-
this.startTime = Date.now();
|
|
206
|
-
const mediaPlaylistUrl = await this.resolveMediaPlaylist();
|
|
207
|
-
if (this.options.mode === 'chunks') {
|
|
208
|
-
await this.downloadChunks(mediaPlaylistUrl, dest);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
if (typeof dest !== 'string') {
|
|
212
|
-
throw new Error('Merge mode requires a string path, not a function');
|
|
142
|
+
const { RequestRunner } = await import('../runner/request-runner.js');
|
|
143
|
+
const runner = new RequestRunner({ concurrency });
|
|
144
|
+
const startTime = Date.now();
|
|
145
|
+
let recording = true;
|
|
146
|
+
while (recording) {
|
|
147
|
+
try {
|
|
148
|
+
let content = await client.get(currentManifestUrl).text();
|
|
149
|
+
if (options.onManifest) {
|
|
150
|
+
const modified = options.onManifest(content, currentManifestUrl);
|
|
151
|
+
if (typeof modified === 'string')
|
|
152
|
+
content = modified;
|
|
213
153
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.seenSequences.add(segment.sequence);
|
|
232
|
-
const data = await this.downloadSegment(segment);
|
|
233
|
-
const segmentData = {
|
|
234
|
-
sequence: segment.sequence,
|
|
235
|
-
duration: segment.duration,
|
|
236
|
-
data,
|
|
237
|
-
url: segment.url,
|
|
238
|
-
downloadedAt: new Date(),
|
|
239
|
-
};
|
|
240
|
-
this.downloadedSegments++;
|
|
241
|
-
this.downloadedBytes += data.byteLength;
|
|
242
|
-
this.emitProgress(playlist, isLive);
|
|
243
|
-
if (this.options.onSegment) {
|
|
244
|
-
await this.options.onSegment(segmentData);
|
|
154
|
+
const playlist = parseM3u8(content, currentManifestUrl);
|
|
155
|
+
let newSegments = playlist.segments.filter(s => !seenSegments.has(s.url));
|
|
156
|
+
if (options.onSegment) {
|
|
157
|
+
const filtered = [];
|
|
158
|
+
for (const seg of newSegments) {
|
|
159
|
+
if (seg.key && options.onKey) {
|
|
160
|
+
const modifiedKey = options.onKey(seg.key);
|
|
161
|
+
if (modifiedKey)
|
|
162
|
+
seg.key = modifiedKey;
|
|
163
|
+
}
|
|
164
|
+
const res = options.onSegment(seg);
|
|
165
|
+
if (res === null)
|
|
166
|
+
continue;
|
|
167
|
+
if (res)
|
|
168
|
+
filtered.push(res);
|
|
169
|
+
else
|
|
170
|
+
filtered.push(seg);
|
|
245
171
|
}
|
|
246
|
-
|
|
172
|
+
newSegments = filtered;
|
|
173
|
+
}
|
|
174
|
+
if (newSegments.length > 0) {
|
|
175
|
+
info(`Found ${newSegments.length} new segments.`);
|
|
176
|
+
await runner.run(newSegments, async (seg) => {
|
|
177
|
+
seenSegments.add(seg.url);
|
|
178
|
+
const fileDest = merge
|
|
179
|
+
? join(tempDir, `${seg.sequence.toString().padStart(10, '0')}.ts`)
|
|
180
|
+
: join(outputDir, `segment_${seg.sequence}.ts`);
|
|
181
|
+
await client.get(seg.url).write(fileDest);
|
|
182
|
+
});
|
|
247
183
|
}
|
|
248
184
|
if (!isLive || playlist.endList) {
|
|
185
|
+
recording = false;
|
|
249
186
|
break;
|
|
250
187
|
}
|
|
251
|
-
if (maxDuration && Date.now() -
|
|
188
|
+
if (maxDuration > 0 && (Date.now() - startTime) > maxDuration) {
|
|
189
|
+
info('Max duration reached. Stopping.');
|
|
190
|
+
recording = false;
|
|
252
191
|
break;
|
|
253
192
|
}
|
|
254
|
-
const
|
|
255
|
-
await
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (!canContinue) {
|
|
263
|
-
await new Promise((resolve) => writable.once('drain', resolve));
|
|
264
|
-
}
|
|
193
|
+
const waitTime = Math.max(1000, (playlist.targetDuration * 1000) / 2);
|
|
194
|
+
await new Promise(r => setTimeout(r, waitTime));
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
if (isLive) {
|
|
198
|
+
info(`Error fetching live manifest, retrying: ${err.message}`);
|
|
199
|
+
const waitTime = 5000;
|
|
200
|
+
await new Promise(r => setTimeout(r, waitTime));
|
|
265
201
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
writable.end();
|
|
202
|
+
else {
|
|
203
|
+
error(new Error(`HLS download failed: ${err.message}`));
|
|
204
|
+
recording = false;
|
|
270
205
|
}
|
|
271
206
|
}
|
|
272
207
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
: undefined;
|
|
283
|
-
return {
|
|
284
|
-
master,
|
|
285
|
-
playlist,
|
|
286
|
-
selectedVariant,
|
|
287
|
-
isLive: !playlist.endList,
|
|
288
|
-
totalDuration,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
const playlist = parseMediaPlaylist(content, this.manifestUrl);
|
|
292
|
-
const totalDuration = playlist.endList
|
|
293
|
-
? playlist.segments.reduce((sum, s) => sum + s.duration, 0)
|
|
294
|
-
: undefined;
|
|
295
|
-
return {
|
|
296
|
-
playlist,
|
|
297
|
-
isLive: !playlist.endList,
|
|
298
|
-
totalDuration,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
async resolveMediaPlaylist() {
|
|
302
|
-
const content = await this.client.get(this.manifestUrl).text();
|
|
303
|
-
if (!isMasterPlaylist(content)) {
|
|
304
|
-
return this.manifestUrl;
|
|
305
|
-
}
|
|
306
|
-
const master = parseMasterPlaylist(content, this.manifestUrl);
|
|
307
|
-
const variant = selectVariant(master.variants, this.options.quality);
|
|
308
|
-
return variant.url;
|
|
309
|
-
}
|
|
310
|
-
async fetchMediaPlaylist(url) {
|
|
311
|
-
const content = await this.client.get(url).text();
|
|
312
|
-
return parseMediaPlaylist(content, url);
|
|
313
|
-
}
|
|
314
|
-
async downloadSegment(segment) {
|
|
315
|
-
if (segment.key && segment.key.method !== 'NONE') {
|
|
316
|
-
throw new Error(`Encrypted HLS (${segment.key.method}) requires ffmpeg. Use unencrypted streams or install ffmpeg.`);
|
|
317
|
-
}
|
|
318
|
-
const response = await this.client.get(segment.url, {
|
|
319
|
-
headers: this.options.headers,
|
|
320
|
-
signal: this.abortController.signal,
|
|
321
|
-
});
|
|
322
|
-
const blob = await response.blob();
|
|
323
|
-
return new Uint8Array(await blob.arrayBuffer());
|
|
324
|
-
}
|
|
325
|
-
async downloadMerged(playlistUrl, outputPath) {
|
|
326
|
-
await mkdir(dirname(outputPath), { recursive: true });
|
|
327
|
-
const output = createWriteStream(outputPath);
|
|
328
|
-
try {
|
|
329
|
-
await this.pipe(output);
|
|
330
|
-
}
|
|
331
|
-
catch (error) {
|
|
332
|
-
output.destroy();
|
|
333
|
-
throw error;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
async downloadChunks(playlistUrl, dest) {
|
|
337
|
-
const getPath = typeof dest === 'string'
|
|
338
|
-
? (seg) => join(dest, `segment-${seg.sequence}.ts`)
|
|
339
|
-
: dest;
|
|
340
|
-
const baseDir = typeof dest === 'string' ? dest : dirname(getPath({ sequence: 0, duration: 0, url: '' }));
|
|
341
|
-
await mkdir(baseDir, { recursive: true });
|
|
342
|
-
for await (const segment of this.stream()) {
|
|
343
|
-
const filePath = getPath({
|
|
344
|
-
sequence: segment.sequence,
|
|
345
|
-
duration: segment.duration,
|
|
346
|
-
url: segment.url,
|
|
347
|
-
});
|
|
348
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
349
|
-
const output = createWriteStream(filePath);
|
|
350
|
-
await new Promise((resolve, reject) => {
|
|
351
|
-
output.write(segment.data, (err) => {
|
|
352
|
-
if (err)
|
|
353
|
-
reject(err);
|
|
354
|
-
else {
|
|
355
|
-
output.end();
|
|
356
|
-
resolve();
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
});
|
|
208
|
+
if (merge) {
|
|
209
|
+
info('Merging segments...');
|
|
210
|
+
const dest = createWriteStream(outputPath);
|
|
211
|
+
const files = (await readdir(tempDir)).sort();
|
|
212
|
+
for (const file of files) {
|
|
213
|
+
if (!file.endsWith('.ts'))
|
|
214
|
+
continue;
|
|
215
|
+
const chunk = await readFile(join(tempDir, file));
|
|
216
|
+
dest.write(chunk);
|
|
360
217
|
}
|
|
218
|
+
dest.end();
|
|
219
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
220
|
+
info(`Saved to ${outputPath}`);
|
|
361
221
|
}
|
|
362
|
-
|
|
363
|
-
|
|
222
|
+
else {
|
|
223
|
+
info(`Download complete.`);
|
|
364
224
|
}
|
|
365
|
-
getMaxDuration() {
|
|
366
|
-
if (typeof this.options.live === 'object' && this.options.live.duration) {
|
|
367
|
-
return this.options.live.duration;
|
|
368
|
-
}
|
|
369
|
-
return undefined;
|
|
370
|
-
}
|
|
371
|
-
emitProgress(playlist, isLive) {
|
|
372
|
-
if (!this.options.onProgress)
|
|
373
|
-
return;
|
|
374
|
-
this.options.onProgress({
|
|
375
|
-
downloadedSegments: this.downloadedSegments,
|
|
376
|
-
totalSegments: isLive ? undefined : playlist.segments.length,
|
|
377
|
-
downloadedBytes: this.downloadedBytes,
|
|
378
|
-
currentSegment: Math.max(...this.seenSequences),
|
|
379
|
-
isLive,
|
|
380
|
-
elapsed: Date.now() - this.startTime,
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
sleep(ms) {
|
|
384
|
-
return new Promise((resolve) => {
|
|
385
|
-
const timeout = setTimeout(resolve, ms);
|
|
386
|
-
this.abortController.signal.addEventListener('abort', () => {
|
|
387
|
-
clearTimeout(timeout);
|
|
388
|
-
resolve();
|
|
389
|
-
}, { once: true });
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
export function hls(client, manifestUrl, options = {}) {
|
|
394
|
-
return new HlsPromise(client, manifestUrl, options);
|
|
395
225
|
}
|