@lzwme/m3u8-dl 1.2.0 → 1.2.2

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 (38) hide show
  1. package/README.MD +1 -0
  2. package/cjs/cli.js +16 -15
  3. package/cjs/lib/file-download.d.ts +1 -1
  4. package/cjs/lib/file-download.js +11 -9
  5. package/cjs/lib/format-options.d.ts +1 -1
  6. package/cjs/lib/format-options.js +3 -3
  7. package/cjs/lib/local-play.js +11 -11
  8. package/cjs/lib/m3u8-convert.js +1 -1
  9. package/cjs/lib/m3u8-download.js +29 -26
  10. package/cjs/lib/parseM3u8.d.ts +1 -1
  11. package/cjs/lib/parseM3u8.js +37 -28
  12. package/cjs/lib/search-api/CommSearchApi.d.ts +1 -1
  13. package/cjs/lib/search-api/CommSearchApi.js +2 -2
  14. package/cjs/lib/storage.js +2 -2
  15. package/cjs/lib/ts-download.d.ts +1 -1
  16. package/cjs/lib/ts-download.js +3 -2
  17. package/cjs/lib/utils.d.ts +1 -1
  18. package/cjs/lib/utils.js +4 -4
  19. package/cjs/lib/video-search.js +56 -56
  20. package/cjs/lib/worker_pool.js +3 -3
  21. package/cjs/m3u8-batch-download.d.ts +1 -1
  22. package/cjs/m3u8-batch-download.js +11 -13
  23. package/cjs/server/download-server.d.ts +6 -3
  24. package/cjs/server/download-server.js +67 -35
  25. package/cjs/types/index.js +0 -2
  26. package/cjs/types/m3u8.d.ts +6 -2
  27. package/cjs/types/video-search.d.ts +1 -1
  28. package/cjs/video-parser/index.js +5 -5
  29. package/cjs/video-parser/parsers/base-parser.js +1 -1
  30. package/cjs/video-parser/parsers/douyin-parser.d.ts +1 -1
  31. package/cjs/video-parser/parsers/douyin-parser.js +8 -8
  32. package/cjs/video-parser/parsers/pipixia-parser.d.ts +1 -1
  33. package/cjs/video-parser/parsers/pipixia-parser.js +7 -7
  34. package/cjs/video-parser/parsers/weibo-parser.d.ts +1 -1
  35. package/cjs/video-parser/parsers/weibo-parser.js +12 -12
  36. package/client/index.html +417 -258
  37. package/package.json +14 -12
  38. package/global.d.ts +0 -1
package/README.MD CHANGED
@@ -152,6 +152,7 @@ services:
152
152
  DS_PORT: '6600'
153
153
  DS_SAVE_DIR: '/app/downloads'
154
154
  DS_CACHE_DIR: '/app/cache'
155
+ DS_SECRET: '' # 设置访问密码
155
156
  DS_DEBUG: ''
156
157
  # command: >
157
158
  # sh -c "node cjs/server/index.js"
package/cjs/cli.js CHANGED
@@ -34,12 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  const node_path_1 = require("node:path");
37
+ const fe_utils_1 = require("@lzwme/fe-utils");
37
38
  const commander_1 = require("commander");
38
39
  const console_log_colors_1 = require("console-log-colors");
39
- const fe_utils_1 = require("@lzwme/fe-utils");
40
40
  const utils_js_1 = require("./lib/utils.js");
41
- const m3u8_batch_download_1 = require("./m3u8-batch-download");
42
41
  const video_search_js_1 = require("./lib/video-search.js");
42
+ const m3u8_batch_download_1 = require("./m3u8-batch-download");
43
43
  const pkg = (0, fe_utils_1.readJsonFileSync)((0, node_path_1.resolve)(__dirname, '../package.json'));
