@lzwme/m3u8-dl 1.4.3 → 1.6.0-0

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.
@@ -0,0 +1,108 @@
1
+ declare const _default: {
2
+ cli: {
3
+ command: {
4
+ download: {
5
+ description: string;
6
+ };
7
+ server: {
8
+ description: string;
9
+ };
10
+ search: {
11
+ description: string;
12
+ };
13
+ };
14
+ option: {
15
+ silent: string;
16
+ debug: string;
17
+ filename: string;
18
+ threadNum: string;
19
+ force: string;
20
+ noProgress: string;
21
+ play: string;
22
+ cacheDir: string;
23
+ saveDir: string;
24
+ noDelCache: string;
25
+ noConvert: string;
26
+ ffmpegPath: string;
27
+ headers: string;
28
+ type: string;
29
+ ignoreSegments: string;
30
+ port: string;
31
+ token: string;
32
+ url: string;
33
+ apidir: string;
34
+ lang: string;
35
+ };
36
+ };
37
+ api: {
38
+ error: {
39
+ unauthorized: string;
40
+ configSaveFailed: string;
41
+ unknown: string;
42
+ downloadFailed: string;
43
+ accessDenied: string;
44
+ invalidUrl: string;
45
+ notFound: string;
46
+ };
47
+ success: {
48
+ configUpdated: string;
49
+ queueCleared: string;
50
+ downloadStarted: string;
51
+ paused: string;
52
+ resumed: string;
53
+ noResumableTasks: string;
54
+ deleted: string;
55
+ };
56
+ };
57
+ download: {
58
+ status: {
59
+ starting: string;
60
+ fileExists: string;
61
+ completed: string;
62
+ failed: string;
63
+ retry: string;
64
+ parseFailed: string;
65
+ segmentsIgnored: string;
66
+ tsDownloadError: string;
67
+ retryTimes: string;
68
+ totalSegments: string;
69
+ parallelJobs: string;
70
+ segmentsFailed: string;
71
+ downloadFailedRetry: string;
72
+ mergingVideo: string;
73
+ mergeFailed: string;
74
+ downloadFailedWithMessage: string;
75
+ };
76
+ error: {
77
+ parseFailed: string;
78
+ downloadFailed: string;
79
+ retryFailed: string;
80
+ };
81
+ };
82
+ error: {
83
+ download: {
84
+ failed: string;
85
+ parseFailed: string;
86
+ };
87
+ unknown: string;
88
+ };
89
+ prompt: {
90
+ search: {
91
+ keyword: string;
92
+ selectVideo: string;
93
+ continueDownload: string;
94
+ playWhileDownload: string;
95
+ noResults: string;
96
+ getVideoInfoFailed: string;
97
+ noPlayUrl: string;
98
+ reSearch: string;
99
+ allDownload: string;
100
+ };
101
+ };
102
+ common: {
103
+ exit: string;
104
+ cancel: string;
105
+ confirm: string;
106
+ };
107
+ };
108
+ export default _default;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {
4
+ cli: {
5
+ command: {
6
+ download: {
7
+ description: 'm3u8 url. Can also be a local txt file specifying a group of m3u8 URLs, suitable for batch download scenarios',
8
+ },
9
+ server: {
10
+ description: 'Start download center web service',
11
+ },
12
+ search: {
13
+ description: 'm3u8 video online search and download',
14
+ },
15
+ },
16
+ option: {
17
+ silent: 'Enable silent mode.',
18
+ debug: 'Enable debug mode.',
19
+ filename: 'Specify the save name of the downloaded file. Default is url md5 value. If multiple url addresses are specified, sequence numbers will be added at the end',
20
+ threadNum: 'Concurrent download thread count. Default is cpu * 2. Can set different values to observe download effects',
21
+ force: 'Enable force execution mode. Whether to continue downloading and generating when file already exists',
22
+ noProgress: 'Whether not to print progress information',
23
+ play: 'Whether to play while downloading',
24
+ cacheDir: 'Temporary file save directory. Default is cache',
25
+ saveDir: 'Path to save downloaded files. Default is current directory',
26
+ noDelCache: 'Whether to delete temporary files after successful download. Default is true. Saving temporary files can identify cache when re-downloading',
27
+ noConvert: 'After successful download, whether not to merge and convert to mp4 file. Default is true.',
28
+ ffmpegPath: 'Specify ffmpeg executable file path. If not specified, will try to use ffmpeg from system PATH',
29
+ headers: 'Custom request headers. Format: key1=value1\nkey2=value2',
30
+ type: 'Specify download type. Default is auto-detect based on URL. If batch downloading multiple different URL types, do not set this. Options: m3u8, file, parser',
31
+ ignoreSegments: 'Ignore video segments, use - to separate start and end time points, multiple separated by commas. Example: 0-10,20-30',
32
+ port: 'Specify web service port. Default is 6600',
33
+ token: 'Specify web service password (authorization header). Default is empty',
34
+ url: 'Video search API address (m3u8 collection site standard API)',
35
+ apidir: 'Specify the directory or specific path where custom video search API is located',
36
+ lang: 'Specify language. Options: zh, en',
37
+ },
38
+ },
39
+ api: {
40
+ error: {
41
+ unauthorized: 'Unauthorized access',
42
+ configSaveFailed: 'Config save failed',
43
+ unknown: 'Unknown error',
44
+ downloadFailed: 'Download failed',
45
+ accessDenied: 'Access denied',
46
+ invalidUrl: 'Invalid url parameter',
47
+ notFound: 'Not Found',
48
+ },
49
+ success: {
50
+ configUpdated: 'Config updated successfully',
51
+ queueCleared: 'Cleared {count} pending download tasks',
52
+ downloadStarted: 'Started {count} download task(s)',
53
+ paused: 'Paused {count} download task(s)',
54
+ resumed: 'Resumed {count} download task(s)',
55
+ noResumableTasks: 'No resumable download tasks found',
56
+ deleted: 'Deleted {count} download task(s)',
57
+ },
58
+ },
59
+ download: {
60
+ status: {
61
+ starting: 'Starting download for',
62
+ fileExists: 'file already exist:',
63
+ completed: 'Download completed',
64
+ failed: 'Download failed',
65
+ retry: 'Retry',
66
+ parseFailed: 'Failed to parse M3U8',
67
+ segmentsIgnored: 'Ignored {count} segments',
68
+ tsDownloadError: 'TS segment download error',
69
+ retryTimes: 'Retry times: {times}',
70
+ totalSegments: 'Total segments: {count}, duration: {duration}sec',
71
+ parallelJobs: 'Parallel jobs: {count}',
72
+ segmentsFailed: '{count} segments failed!',
73
+ downloadFailedRetry: 'Download Failed! Please retry!',
74
+ mergingVideo: 'Starting to merge video file, please wait...',
75
+ mergeFailed: 'Video file merge failed!',
76
+ downloadFailedWithMessage: 'Download failed: {message}',
77
+ },
78
+ error: {
79
+ parseFailed: 'Failed to parse M3U8',
80
+ downloadFailed: 'Download failed',
81
+ retryFailed: 'Retry failed',
82
+ },
83
+ },
84
+ error: {
85
+ download: {
86
+ failed: 'Download failed',
87
+ parseFailed: 'Parse failed',
88
+ },
89
+ unknown: 'Unknown error',
90
+ },
91
+ prompt: {
92
+ search: {
93
+ keyword: 'Please enter keyword',
94
+ selectVideo: 'Found {count} results, please select:',
95
+ continueDownload: 'There is an unfinished download from last time 【{name}】, continue?',
96
+ playWhileDownload: '【{name}】Play while downloading?',
97
+ noResults: 'No results found',
98
+ getVideoInfoFailed: 'Failed to get video information!',
99
+ noPlayUrl: 'No playback URL information obtained',
100
+ reSearch: 'Re-search',
101
+ allDownload: 'Download All',
102
+ },
103
+ },
104
+ common: {
105
+ exit: 'Exit',
106
+ cancel: 'Cancel',
107
+ confirm: 'Confirm',
108
+ },
109
+ };
@@ -0,0 +1,108 @@
1
+ declare const _default: {
2
+ cli: {
3
+ command: {
4
+ download: {
5
+ description: string;
6
+ };
7
+ server: {
8
+ description: string;
9
+ };
10
+ search: {
11
+ description: string;
12
+ };
13
+ };
14
+ option: {
15
+ silent: string;
16
+ debug: string;
17
+ filename: string;
18
+ threadNum: string;
19
+ force: string;
20
+ noProgress: string;
21
+ play: string;
22
+ cacheDir: string;
23
+ saveDir: string;
24
+ noDelCache: string;
25
+ noConvert: string;
26
+ ffmpegPath: string;
27
+ headers: string;
28
+ type: string;
29
+ ignoreSegments: string;
30
+ port: string;
31
+ token: string;
32
+ url: string;
33
+ apidir: string;
34
+ lang: string;
35
+ };
36
+ };
37
+ api: {
38
+ error: {
39
+ unauthorized: string;
40
+ configSaveFailed: string;
41
+ unknown: string;
42
+ downloadFailed: string;
43
+ accessDenied: string;
44
+ invalidUrl: string;
45
+ notFound: string;
46
+ };
47
+ success: {
48
+ configUpdated: string;
49
+ queueCleared: string;
50
+ downloadStarted: string;
51
+ paused: string;
52
+ resumed: string;
53
+ noResumableTasks: string;
54
+ deleted: string;
55
+ };
56
+ };
57
+ download: {
58
+ status: {
59
+ starting: string;
60
+ fileExists: string;
61
+ completed: string;
62
+ failed: string;
63
+ retry: string;
64
+ parseFailed: string;
65
+ segmentsIgnored: string;
66
+ tsDownloadError: string;
67
+ retryTimes: string;
68
+ totalSegments: string;
69
+ parallelJobs: string;
70
+ segmentsFailed: string;
71
+ downloadFailedRetry: string;
72
+ mergingVideo: string;
73
+ mergeFailed: string;
74
+ downloadFailedWithMessage: string;
75
+ };
76
+ error: {
77
+ parseFailed: string;
78
+ downloadFailed: string;
79
+ retryFailed: string;
80
+ };
81
+ };
82
+ error: {
83
+ download: {
84
+ failed: string;
85
+ parseFailed: string;
86
+ };
87
+ unknown: string;
88
+ };
89
+ prompt: {
90
+ search: {
91
+ keyword: string;
92
+ selectVideo: string;
93
+ continueDownload: string;
94
+ playWhileDownload: string;
95
+ noResults: string;
96
+ getVideoInfoFailed: string;
97
+ noPlayUrl: string;
98
+ reSearch: string;
99
+ allDownload: string;
100
+ };
101
+ };
102
+ common: {
103
+ exit: string;
104
+ cancel: string;
105
+ confirm: string;
106
+ };
107
+ };
108
+ export default _default;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {
4
+ cli: {
5
+ command: {
6
+ download: {
7
+ description: 'm3u8 url。也可以是本地 txt 文件,指定一组 m3u8,适用于批量下载的场景',
8
+ },
9
+ server: {
10
+ description: '启动下载中心web服务',
11
+ },
12
+ search: {
13
+ description: 'm3u8视频在线搜索与下载',
14
+ },
15
+ },
16
+ option: {
17
+ silent: '开启静默模式。',
18
+ debug: '开启调试模式。',
19
+ filename: '指定下载文件的保存名称。默认取 url md5 值。若指定了多个 url 地址,则会在末尾增加序号',
20
+ threadNum: '并发下载线程数。默认为 cpu * 2。可设置不同数值观察下载效果',
21
+ force: '启用强制执行模式。文件已存在时,是否仍继续下载和生成',
22
+ noProgress: '是否不打印进度信息',
23
+ play: '是否边下边看',
24
+ cacheDir: '临时文件保存目录。默认为 cache',
25
+ saveDir: '下载文件保存的路径。默认为当前目录',
26
+ noDelCache: '下载成功后是否删除临时文件。默认为 true。保存临时文件可以在重复下载时识别缓存',
27
+ noConvert: '下载成功后,是否不合并转换为 mp4 文件。默认为 true。',
28
+ ffmpegPath: '指定 ffmpeg 可执行文件路径。如果未指定,则尝试使用系统 PATH 中的 ffmpeg',
29
+ headers: '自定义请求头。格式为 key1=value1\nkey2=value2',
30
+ type: '指定下载类型。默认根据URL自动识别,如果是批量下载多个不同 URL 类型,请不要设置。可选值:m3u8, file, parser',
31
+ ignoreSegments: '忽略的视频片段,用-分割起始时间点,多个用逗号分隔。如:0-10,20-30',
32
+ port: '指定web服务端口。默认为6600',
33
+ token: '指定web服务密码(请求头authorization)。默认为空',
34
+ url: '影视搜索的接口地址(m3u8采集站标准接口)',
35
+ apidir: '指定自定义视频搜索 api 所在的目录或具体路径',
36
+ lang: '指定语言。可选值:zh, en',
37
+ },
38
+ },
39
+ api: {
40
+ error: {
41
+ unauthorized: '未授权,禁止访问',
42
+ configSaveFailed: '配置保存失败',
43
+ unknown: '未知错误',
44
+ downloadFailed: '下载失败',
45
+ accessDenied: '访问被拒绝',
46
+ invalidUrl: '无效的 url 参数',
47
+ notFound: '未找到',
48
+ },
49
+ success: {
50
+ configUpdated: '配置更新成功',
51
+ queueCleared: '已清空 {count} 个等待中的下载任务',
52
+ downloadStarted: '已开始下载 {count} 个任务',
53
+ paused: '已暂停 {count} 个下载任务',
54
+ resumed: '已恢复 {count} 个下载任务',
55
+ noResumableTasks: '没有找到可恢复的下载任务',
56
+ deleted: '已删除 {count} 个下载任务',
57
+ },
58
+ },
59
+ download: {
60
+ status: {
61
+ starting: '开始下载',
62
+ fileExists: '文件已存在:',
63
+ completed: '下载完成',
64
+ failed: '下载失败',
65
+ retry: '重试',
66
+ parseFailed: '解析 M3U8 失败',
67
+ segmentsIgnored: '已忽略 {count} 个片段',
68
+ tsDownloadError: 'TS 片段下载错误',
69
+ retryTimes: '重试次数: {times}',
70
+ totalSegments: '总片段数: {count},时长: {duration}秒',
71
+ parallelJobs: '并行任务数: {count}',
72
+ segmentsFailed: '{count} 个片段下载失败!',
73
+ downloadFailedRetry: '下载失败!请重试!',
74
+ mergingVideo: '开始合并为视频文件,请稍等...',
75
+ mergeFailed: '视频文件合并失败!',
76
+ downloadFailedWithMessage: '下载失败: {message}',
77
+ },
78
+ error: {
79
+ parseFailed: '解析 M3U8 失败',
80
+ downloadFailed: '下载失败',
81
+ retryFailed: '重试失败',
82
+ },
83
+ },
84
+ error: {
85
+ download: {
86
+ failed: '下载失败',
87
+ parseFailed: '解析失败',
88
+ },
89
+ unknown: '未知错误',
90
+ },
91
+ prompt: {
92
+ search: {
93
+ keyword: '请输入关键字',
94
+ selectVideo: '查找到了 {count} 条结果,请选择:',
95
+ continueDownload: '存在上次未完成的下载【{name}】,是否继续?',
96
+ playWhileDownload: '【{name}】是否边下边播?',
97
+ noResults: '没有搜到结果',
98
+ getVideoInfoFailed: '获取视频信息失败!',
99
+ noPlayUrl: '未获取到播放地址信息',
100
+ reSearch: '重新搜索',
101
+ allDownload: '全部下载',
102
+ },
103
+ },
104
+ common: {
105
+ exit: '退出',
106
+ cancel: '取消',
107
+ confirm: '确认',
108
+ },
109
+ };
package/cjs/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './lib/m3u8-download';
2
1
  export * from './lib/file-download';