44
44
  process.on('unhandledRejection', r => {
45
45
  console.error(r);
@@ -54,19 +54,19 @@ commander_1.program
54
54
  .version(pkg.version, '-v, --version')
55
55
  .description((0, console_log_colors_1.cyanBright)(pkg.description))
56
56
  .argument('<m3u8Urls...>', 'm3u8 url。也可以是本地 txt 文件,指定一组 m3u8,适用于批量下载的场景')
57
- .option('--silent', `开启静默模式。`)
58
- .option('--debug', `开启调试模式。`)
59
- .option('-f, --filename <name>', `指定下载文件的保存名称。默认取 url md5 值。若指定了多个 url 地址,则会在末尾增加序号`)
60
- .option('-n, --thread-num <number>', `并发下载线程数。默认为 cpu * 2。可设置不同数值观察下载效果`)
61
- .option('-F, --force', `启用强制执行模式。文件已存在时,是否仍继续下载和生成`)
62
- .option('--no-progress', `是否不打印进度信息`)
63
- .option('-p, --play', `是否边下边看`)
64
- .option('-C, --cache-dir <dirpath>', `临时文件保存目录。默认为 cache`)
65
- .option('-S, --save-dir <dirpath>', `下载文件保存的路径。默认为当前目录`)
66
- .option('--no-del-cache', `下载成功后是否删除临时文件。默认为 true。保存临时文件可以在重复下载时识别缓存`)
57
+ .option('--silent', '开启静默模式。')
58
+ .option('--debug', '开启调试模式。')
59
+ .option('-f, --filename <name>', '指定下载文件的保存名称。默认取 url md5 值。若指定了多个 url 地址,则会在末尾增加序号')
60
+ .option('-n, --thread-num <number>', '并发下载线程数。默认为 cpu * 2。可设置不同数值观察下载效果')
61
+ .option('-F, --force', '启用强制执行模式。文件已存在时,是否仍继续下载和生成')
62
+ .option('--no-progress', '是否不打印进度信息')
63
+ .option('-p, --play', '是否边下边看')
64
+ .option('-C, --cache-dir <dirpath>', '临时文件保存目录。默认为 cache')
65
+ .option('-S, --save-dir <dirpath>', '下载文件保存的路径。默认为当前目录')
66
+ .option('--no-del-cache', '下载成功后是否删除临时文件。默认为 true。保存临时文件可以在重复下载时识别缓存')
67
67
  .option('--no-convert', '下载成功后,是否不合并转换为 mp4 文件。默认为 true。')
68
- .option('-H, --headers <headers>', `自定义请求头。格式为 key1=value1\nkey2=value2`)
69
- .option('-T, --type <type>', `指定下载类型。默认根据URL自动识别,如果是批量下载多个不同 URL 类型,请不要设置。可选值:m3u8, file, parser`)
68
+ .option('-H, --headers <headers>', '自定义请求头。格式为 key1=value1\nkey2=value2')
69
+ .option('-T, --type <type>', '指定下载类型。默认根据URL自动识别,如果是批量下载多个不同 URL 类型,请不要设置。可选值:m3u8, file, parser')
70
70
  .action(async (urls) => {
71
71
  const options = getOptions();
72
72
  utils_js_1.logger.debug(urls, options);
@@ -83,11 +83,12 @@ commander_1.program
83
83
  .command('server')
84
84
  .description('启动下载中心web服务')
85
85
  .option('-P, --port <port>', '指定web服务端口。默认为6600')
86
- .option('--token <token>', '指定web服务密码(请求头authorization)。默认为空')
86
+ .option('-t, --token <token>', '指定web服务密码(请求头authorization)。默认为空')
87
87
  .action((options) => {
88
88
  const opts = getOptions();
89
89
  if (opts.debug)
90
90
  options.debug = true;
91
+ console.log(opts, options);
91
92
  Promise.resolve().then(() => __importStar(require('./server/download-server.js'))).then(m => {
92
93
  new m.DLServer(options);
93
94
  });
@@ -1,2 +1,2 @@
1
1
  import type { M3u8DLOptions, M3u8DLResult } from '../types';
2
- export declare function fileDownload(url: string, options: M3u8DLOptions): Promise<M3u8DLResult>;
2
+ export declare function fileDownload(u: string, opts: M3u8DLOptions): Promise<M3u8DLResult>;
@@ -4,11 +4,11 @@ exports.fileDownload = fileDownload;
4
4
  const node_path_1 = require("node:path");
5
5
  const fe_utils_1 = require("@lzwme/fe-utils");
6
6
  const console_log_colors_1 = require("console-log-colors");
7
- const utils_js_1 = require("./utils.js");
8
7
  const format_options_js_1 = require("./format-options.js");
9
- async function fileDownload(url, options) {
10
- utils_js_1.logger.debug('fileDownload', url, options);
11
- [url, options] = (0, format_options_js_1.formatOptions)(url, options);
8
+ const utils_js_1 = require("./utils.js");
9
+ async function fileDownload(u, opts) {
10
+ utils_js_1.logger.debug('fileDownload', u, opts);
11
+ const [url, options] = (0, format_options_js_1.formatOptions)(u, opts);
12
12
  const startTime = Date.now();
13
13
  const stats = {
14
14
  url,
@@ -49,13 +49,15 @@ async function fileDownload(url, options) {
49
49
  stats.size = info.size;
50
50
  stats.downloadedSize = info.downloaded;
51
51
  stats.speed = info.speed;
52
- stats.speedDesc = (0, fe_utils_1.formatByteSize)(stats.speed) + '/s';
52
+ stats.speedDesc = `${(0, fe_utils_1.formatByteSize)(stats.speed)}/s`;
53
53
  stats.remainingTime = Math.round((info.size - info.downloaded) / stats.speed);
54
54
  if (options.showProgress) {
55
55
  const processBar = info.percent === -1 ? '' : '='.repeat(Math.floor(stats.progress * 0.2)).padEnd(20, '-');
56
- utils_js_1.logger.logInline(`${stats.progress}% [${(0, console_log_colors_1.greenBright)(processBar)}] ` +
57
- `${(0, console_log_colors_1.blueBright)((0, fe_utils_1.formatByteSize)(stats.downloadedSize))} ${(0, console_log_colors_1.yellowBright)((0, fe_utils_1.formatTimeCost)(startTime))} ${(0, console_log_colors_1.magentaBright)(stats.speedDesc)} ` +
58
- (stats.progress === 100 ? '\n' : stats.remainingTime ? `${(0, console_log_colors_1.cyan)((0, fe_utils_1.formatTimeCost)(Date.now() - stats.remainingTime))}` : ''));
56
+ utils_js_1.logger.logInline([
57
+ `${stats.progress}% [${(0, console_log_colors_1.greenBright)(processBar)}] `,
58
+ `${(0, console_log_colors_1.blueBright)((0, fe_utils_1.formatByteSize)(stats.downloadedSize))} ${(0, console_log_colors_1.yellowBright)((0, fe_utils_1.formatTimeCost)(startTime))} ${(0, console_log_colors_1.magentaBright)(stats.speedDesc)} `,
59
+ stats.progress === 100 ? '\n' : stats.remainingTime ? `${(0, console_log_colors_1.cyan)((0, fe_utils_1.formatTimeCost)(Date.now() - stats.remainingTime))}` : '',
60
+ ].join(''));
59
61
  }
60
62
  if (options.onProgress) {
61
63
  return options.onProgress(info.downloaded, info.size, null, stats);
@@ -74,7 +76,7 @@ async function fileDownload(url, options) {
74
76
  stats.errmsg = error.message;
75
77
  return {
76
78
  isExist: false,
77
- errmsg: '下载失败: ' + error.message,
79
+ errmsg: `下载失败: ${error.message}`,
78
80
  stats,
79
81
  };
80
82
  }
@@ -1,2 +1,2 @@
1
- import { M3u8DLOptions } from '../types';
1
+ import type { M3u8DLOptions } from '../types';
2
2
  export declare function formatOptions(url: string, opts: M3u8DLOptions): readonly [string, M3u8DLOptions, string];
@@ -4,8 +4,8 @@ exports.formatOptions = formatOptions;
4
4
  const node_os_1 = require("node:os");
5
5
  const node_path_1 = require("node:path");
6
6
  const fe_utils_1 = require("@lzwme/fe-utils");
7
- const utils_1 = require("./utils");
8
7
  const video_parser_1 = require("../video-parser");
8
+ const utils_1 = require("./utils");
9
9
  const fileSupportExtList = [
10
10
  '.mp4',
11
11
  '.mkv',
@@ -48,7 +48,7 @@ function formatOptions(url, opts) {
48
48
  }
49
49
  }
50
50
  let [u, n] = url.split(/[|$]+/);
51
- if (n && n.startsWith('http'))
51
+ if (n?.startsWith('http'))
52
52
  [u, n] = [n, u];
53
53
  url = u;
54
54
  if (n) {
@@ -67,7 +67,7 @@ function formatOptions(url, opts) {
67
67
  if (!(0, node_path_1.extname)(options.filename) && options.type !== 'file')
68
68
  options.filename += ext || '.mp4';
69
69
  if (!options.cacheDir)
70
- options.cacheDir = `cache`;
70
+ options.cacheDir = 'cache';
71
71
  if (options.headers)
72
72
  options.headers = (0, utils_1.formatHeaders)(options.headers);
73
73
  if (!options.threadNum || +options.threadNum <= 0)
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.localPlay = localPlay;
4
4
  exports.toLocalM3u8 = toLocalM3u8;
5
- const fe_utils_1 = require("@lzwme/fe-utils");
6
- const console_log_colors_1 = require("console-log-colors");
7
5
  const node_fs_1 = require("node:fs");
8
6
  const node_http_1 = require("node:http");
9
7
  const node_path_1 = require("node:path");
8
+ const fe_utils_1 = require("@lzwme/fe-utils");
9
+ const console_log_colors_1 = require("console-log-colors");
10
10
  const utils_js_1 = require("./utils.js");
11
11
  /**
12
12
  * 边下边看
@@ -32,24 +32,24 @@ function toLocalM3u8(m3u8Info, m3u8FilePath = '', host = '') {
32
32
  if ((0, node_fs_1.existsSync)(m3u8FilePath))
33
33
  return m3u8FilePath;
34
34
  const m3u8ContentList = [
35
- `#EXTM3U`,
36
- `#EXT-X-VERSION:3`,
37
- `#EXT-X-ALLOW-CACHE:YES`,
35
+ '#EXTM3U',
36
+ '#EXT-X-VERSION:3',
37
+ '#EXT-X-ALLOW-CACHE:YES',
38
38
  `#EXT-X-TARGETDURATION:${Math.max(...m3u8Info.map(d => d.duration))}`,
39
- `#EXT-X-MEDIA-SEQUENCE:0`,
39
+ '#EXT-X-MEDIA-SEQUENCE:0',
40
40
  // `#EXT-X-KEY:METHOD=AES-128,URI="/api/aes/enc.key"`,
41
41
  ];
42
42
  if (host && !host.endsWith('/'))
43
43
  host += '/';
44
- m3u8Info.forEach(d => {
44
+ for (const d of m3u8Info) {
45
45
  if (d.tsOut)
46
46
  m3u8ContentList.push(`#EXTINF:${Number(d.duration).toFixed(6)},`, `${host}${(0, node_path_1.basename)(d.tsOut)}`);
47
- });
48
- m3u8ContentList.push(`#EXT-X-ENDLIST`);
47
+ }
48
+ m3u8ContentList.push('#EXT-X-ENDLIST');
49
49
  const m3u8Content = m3u8ContentList.join('\n');
50
50
  const ext = (0, node_path_1.extname)(m3u8FilePath);
51
51
  if (ext !== '.m3u8')
52
- m3u8FilePath = m3u8FilePath.replace(ext, '') + '.m3u8';
52
+ m3u8FilePath = `${m3u8FilePath.replace(ext, '')}.m3u8`;
53
53
  m3u8FilePath = (0, node_path_1.resolve)(cacheDir, m3u8FilePath);
54
54
  (0, fe_utils_1.mkdirp)(cacheDir);
55
55
  (0, node_fs_1.writeFileSync)(m3u8FilePath, m3u8Content, 'utf8');
@@ -79,7 +79,7 @@ async function createLocalServer(baseDir) {
79
79
  (0, node_fs_1.createReadStream)(filename).pipe(res);
80
80
  return;
81
81
  }
82
- else if (stats.isDirectory()) {
82
+ if (stats.isDirectory()) {
83
83
  const html = (0, node_fs_1.readdirSync)(filename).map(fname => {
84
84
  const rpath = (0, node_path_1.resolve)(filename, fname).replace(baseDir, '');
85
85
  return `<li><a href="${rpath}">${rpath}</a></li>`;
@@ -20,7 +20,7 @@ async function m3u8Convert(options, data) {
20
20
  let filesAllArr = data.filter(d => (0, node_fs_1.existsSync)(d.tsOut)).map(d => `file '${d.tsOut}'\nduration ${d.duration}`);
21
21
  if (process.platform === 'win32')
22
22
  filesAllArr = filesAllArr.map(d => d.replaceAll('\\', '/'));
23
- (0, node_fs_1.writeFileSync)(ffconcatFile, 'ffconcat version 1.0\n' + filesAllArr.join('\n'));
23
+ (0, node_fs_1.writeFileSync)(ffconcatFile, `ffconcat version 1.0\n${filesAllArr.join('\n')}`);
24
24
  let headersString = '';
25
25
  if (options.headers) {
26
26
  for (const [key, value] of Object.entries(options.headers)) {
@@ -4,17 +4,17 @@ exports.workPollPublic = exports.downloadQueue = exports.DownloadQueue = void 0;
4
4
  exports.preDownLoad = preDownLoad;
5
5
  exports.m3u8Download = m3u8Download;
6
6
  exports.m3u8DLStop = m3u8DLStop;
7
- const node_path_1 = require("node:path");
8
7
  const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
9
  const fe_utils_1 = require("@lzwme/fe-utils");
10
10
  const helper_1 = require("@lzwme/fe-utils/cjs/common/helper");
11
11
  const console_log_colors_1 = require("console-log-colors");
12
- const utils_js_1 = require("./utils.js");
13
12
  const format_options_js_1 = require("./format-options.js");
14
- const worker_pool_js_1 = require("./worker_pool.js");
15
- const parseM3u8_js_1 = require("./parseM3u8.js");
16
- const m3u8_convert_js_1 = require("./m3u8-convert.js");
17
13
  const local_play_js_1 = require("./local-play.js");
14
+ const m3u8_convert_js_1 = require("./m3u8-convert.js");
15
+ const parseM3u8_js_1 = require("./parseM3u8.js");
16
+ const utils_js_1 = require("./utils.js");
17
+ const worker_pool_js_1 = require("./worker_pool.js");
18
18
  /** 下载队列管理 */
19
19
  class DownloadQueue {
20
20
  queue = [];
@@ -101,7 +101,10 @@ async function m3u8InfoParse(url, options = {}) {
101
101
  }
102
102
  if (!options.force && (0, node_fs_1.existsSync)(filepath))
103
103
  return result;
104
- const m3u8Info = await (0, parseM3u8_js_1.parseM3U8)(url, (0, node_path_1.resolve)(options.cacheDir, urlMd5), options.headers).catch(e => utils_js_1.logger.error('[parseM3U8][failed]', e));
104
+ const m3u8Info = await (0, parseM3u8_js_1.parseM3U8)(url, (0, node_path_1.resolve)(options.cacheDir, urlMd5), options.headers).catch(e => {
105
+ utils_js_1.logger.error('[parseM3U8][failed]', e.message);
106
+ console.log(e);
107
+ });
105
108
  if (m3u8Info && m3u8Info?.tsCount > 0)
106
109
  result.m3u8Info = m3u8Info;
107
110
  return result;
@@ -132,7 +135,7 @@ async function preDownLoad(url, options, wp = exports.workPollPublic) {
132
135
  return;
133
136
  if (!cache.downloading.has(info.uri)) {
134
137
  cache.downloading.add(info.uri);
135
- wp.runTask({ url, info, options: JSON.parse(JSON.stringify(result.options)), crypto: result.m3u8Info.crypto }, () => {
138
+ wp.runTask({ url, info, options: JSON.parse(JSON.stringify(result.options)), crypto: result.m3u8Info.crypto[info.keyUri] }, () => {
136
139
  cache.downloading.delete(info.uri);
137
140
  });
138
141
  }
@@ -196,7 +199,7 @@ async function m3u8Download(url, options = {}) {
196
199
  const runTask = (data) => {
197
200
  for (const info of data) {
198
201
  const o = JSON.parse(JSON.stringify(options));
199
- workPoll.runTask({ url, info, options: o, crypto: m3u8Info.crypto }, (err, res, taskStartTime) => {
202
+ workPoll.runTask({ url, info, options: o, crypto: m3u8Info.crypto[info.keyUri] }, (err, res, taskStartTime) => {
200
203
  stats.errmsg = err ? err.cause || err.message || err.toString() : '';
201
204
  if (!res || err) {
202
205
  if (err) {
@@ -230,28 +233,26 @@ async function m3u8Download(url, options = {}) {
230
233
  const downloadedDuration = m3u8Info.data.reduce((a, b) => a + (b.tsSize ? b.duration : 0), 0);
231
234
  stats.downloadedSize = m3u8Info.data.reduce((a, b) => a + (b.tsSize || 0), 0);
232
235
  stats.avgSpeed = (stats.downloadedSize / timeCost) * 1000;
233
- stats.avgSpeedDesc = (0, helper_1.formatByteSize)(stats.avgSpeed) + '/s';
236
+ stats.avgSpeedDesc = `${(0, helper_1.formatByteSize)(stats.avgSpeed)}/s`;
234
237
  // 如果当前速度小于平均速度,则更新为平均速度
235
238
  if (stats.speed < stats.avgSpeed)
236
239
  stats.speed = stats.avgSpeed;
237
- stats.speedDesc = (0, helper_1.formatByteSize)(stats.speed) + '/s';
240
+ stats.speedDesc = `${(0, helper_1.formatByteSize)(stats.speed)}/s`;
238
241
  stats.progress = Math.floor((finished / m3u8Info.tsCount) * 100);
239
242
  if (downloadedDuration) {
240
243
  stats.remainingTime = (timeCost / downloadedDuration) * (m3u8Info.duration - stats.durationDownloaded);
241
244
  if (stats.speed > stats.avgSpeed)
242
245
  stats.remainingTime = stats.remainingTime * (stats.avgSpeed / stats.speed);
243
246
  stats.remainingTime = Math.ceil(stats.remainingTime);
244
- stats.size = stats.downloadedSize * (stats.duration / stats.durationDownloaded);
247
+ stats.size = Math.round(stats.downloadedSize * (stats.duration / stats.durationDownloaded));
245
248
  }
246
249
  if (options.showProgress) {
247
250
  const processBar = '='.repeat(Math.floor(stats.progress * 0.2)).padEnd(20, '-');
248
- utils_js_1.logger.logInline(`${stats.progress}% [${(0, console_log_colors_1.greenBright)(processBar)}] ${(0, console_log_colors_1.cyan)(finished)} ${(0, console_log_colors_1.green)(stats.durationDownloaded.toFixed(2) + 'sec')} ` +
249
- `${(0, console_log_colors_1.blueBright)((0, helper_1.formatByteSize)(stats.downloadedSize))} ${(0, console_log_colors_1.yellowBright)((0, fe_utils_1.formatTimeCost)(startTime))} ${(0, console_log_colors_1.magentaBright)(stats.speedDesc)} ` +
250
- (finished === m3u8Info.tsCount
251
- ? '\n'
252
- : stats.remainingTime
253
- ? `${(0, console_log_colors_1.cyan)((0, fe_utils_1.formatTimeCost)(Date.now() - stats.remainingTime))}`
254
- : ''));
251
+ utils_js_1.logger.logInline(`${stats.progress}% [${(0, console_log_colors_1.greenBright)(processBar)}] ${(0, console_log_colors_1.cyan)(finished)} ${(0, console_log_colors_1.green)(`${stats.durationDownloaded.toFixed(2)}sec`)} ${(0, console_log_colors_1.blueBright)((0, helper_1.formatByteSize)(stats.downloadedSize))} ${(0, console_log_colors_1.yellowBright)((0, fe_utils_1.formatTimeCost)(startTime))} ${(0, console_log_colors_1.magentaBright)(stats.speedDesc)} ${finished === m3u8Info.tsCount
252
+ ? '\n'
253
+ : stats.remainingTime
254
+ ? `${(0, console_log_colors_1.cyan)((0, fe_utils_1.formatTimeCost)(Date.now() - stats.remainingTime))}`
255
+ : ''}`);
255
256
  }
256
257
  if (options.onProgress)
257
258
  options.onProgress(finished, m3u8Info.tsCount, info, stats);
@@ -265,23 +266,25 @@ async function m3u8Download(url, options = {}) {
265
266
  }
266
267
  };
267
268
  if (options.showProgress) {
268
- console.info(`\nTotal segments: ${(0, console_log_colors_1.cyan)(m3u8Info.tsCount)}, duration: ${(0, console_log_colors_1.green)(m3u8Info.duration + 'sec')}.`, `Parallel jobs: ${(0, console_log_colors_1.magenta)(options.threadNum)}`);
269
+ console.info(`\nTotal segments: ${(0, console_log_colors_1.cyan)(m3u8Info.tsCount)}, duration: ${(0, console_log_colors_1.green)(`${m3u8Info.duration}sec`)}.`, `Parallel jobs: ${(0, console_log_colors_1.magenta)(options.threadNum)}`);
269
270
  }
270
271
  result.stats = stats;
271
- (0, local_play_js_1.toLocalM3u8)(m3u8Info.data, options.filename);
272
+ (0, local_play_js_1.toLocalM3u8)(m3u8Info.data);
272
273
  if (options.onInited)
273
274
  options.onInited(stats, m3u8Info, workPoll);
274
275
  runTask(m3u8Info.data);
275
276
  await barrier.wait();
276
- if (stats.tsFailed === 0) {
277
- if (options.convert !== false) {
278
- result.filepath = await (0, m3u8_convert_js_1.m3u8Convert)(options, m3u8Info.data);
279
- if (options.delCache && result.filepath && (0, node_fs_1.existsSync)(result.filepath))
277
+ if (stats.tsFailed > 0) {
278
+ utils_js_1.logger.warn('Download Failed! Please retry!', stats.tsFailed);
279
+ }
280
+ else if (options.convert !== false) {
281
+ result.filepath = await (0, m3u8_convert_js_1.m3u8Convert)(options, m3u8Info.data);
282
+ if (result.filepath && (0, node_fs_1.existsSync)(result.filepath)) {
283
+ stats.size = (0, node_fs_1.statSync)(result.filepath).size;
284
+ if (options.delCache)
280
285
  (0, fe_utils_1.rmrfAsync)((0, node_path_1.dirname)(m3u8Info.data[0].tsOut));
281
286
  }
282
287
  }
283
- else
284
- utils_js_1.logger.warn('Download Failed! Please retry!', stats.tsFailed);
285
288
  }
286
289
  utils_js_1.logger.debug('Done!', url, result.m3u8Info);
287
290
  return result;
@@ -1,5 +1,5 @@
1
+ import type { IncomingHttpHeaders } from 'node:http';
1
2
  import type { M3u8Info } from '../types/m3u8';
2
- import { IncomingHttpHeaders } from 'node:http';
3
3
  /**
4
4
  * 解析 m3u8 文件
5
5
  * @param content m3u8 文件的内容,可为 http 远程地址、本地文件路径
@@ -30,7 +30,14 @@ async function parseM3U8(content, cacheDir = './cache', headers) {
30
30
  parser.end();
31
31
  utils_1.logger.debug('parser.manifest', parser.manifest);
32
32
  if (parser.manifest.playlists?.length > 0) {
33
- url = new URL(parser.manifest.playlists[0].uri, url).toString();
33
+ let maxBandwidthItem = parser.manifest.playlists[0];
34
+ for (const item of parser.manifest.playlists) {
35
+ if (!maxBandwidthItem || (item.attributes?.BANDWIDTH || 0) > (maxBandwidthItem.attributes?.BANDWIDTH || 0)) {
36
+ maxBandwidthItem = item;
37
+ }
38
+ }
39
+ url = new URL(maxBandwidthItem.uri, url).toString();
40
+ utils_1.logger.debug('maxBandwidthItem', maxBandwidthItem, url);
34
41
  content = (await (0, utils_1.getRetry)(url, headers)).data;
35
42
  parser = new m3u8_parser_1.Parser();
36
43
  parser.push(content);
@@ -45,42 +52,44 @@ async function parseM3U8(content, cacheDir = './cache', headers) {
45
52
  duration: 0,
46
53
  data: [],
47
54
  /** 加密相关信息 */
48
- crypto: {
49
- method: 'AES-128',
50
- iv: new Uint8Array(16),
51
- key: '',
52
- uri: '',
53
- },
55
+ crypto: {},
54
56
  };
55
57
  if (!result.tsCount) {
56
58
  utils_1.logger.error('m3u8 file error!\n', url, content);
57
59
  return result;
58
60
  }
59
- const tsKeyInfo = tsList[0].key;
60
- if (tsKeyInfo?.uri) {
61
- if (tsKeyInfo.method)
62
- result.crypto.method = tsKeyInfo.method.toUpperCase();
63
- if (tsKeyInfo.iv) {
64
- result.crypto.iv = typeof tsKeyInfo.iv === 'string' ? new Uint8Array(Buffer.from(tsKeyInfo.iv)) : tsKeyInfo.iv;
61
+ for (const [i, item] of tsList.entries()) {
62
+ if (!item.uri.includes('://'))
63
+ item.uri = new URL(item.uri, url).toString();
64
+ if (item.key) {
65
+ const tsKeyInfo = item.key;
66
+ if (!tsKeyInfo.uri.includes('://'))
67
+ tsKeyInfo.uri = new URL(tsKeyInfo.uri, url).toString();
68
+ if (tsKeyInfo?.uri && !result.crypto[tsKeyInfo.uri]) {
69
+ const r = await (0, utils_1.getRetry)(tsKeyInfo.uri);
70
+ if (r.response.statusCode !== 200) {
71
+ utils_1.logger.error('获取加密 key 失败:', tsKeyInfo.uri, r.response.statusCode, r.data);
72
+ }
73
+ else {
74
+ result.crypto[tsKeyInfo.uri] = {
75
+ uri: tsKeyInfo.uri,
76
+ method: tsKeyInfo.method.toUpperCase() || 'AES-128',
77
+ iv: typeof tsKeyInfo.iv === 'string' ? new Uint8Array(Buffer.from(tsKeyInfo.iv)) : tsKeyInfo.iv,
78
+ key: r.buffer,
79
+ };
80
+ }
81
+ }
65
82
  }
66
- result.crypto.uri = tsKeyInfo.uri.includes('://') ? tsKeyInfo.uri : new URL(tsKeyInfo.uri, url).toString();
67
- }
68
- if (result.crypto.uri !== '') {
69
- const r = await (0, utils_1.getRetry)(result.crypto.uri);
70
- result.crypto.key = r.buffer;
71
- }
72
- for (let i = 0; i < result.tsCount; i++) {
73
- if (!tsList[i].uri.startsWith('http'))
74
- tsList[i].uri = new URL(tsList[i].uri, url).toString();
75
83
  result.data.push({
76
- m3u8: url,
77
84
  index: i,
78
- duration: tsList[i].duration,
79
- timeline: tsList[i].timeline,
80
- uri: tsList[i].uri,
81
- tsOut: (0, node_path_1.resolve)(cacheDir, `${(0, fe_utils_1.md5)(tsList[i].uri)}.ts`),
85
+ duration: item.duration,
86
+ timeline: item.timeline,
87
+ uri: item.uri,
88
+ tsOut: (0, node_path_1.resolve)(cacheDir, `${(0, fe_utils_1.md5)(item.uri)}.ts`),
89
+ keyUri: item.key?.uri || '',
90
+ m3u8: url,
82
91
  });
83
- result.duration += tsList[i].duration;
92
+ result.duration += item.duration;
84
93
  }
85
94
  result.duration = +Number(result.duration).toFixed(2);
86
95
  return result;
@@ -1,5 +1,5 @@
1
1
  import type { SearchApi, VideoDetailsResult, VideoSearchResult } from '../../types';
2
- import { type M3u8StorConfig } from '../storage.js';
2
+ import type { M3u8StorConfig } from '../storage.js';
3
3
  export interface VSOptions {
4
4
  /** 采集站地址 */
5
5
  api?: string;
@@ -41,7 +41,7 @@ class CommSearchApi {
41
41
  }
42
42
  async search(wd, api = this.options.api) {
43
43
  let { data } = await req.get(api, { wd }, null, { rejectUnauthorized: false });
44
- if (typeof data == 'string')
44
+ if (typeof data === 'string')
45
45
  data = JSON.parse(data);
46
46
  return data;
47
47
  }
@@ -54,7 +54,7 @@ class CommSearchApi {
54
54
  ac: 'videolist',
55
55
  ids: Array.isArray(ids) ? ids.join(',') : ids,
56
56
  }, null, { rejectUnauthorized: false });
57
- if (typeof data == 'string')
57
+ if (typeof data === 'string')
58
58
  data = JSON.parse(data);
59
59
  return data;
60
60
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.stor = void 0;
4
- const fe_utils_1 = require("@lzwme/fe-utils");
5
- const node_path_1 = require("node:path");
6
4
  const node_os_1 = require("node:os");
5
+ const node_path_1 = require("node:path");
6
+ const fe_utils_1 = require("@lzwme/fe-utils");
7
7
  exports.stor = fe_utils_1.LiteStorage.getInstance({ uuid: 'm3u8dl', filepath: (0, node_path_1.resolve)((0, node_os_1.homedir)(), '.liteStorage/m3u8dl.json') });
@@ -1,3 +1,3 @@
1
- import type { M3u8Crypto, TsItemInfo } from '../types/m3u8';
2
1
  import type { IncomingHttpHeaders } from 'node:http';
2
+ import type { M3u8Crypto, TsItemInfo } from '../types/m3u8';
3
3
  export declare function tsDownload(info: TsItemInfo, cryptoInfo: M3u8Crypto, headers?: IncomingHttpHeaders): Promise<boolean>;
@@ -14,7 +14,7 @@ async function tsDownload(info, cryptoInfo, headers) {
14
14
  const r = await (0, utils_js_1.getRetry)(info.uri, headers);
15
15
  if (r.response.statusCode === 200) {
16
16
  utils_js_1.logger.debug('\n', info);
17
- const data = cryptoInfo.key ? aesDecrypt(r.buffer, cryptoInfo) : r.buffer;
17
+ const data = cryptoInfo?.key ? aesDecrypt(r.buffer, cryptoInfo) : r.buffer;
18
18
  (0, fe_utils_1.mkdirp)((0, node_path_1.dirname)(info.tsOut));
19
19
  await node_fs_1.promises.writeFile(info.tsOut, data);
20
20
  info.tsSize = r.buffer.byteLength;
@@ -29,7 +29,8 @@ async function tsDownload(info, cryptoInfo, headers) {
29
29
  }
30
30
  function aesDecrypt(data, cryptoInfo) {
31
31
  try {
32
- const decipher = (0, node_crypto_1.createDecipheriv)((cryptoInfo.method + '-cbc').toLocaleLowerCase(), cryptoInfo.key, cryptoInfo.iv);
32
+ const iv = cryptoInfo.iv || new Uint8Array(16);
33
+ const decipher = (0, node_crypto_1.createDecipheriv)(`${cryptoInfo.method}-cbc`.toLocaleLowerCase(), cryptoInfo.key, iv);
33
34
  return Buffer.concat([decipher.update(Buffer.isBuffer(data) ? data : Buffer.from(data)), decipher.final()]);
34
35
  }
35
36
  catch (err) {
@@ -1,5 +1,5 @@
1
+ import { type Stats } from 'node:fs';
1
2
  import type { IncomingHttpHeaders } from 'node:http';
2
- import { Stats } from 'node:fs';
3
3
  import { NLogger, Request } from '@lzwme/fe-utils';
4
4
  export declare const request: Request;
5
5
  export declare const getRetry: <T = string>(url: string, headers?: IncomingHttpHeaders, retries?: number) => Promise<{
package/cjs/lib/utils.js CHANGED
@@ -38,9 +38,9 @@ function findFiles(apidir, validate) {
38
38
  files.push((0, node_path_1.resolve)(apidir));
39
39
  }
40
40
  else if (stat.isDirectory()) {
41
- (0, node_fs_1.readdirSync)(apidir).forEach(filename => {
42
- findFiles((0, node_path_1.resolve)(apidir, filename)).forEach(f => files.push(f));
43
- });
41
+ for (const filename of (0, node_fs_1.readdirSync)(apidir)) {
42
+ files.push(...findFiles((0, node_path_1.resolve)(apidir, filename)));
43
+ }
44
44
  }
45
45
  }
46
46
  }
@@ -49,7 +49,7 @@ function findFiles(apidir, validate) {
49
49
  /** 获取重定向后的 URL */
50
50
  async function getLocation(url, method = 'HEAD') {
51
51
  const { res } = await exports.request.req(url, null, { method, headers: { 'content-type': 'text/html' } }, false);
52
- const rurl = res.headers['location'] || res.headers['x-redirect'] || res.headers['x-location'];
52
+ const rurl = res.headers.location || res.headers['x-redirect'] || res.headers['x-location'];
53
53
  if (typeof rurl === 'string' && rurl !== url)
54
54
  return getLocation(rurl, method);
55
55
  return url;