3
2
  export * from './lib/getM3u8Urls';
3
+ export * from './lib/m3u8-download';
4
4
  export * from './lib/parseM3u8';
5
5
  export * from './video-parser';
package/cjs/index.js CHANGED
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./lib/m3u8-download"), exports);
18
17
  __exportStar(require("./lib/file-download"), exports);
19
18
  __exportStar(require("./lib/getM3u8Urls"), exports);
19
+ __exportStar(require("./lib/m3u8-download"), exports);
20
20
  __exportStar(require("./lib/parseM3u8"), exports);
21
21
  __exportStar(require("./video-parser"), exports);
@@ -4,11 +4,13 @@ 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 i18n_js_1 = require("./i18n.js");
7
8
  const format_options_js_1 = require("./format-options.js");
8
9
  const utils_js_1 = require("./utils.js");
9
10
  async function fileDownload(u, opts) {
10
11
  utils_js_1.logger.debug('fileDownload', u, opts);
11
12
  const { url, options } = (0, format_options_js_1.formatOptions)(u, opts);
13
+ const lang = (0, i18n_js_1.getLang)(options.lang);
12
14
  const startTime = Date.now();
13
15
  const stats = {
14
16
  url,
@@ -28,7 +30,7 @@ async function fileDownload(u, opts) {
28
30
  filename: options.filename,
29
31
  localVideo: (0, node_path_1.resolve)(options.saveDir, options.filename),
30
32
  };
31
- utils_js_1.logger.debug('开始下载', (0, console_log_colors_1.gray)(url), (0, console_log_colors_1.cyan)(stats.localVideo));
33
+ utils_js_1.logger.debug((0, i18n_js_1.t)('download.status.starting', lang), (0, console_log_colors_1.gray)(url), (0, console_log_colors_1.cyan)(stats.localVideo));
32
34
  if (options.onInited)
33
35
  options.onInited(stats, null, null);
34
36
  try {
@@ -66,17 +68,18 @@ async function fileDownload(u, opts) {
66
68
  });
67
69
  stats.endTime = Date.now();
68
70
  return {
69
- errmsg: r.filepath ? '下载完成' : '下载失败',
71
+ errmsg: r.filepath ? (0, i18n_js_1.t)('download.status.completed', lang) : (0, i18n_js_1.t)('download.status.failed', lang),
70
72
  ...r,
71
73
  stats,
72
74
  };
73
75
  }
74
76
  catch (error) {
75
- utils_js_1.logger.error('下载失败', error.message, (0, console_log_colors_1.gray)(url));
76
- stats.errmsg = error.message;
77
+ const errorMessage = error.message;
78
+ utils_js_1.logger.error((0, i18n_js_1.t)('download.status.failed', lang), errorMessage, (0, console_log_colors_1.gray)(url));
79
+ stats.errmsg = errorMessage;
77
80
  return {
78
81
  isExist: false,
79
- errmsg: `下载失败: ${error.message}`,
82
+ errmsg: (0, i18n_js_1.t)('download.status.downloadFailedWithMessage', lang, { message: errorMessage }),
80
83
  stats,
81
84
  };
82
85
  }
@@ -6,6 +6,12 @@ export interface GetM3u8UrlsOption {
6
6
  headers?: OutgoingHttpHeaders | string;
7
7
  deep?: number;
8
8
  visited?: Set<string>;
9
+ /** 并发处理子页面的数量,默认为 3 */
10
+ concurrency?: number;
11
+ /** 最大提取 URL 数量,超过则不再执行子页面抓取 */
12
+ maxUrls?: number;
13
+ /** HTML 内容大小限制(字节),超过则跳过处理 */
14
+ maxHtmlSize?: number;
9
15
  }
10
16
  /** 从指定的 url 页面中提取 m3u8 播放地址。deep 指定搜索页面深度 */
11
17
  export declare function getM3u8Urls(opts: GetM3u8UrlsOption): Promise<Map<string, string>>;
@@ -15,7 +15,14 @@ function getFormatTitle(text) {
15
15
  }
16
16
  /** 从指定的 url 页面中提取 m3u8 播放地址。deep 指定搜索页面深度 */
17
17
  async function getM3u8Urls(opts) {
18
- const options = { headers: {}, deep: 1, visited: new Set(), ...opts };
18
+ const options = {
19
+ headers: {},
20
+ deep: 1,
21
+ visited: new Set(),
22
+ maxUrls: 3000,
23
+ maxHtmlSize: 5 * 1024 * 1024, // 5MB
24
+ ...opts,
25
+ };
19
26
  const baseUrl = new URL(options.url).origin;
20
27
  const req = new fe_utils_1.Request({
21
28
  headers: { 'content-type': 'text/html; charset=UTF-8', referer: baseUrl, ...(0, utils_js_1.formatHeaders)(options.headers) },
@@ -27,6 +34,11 @@ async function getM3u8Urls(opts) {
27
34
  utils_js_1.logger.error('获取页面失败:', fe_utils_1.color.red(options.url), response.statusCode, response.statusMessage, html);
28
35
  return m3u8Urls;
29
36
  }
37
+ // 检查 HTML 大小
38
+ if (options.maxHtmlSize && html.length > options.maxHtmlSize) {
39
+ utils_js_1.logger.warn('HTML 内容过大,跳过处理:', fe_utils_1.color.yellow(options.url), `${(html.length / 1024 / 1024).toFixed(2)}MB`, `(限制: ${(options.maxHtmlSize / 1024 / 1024).toFixed(2)}MB)`);
40
+ return m3u8Urls;
41
+ }
30
42
  // 从 html 中正则匹配提取 m3u8
31
43
  const m3u8Regex = /https?:[^\s'":]+\.(m3u8|mp4)(\?[^\s'"]*)?/gi;
32
44
  // 1. 直接正则匹配 m3u8 地址
@@ -71,37 +83,43 @@ async function getM3u8Urls(opts) {
71
83
  subPageUrls.set(href, text);
72
84
  utils_js_1.logger.debug(' > 提取到子页面: ', fe_utils_1.color.gray(href), text);
73
85
  }
74
- for (const [href, text] of subPageUrls) {
75
- try {
76
- options.visited.add(href);
77
- const subUrls = await getM3u8Urls({ ...options, url: href, deep: options.deep - 1 });
78
- utils_js_1.logger.debug(' > 从子页面提取: ', fe_utils_1.color.gray(href), text, subUrls);
79
- if (subUrls.size === 0 && m3u8Urls.size === 0) {
80
- failedSubPages++;
81
- if (failedSubPages > 3) {
82
- utils_js_1.logger.warn(`连续查找 ${failedSubPages} 个子页面均未获取到,不再继续`, options.url, href);
83
- return m3u8Urls;
86
+ const taskList = Array.from(subPageUrls.entries()).map(([href, text]) => {
87
+ return async () => {
88
+ // 达到最大 URL 数量限制,不再继续
89
+ if (options.maxUrls && m3u8Urls.size >= options.maxUrls)
90
+ return;
91
+ try {
92
+ options.visited.add(href);
93
+ const subUrls = await getM3u8Urls({ ...options, url: href, deep: options.deep - 1 });
94
+ utils_js_1.logger.debug(' > 从子页面提取: ', fe_utils_1.color.gray(href), text, subUrls);
95
+ if (subUrls.size === 0 && m3u8Urls.size === 0) {
96
+ failedSubPages++;
97
+ if (failedSubPages > 3) {
98
+ utils_js_1.logger.warn(`连续查找 ${failedSubPages} 个子页面均未获取到,不再继续`, options.url, href);
99
+ return;
100
+ }
84
101
  }
85
- }
86
- for (const [u, t] of subUrls) {
87
- let stitle = t;
88
- for (const s of [text, t, m3u8Urls.get(u) || '']) {
89
- const ft = getFormatTitle(s);
90
- if (ft) {
91
- stitle = ft;
92
- break;
102
+ for (const [u, t] of subUrls) {
103
+ let stitle = t;
104
+ for (const s of [text, t, m3u8Urls.get(u) || '']) {
105
+ const ft = getFormatTitle(s);
106
+ if (ft) {
107
+ stitle = ft;
108
+ break;
109
+ }
93
110
  }
111
+ utils_js_1.logger.debug(' > m3u8地址: ', fe_utils_1.color.gray(u), fe_utils_1.color.green(stitle));
112
+ m3u8Urls.set(u, stitle.trim());
94
113
  }
95
- utils_js_1.logger.debug(' > m3u8地址: ', fe_utils_1.color.gray(u), fe_utils_1.color.green(stitle));
96
- m3u8Urls.set(u, stitle.trim());
97
114
  }
98
- }
99
- catch (err) {
100
- utils_js_1.logger.warn(' > 尝试访问子页面异常: ', fe_utils_1.color.red(href), err.message);
101
- }
102
- }
115
+ catch (err) {
116
+ utils_js_1.logger.warn(' > 尝试访问子页面异常: ', fe_utils_1.color.red(href), err.message);
117
+ }
118
+ };
119
+ });
120
+ await (0, fe_utils_1.concurrency)(taskList, Math.max(1, +options.concurrency || 3));
103
121
  }
104
122
  return m3u8Urls;
105
123
  }
106
124
  // logger.updateOptions({ levelType: 'debug' });
107
- // getM3u8Urls(process.argv.slice(2)[0]).then(d => console.log(d));
125
+ // getM3u8Urls({ url: process.argv.slice(2)[0] }).then(d => console.log(d));
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared i18n utility for backend (CLI, SDK, Server)
3
+ */
4
+ import type { AnyObject } from '@lzwme/fe-utils';
5
+ type Locale = 'zh' | 'en';
6
+ export declare const LANG_CODES: Set<string>;
7
+ /**
8
+ * Detect language from OS or environment
9
+ */
10
+ export declare function detectLanguage(): Locale;
11
+ /**
12
+ * Set global language context
13
+ */
14
+ export declare function setLanguage(lang: Locale | null): void;
15
+ /**
16
+ * Get global language context
17
+ */
18
+ export declare function getLanguage(): Locale | null;
19
+ /**
20
+ * Get language from various sources
21
+ */
22
+ export declare function getLang(lang?: string): Locale;
23
+ /**
24
+ * Translation function
25
+ */
26
+ export declare function t(key: string, lang?: string, params?: AnyObject): string;
27
+ export {};