@lynker-desktop/electron-sdk 0.0.9-alpha.50 → 0.0.9-alpha.52

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/main/index.js CHANGED
@@ -3,9 +3,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
  const node_util = require('node:util');
6
- const http = require('node:http');
7
- const https = require('node:https');
8
- const node_child_process = require('node:child_process');
9
6
  const electron = require('electron');
10
7
  const md5 = require('md5');
11
8
  const uuid = require('uuid');
@@ -17,6 +14,8 @@ const windowManager = require('@lynker-desktop/electron-window-manager/main');
17
14
  const index$1 = require('../common/index.js');
18
15
  const store = require('./store.js');
19
16
  const clipboard = require('./clipboard.js');
17
+ const resourceCache = require('./resource-cache.js');
18
+ const videoDownloader = require('./video-downloader.js');
20
19
 
21
20
  function _interopNamespaceDefault(e) {
22
21
  const n = Object.create(null);
@@ -70,7 +69,7 @@ const initialize = (options) => {
70
69
  store.initializeStore();
71
70
  clipboard.initializeClipboard();
72
71
  if (options.ffmpegPath) {
73
- VideoDownloader.getInstance().setFFmpegPath(options.ffmpegPath);
72
+ videoDownloader.setFFmpegPath(options.ffmpegPath);
74
73
  }
75
74
  const wm = windowManager__namespace.initialize(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList || []);
76
75
  if (protocol) {
@@ -82,7 +81,7 @@ const initialize = (options) => {
82
81
  if (resourceCacheOptions) {
83
82
  if (resourceCacheOptions.cacheDir) {
84
83
  serveOptions.push({
85
- scheme: ResourceCache.scheme,
84
+ scheme: resourceCache.ResourceCache.scheme,
86
85
  directory: resourceCacheOptions.cacheDir,
87
86
  });
88
87
  }
@@ -92,7 +91,7 @@ const initialize = (options) => {
92
91
  }
93
92
  if (resourceCacheOptions) {
94
93
  electron.app.on('ready', () => {
95
- new ResourceCache(electron.session.defaultSession, resourceCacheOptions);
94
+ new resourceCache.ResourceCache(electron.session.defaultSession, resourceCacheOptions);
96
95
  });
97
96
  }
98
97
  class SDK {
@@ -100,10 +99,10 @@ const initialize = (options) => {
100
99
  SDK.ipc = ipc__namespace.mainIPC;
101
100
  SDK.windowManager = wm;
102
101
  SDK.getPreload = () => preload;
103
- SDK.downloadVideo = downloadVideo;
104
- SDK.clearVideoCache = clearVideoCache;
105
- SDK.getVideoCacheStats = getVideoCacheStats;
106
- SDK.removeVideoCache = removeVideoCache;
102
+ SDK.downloadVideo = videoDownloader.downloadVideo;
103
+ SDK.clearVideoCache = videoDownloader.clearVideoCache;
104
+ SDK.getVideoCacheStats = videoDownloader.getVideoCacheStats;
105
+ SDK.removeVideoCache = videoDownloader.removeVideoCache;
107
106
  ipc__namespace.mainIPC.handleRenderer(index$1.IPC_GET_APP_METRICS, async () => {
108
107
  const metrics = electron.app.getAppMetrics().map(i => {
109
108
  const webContents = findWebContentsByPid(i.pid || 0);
@@ -118,7 +117,7 @@ const initialize = (options) => {
118
117
  });
119
118
  ipc__namespace.mainIPC.handleRenderer(`__sdk_transcode_video__`, async (options) => {
120
119
  const md5Options = md5(JSON.stringify(options.url));
121
- downloadVideo(options, {
120
+ videoDownloader.downloadVideo(options, {
122
121
  onDownloadProgress: (progress) => {
123
122
  ipc__namespace.mainIPC.invokeAllRenderer(`__sdk_transcode_video_callback_${md5Options}__`, {
124
123
  type: 'downloadProgress',
@@ -296,231 +295,12 @@ const serve = (options) => {
296
295
  }
297
296
  });
298
297
  };
299
- /**
300
- * 默认配置
301
- */
302
- const DEFAULT_OPTIONS = {
303
- cacheDir: '',
304
- cacheTTL: 24 * 60 * 60 * 1000,
305
- match: /\.(png|jpe?g|webp|gif|svg|woff2?|ttf|mp4|webm|ogg|css|js)(\?.*)?$/i,
306
- allowedOrigins: null,
307
- };
308
- /**
309
- * 资源缓存类:拦截并缓存静态资源,提升加载性能
310
- */
311
- class ResourceCache {
312
- /**
313
- * 构造函数
314
- * @param session Electron session
315
- * @param options 缓存配置
316
- */
317
- constructor(session, options) {
318
- this.cacheHost = `${ResourceCache.scheme}://-`;
319
- if (!session)
320
- throw new Error('ResourceCache: session is required');
321
- this.session = session;
322
- // 合并配置,保证类型安全
323
- this.options = {
324
- ...DEFAULT_OPTIONS,
325
- ...options,
326
- cacheDir: options.cacheDir,
327
- cacheTTL: options.cacheTTL ?? DEFAULT_OPTIONS.cacheTTL,
328
- match: options.match ?? DEFAULT_OPTIONS.match,
329
- allowedOrigins: options.allowedOrigins ?? DEFAULT_OPTIONS.allowedOrigins,
330
- };
331
- if (!this.options.cacheDir) {
332
- throw new Error('ResourceCache: cacheDir is required');
333
- }
334
- // 确保缓存目录存在
335
- if (!fs.existsSync(this.options.cacheDir)) {
336
- fs.mkdirSync(this.options.cacheDir, { recursive: true });
337
- }
338
- this._registerInterceptor();
339
- this._cleanOldCache();
340
- }
341
- /**
342
- * 获取资源匹配函数
343
- */
344
- _getMatchFunction() {
345
- const matcher = this.options.match;
346
- if (typeof matcher === 'function')
347
- return matcher;
348
- if (matcher instanceof RegExp)
349
- return (url) => matcher.test(url);
350
- return () => false;
351
- }
352
- /**
353
- * 获取来源校验函数
354
- */
355
- _getOriginAllowFunction() {
356
- const origins = this.options.allowedOrigins;
357
- if (!origins)
358
- return () => true;
359
- if (typeof origins === 'function')
360
- return origins;
361
- const prefixList = origins.map(o => o.toLowerCase());
362
- return (url) => {
363
- try {
364
- const origin = new URL(url).origin.toLowerCase();
365
- return prefixList.some(prefix => origin.startsWith(prefix));
366
- }
367
- catch {
368
- return false;
369
- }
370
- };
371
- }
372
- /**
373
- * 获取缓存文件路径
374
- * @param url 资源URL
375
- */
376
- getCachedPath(url) {
377
- const md5Str = md5(url);
378
- const urlObj = new URL(url);
379
- // 取文件扩展名,若无则用 .res
380
- const ext = path.extname(urlObj.pathname) || '.res';
381
- return {
382
- filePath: path.join(this.options.cacheDir, `${md5Str}${ext}`),
383
- hostPath: `${this.cacheHost}/${md5Str}${ext}`,
384
- };
385
- }
386
- /**
387
- * 判断缓存是否有效
388
- * @param filePath 缓存文件路径
389
- */
390
- isCacheValid(filePath) {
391
- if (!fs.existsSync(filePath))
392
- return false;
393
- const stat = fs.statSync(filePath);
394
- return Date.now() - stat.mtimeMs < this.options.cacheTTL;
395
- }
396
- /**
397
- * 下载资源到本地缓存
398
- * @param url 资源URL
399
- * @param filePath 本地缓存路径
400
- */
401
- downloadResource(url, filePath) {
402
- const tempFilePath = `${filePath}.cache`;
403
- const lib = url.startsWith('https') ? https : http;
404
- const file = fs.createWriteStream(tempFilePath);
405
- let request;
406
- const cleanupAndAbort = (errMsg, err) => {
407
- if (err) {
408
- console.log(errMsg, err);
409
- }
410
- else {
411
- console.log(errMsg);
412
- }
413
- if (request) {
414
- request.destroy();
415
- }
416
- file.close(() => {
417
- // 使用 existsSync 避免在文件不存在时 unlink 抛出错误
418
- if (fs.existsSync(tempFilePath)) {
419
- fs.unlink(tempFilePath, () => { });
420
- }
421
- });
422
- };
423
- request = lib.get(url, (res) => {
424
- if (res.statusCode !== 200) {
425
- res.resume(); // 消费响应数据以释放内存
426
- cleanupAndAbort(`下载失败,状态码: ${res.statusCode} for ${url}`);
427
- return;
428
- }
429
- res.pipe(file);
430
- });
431
- file.on('finish', () => {
432
- file.close((err) => {
433
- if (err) {
434
- return cleanupAndAbort(`关闭临时文件流失败: ${tempFilePath}`, err);
435
- }
436
- fs.rename(tempFilePath, filePath, (renameErr) => {
437
- if (renameErr) {
438
- cleanupAndAbort(`缓存文件重命名失败 from ${tempFilePath} to ${filePath}`, renameErr);
439
- }
440
- });
441
- });
442
- });
443
- file.on('error', (err) => {
444
- cleanupAndAbort(`写入临时文件失败: ${tempFilePath}`, err);
445
- });
446
- request.on('error', (err) => {
447
- cleanupAndAbort(`下载资源请求失败: ${url}`, err);
448
- });
449
- }
450
- /**
451
- * 清理过期缓存文件
452
- */
453
- _cleanOldCache() {
454
- const files = fs.readdirSync(this.options.cacheDir);
455
- const now = Date.now();
456
- files.forEach(file => {
457
- const fullPath = path.join(this.options.cacheDir, file);
458
- try {
459
- const stat = fs.statSync(fullPath);
460
- if (now - stat.mtimeMs > this.options.cacheTTL) {
461
- fs.unlinkSync(fullPath);
462
- }
463
- }
464
- catch {
465
- // 忽略单个文件异常
466
- }
467
- });
468
- }
469
- /**
470
- * 注册 Electron 请求拦截器,实现资源缓存
471
- */
472
- _registerInterceptor() {
473
- const shouldCache = this._getMatchFunction();
474
- const isAllowedOrigin = this._getOriginAllowFunction();
475
- this.session.webRequest.onBeforeRequest({ urls: ['http://*/*', 'https://*/*'] }, (details, callback) => {
476
- const url = details.url;
477
- // 不匹配或来源不允许,直接放行
478
- if (details.method !== 'GET' || !shouldCache(url) || !isAllowedOrigin(url))
479
- return callback({});
480
- const cachePath = this.getCachedPath(url);
481
- // 命中缓存,直接重定向到本地文件
482
- if (this.isCacheValid(cachePath.filePath)) {
483
- console.log('命中缓存: ', url, cachePath);
484
- return callback({ redirectURL: cachePath.hostPath });
485
- }
486
- console.log('未命中缓存: ', url);
487
- // 未命中则异步下载,当前请求正常放行
488
- this.downloadResource(url, cachePath.filePath);
489
- return callback({});
490
- });
491
- }
492
- }
493
- ResourceCache.scheme = 'cachefile';
494
298
  /**
495
299
  * @electron/remote/main enable方法
496
300
  * @param webContents
497
301
  * @returns
498
302
  */
499
303
  const enable = (webContents) => remote__namespace.enable(webContents);
500
- /**
501
- * 生成跨平台的文件URL
502
- * @param filePath 文件路径
503
- * @returns file:// URL
504
- */
505
- function getFileUrl(filePath) {
506
- if (!filePath) {
507
- return '';
508
- }
509
- if (filePath.startsWith('file://')) {
510
- return filePath;
511
- }
512
- // 在Windows上,需要添加驱动器字母前的斜杠
513
- if (process.platform === 'win32') {
514
- // 如果路径是绝对路径(如 C:\path\to\file),转换为 /C:/path/to/file
515
- if (filePath.match(/^[A-Za-z]:/)) {
516
- return `file:///${filePath}`;
517
- }
518
- // 如果已经是 /C:/path/to/file 格式,直接添加 file://
519
- return `file://${filePath}`;
520
- }
521
- // macOS 和 Linux 使用 file:/// 前缀
522
- return `file://${filePath}`;
523
- }
524
304
  /**
525
305
  * 获取随机UUID
526
306
  * @param key
@@ -544,469 +324,6 @@ function getRandomUUID(key = `${Date.now()}`) {
544
324
  const uuid$1 = uuid.v5(`${JSON.stringify(key)}_${Date.now()}_${randomValue}`, uuid.v5.URL);
545
325
  return uuid$1;
546
326
  }
547
- /**
548
- * 视频下载和转码类
549
- */
550
- class VideoDownloader {
551
- constructor() {
552
- this.ffmpegPath = '';
553
- this.defaultCacheTTL = 24 * 60 * 60 * 1000; // 24小时缓存有效期
554
- }
555
- static getInstance() {
556
- if (!VideoDownloader.instance) {
557
- VideoDownloader.instance = new VideoDownloader();
558
- }
559
- return VideoDownloader.instance;
560
- }
561
- setFFmpegPath(ffmpegPath) {
562
- this.ffmpegPath = ffmpegPath;
563
- }
564
- /**
565
- * 下载视频文件
566
- */
567
- async downloadVideoFile(url, outputPath, onProgress) {
568
- return new Promise((resolve, reject) => {
569
- const lib = url.startsWith('https') ? https : http;
570
- const file = fs.createWriteStream(outputPath);
571
- let downloadedBytes = 0;
572
- let totalBytes = 0;
573
- let startTime = Date.now();
574
- let lastTime = startTime;
575
- let lastBytes = 0;
576
- const cleanup = () => {
577
- if (file) {
578
- file.close();
579
- fs.unlinkSync(outputPath);
580
- }
581
- };
582
- const request = lib.get(url, (res) => {
583
- if (res.statusCode !== 200) {
584
- cleanup();
585
- reject(new Error(`下载失败,状态码: ${res.statusCode}`));
586
- return;
587
- }
588
- totalBytes = parseInt(res.headers['content-length'] || '0', 10);
589
- res.on('data', (chunk) => {
590
- downloadedBytes += chunk.length;
591
- const now = Date.now();
592
- if (onProgress && (now - lastTime > 100 || downloadedBytes === totalBytes)) {
593
- const speed = ((downloadedBytes - lastBytes) * 1000) / (now - lastTime);
594
- onProgress({
595
- downloaded: downloadedBytes,
596
- total: totalBytes,
597
- percentage: totalBytes > 0 ? (downloadedBytes / totalBytes) * 100 : 0,
598
- speed: speed || 0
599
- });
600
- lastTime = now;
601
- lastBytes = downloadedBytes;
602
- }
603
- });
604
- res.pipe(file);
605
- file.on('finish', () => {
606
- file.close();
607
- resolve();
608
- });
609
- file.on('error', (err) => {
610
- cleanup();
611
- reject(err);
612
- });
613
- });
614
- request.on('error', (err) => {
615
- cleanup();
616
- reject(err);
617
- });
618
- });
619
- }
620
- /**
621
- * 使用FFmpeg转码视频
622
- */
623
- async transcodeVideo(inputPath, outputPath, options, onProgress) {
624
- return new Promise((resolve, reject) => {
625
- const ffmpegPath = options.ffmpegPath || this.ffmpegPath;
626
- if (!ffmpegPath) {
627
- reject(new Error('FFmpeg路径未设置'));
628
- return;
629
- }
630
- const format = options.format || 'mp4';
631
- const quality = options.quality || 18;
632
- const videoCodec = options.videoCodec || 'h264';
633
- const audioCodec = options.audioCodec || 'aac';
634
- // 根据格式选择合适的编码器
635
- const getCodecForFormat = (format, videoCodec, audioCodec) => {
636
- switch (format) {
637
- case 'webm':
638
- return {
639
- videoCodec: videoCodec === 'h264' ? 'libvpx-vp9' : videoCodec,
640
- audioCodec: audioCodec === 'aac' ? 'libopus' : audioCodec
641
- };
642
- case 'avi':
643
- return {
644
- videoCodec: videoCodec === 'h265' ? 'h264' : videoCodec, // AVI不支持H265
645
- audioCodec: audioCodec === 'opus' ? 'mp3' : audioCodec
646
- };
647
- case 'mov':
648
- return {
649
- videoCodec: videoCodec === 'vp9' ? 'h264' : videoCodec, // MOV对VP9支持有限
650
- audioCodec: audioCodec === 'opus' ? 'aac' : audioCodec
651
- };
652
- default: // mp4, mkv
653
- return { videoCodec, audioCodec };
654
- }
655
- };
656
- const { videoCodec: finalVideoCodec, audioCodec: finalAudioCodec } = getCodecForFormat(format, videoCodec, audioCodec);
657
- // 构建FFmpeg命令
658
- const args = [
659
- '-i', inputPath,
660
- '-c:v', finalVideoCodec,
661
- '-c:a', finalAudioCodec,
662
- '-crf', quality.toString(),
663
- '-preset', 'medium',
664
- '-f', format, // 指定输出格式
665
- '-y', // 覆盖输出文件
666
- outputPath
667
- ];
668
- const ffmpegProcess = node_child_process.spawn(ffmpegPath, args);
669
- let duration = 0;
670
- let stderr = '';
671
- ffmpegProcess.stderr.on('data', (data) => {
672
- stderr += data.toString();
673
- // 解析FFmpeg输出获取进度
674
- if (onProgress) {
675
- const durationMatch = stderr.match(/Duration: (\d{2}):(\d{2}):(\d{2})\.(\d{2})/);
676
- if (durationMatch && duration === 0) {
677
- const hours = parseInt(durationMatch[1] || '0', 10);
678
- const minutes = parseInt(durationMatch[2] || '0', 10);
679
- const seconds = parseInt(durationMatch[3] || '0', 10);
680
- const centiseconds = parseInt(durationMatch[4] || '0', 10);
681
- duration = hours * 3600 + minutes * 60 + seconds + centiseconds / 100;
682
- }
683
- const timeMatch = stderr.match(/time=(\d{2}):(\d{2}):(\d{2})\.(\d{2})/);
684
- if (timeMatch && duration > 0) {
685
- const hours = parseInt(timeMatch[1] || '0', 10);
686
- const minutes = parseInt(timeMatch[2] || '0', 10);
687
- const seconds = parseInt(timeMatch[3] || '0', 10);
688
- const centiseconds = parseInt(timeMatch[4] || '0', 10);
689
- const currentTime = hours * 3600 + minutes * 60 + seconds + centiseconds / 100;
690
- const percentage = (currentTime / duration) * 100;
691
- const speedMatch = stderr.match(/speed=([\d.]+)x/);
692
- const speed = speedMatch ? parseFloat(speedMatch[1] || '1') : 1;
693
- onProgress({
694
- percentage: Math.min(percentage, 100),
695
- time: currentTime,
696
- speed: speed
697
- });
698
- }
699
- }
700
- });
701
- ffmpegProcess.on('close', (code) => {
702
- if (code === 0) {
703
- resolve();
704
- }
705
- else {
706
- reject(new Error(`FFmpeg转码失败,退出码: ${code}\n${stderr}`));
707
- }
708
- });
709
- ffmpegProcess.on('error', (err) => {
710
- reject(new Error(`FFmpeg执行失败: ${err.message}`));
711
- });
712
- });
713
- }
714
- /**
715
- * 检查FFmpeg是否可用
716
- */
717
- async checkFFmpegAvailable(ffmpegPath) {
718
- return new Promise((resolve) => {
719
- const command = ffmpegPath || this.ffmpegPath;
720
- if (!command) {
721
- resolve(false);
722
- return;
723
- }
724
- const childProcess = node_child_process.spawn(command, ['-version']);
725
- childProcess.on('error', (error) => {
726
- console.log("checkFFmpegAvailable", error);
727
- resolve(false);
728
- });
729
- childProcess.on('close', (code) => {
730
- console.log("checkFFmpegAvailable", code);
731
- resolve(code === 0);
732
- });
733
- });
734
- }
735
- /**
736
- * 获取视频信息
737
- */
738
- async getVideoInfo(inputPath, ffmpegPath) {
739
- return new Promise((resolve, reject) => {
740
- const ffmpegPath_cmd = ffmpegPath || this.ffmpegPath;
741
- const args = ['-i', inputPath, '-f', 'null', '-'];
742
- if (!ffmpegPath_cmd) {
743
- resolve({ duration: 0, fileSize: 0 });
744
- return;
745
- }
746
- const ffmpegProcess = node_child_process.spawn(ffmpegPath_cmd, args);
747
- let stderr = '';
748
- let duration = 0;
749
- ffmpegProcess.stderr.on('data', (data) => {
750
- stderr += data.toString();
751
- const durationMatch = stderr.match(/Duration: (\d{2}):(\d{2}):(\d{2})\.(\d{2})/);
752
- if (durationMatch) {
753
- const hours = parseInt(durationMatch[1] || '0', 10);
754
- const minutes = parseInt(durationMatch[2] || '0', 10);
755
- const seconds = parseInt(durationMatch[3] || '0', 10);
756
- const centiseconds = parseInt(durationMatch[4] || '0', 10);
757
- duration = hours * 3600 + minutes * 60 + seconds + centiseconds / 100;
758
- }
759
- });
760
- ffmpegProcess.on('close', () => {
761
- const stats = fs.statSync(inputPath);
762
- resolve({
763
- duration,
764
- fileSize: stats.size
765
- });
766
- });
767
- ffmpegProcess.on('error', (err) => {
768
- reject(err);
769
- });
770
- });
771
- }
772
- /**
773
- * 生成缓存文件名(基于URL的MD5)
774
- */
775
- generateCacheFileName(options) {
776
- const format = options.format || 'mp4';
777
- const urlMd5 = md5(options.url);
778
- return `${urlMd5}.${format}`;
779
- }
780
- /**
781
- * 检查缓存文件是否存在
782
- */
783
- isCacheFileExists(cacheFilePath) {
784
- console.log('检查缓存文件是否存在: ', cacheFilePath);
785
- return fs.existsSync(cacheFilePath);
786
- }
787
- /**
788
- * 清理过期缓存文件
789
- */
790
- cleanExpiredCacheFiles(outputDir, cacheTTL = this.defaultCacheTTL) {
791
- try {
792
- const files = fs.readdirSync(outputDir);
793
- const now = Date.now();
794
- files.forEach(file => {
795
- const filePath = path.join(outputDir, file);
796
- try {
797
- const stats = fs.statSync(filePath);
798
- // 如果文件超过缓存时间,删除它
799
- if (now - stats.mtimeMs > cacheTTL) {
800
- fs.unlinkSync(filePath);
801
- console.log(`🧹 删除过期缓存文件: ${file}`);
802
- }
803
- }
804
- catch (error) {
805
- // 忽略单个文件错误
806
- }
807
- });
808
- }
809
- catch (error) {
810
- // 忽略目录读取错误
811
- }
812
- }
813
- /**
814
- * 下载并转码视频
815
- */
816
- async downloadAndTranscode(options, callbacks) {
817
- try {
818
- const outputDir = options.outputDir || electron.app.getPath('temp');
819
- // 获取缓存配置
820
- const cacheConfig = options.cache || { enabled: true, ttl: this.defaultCacheTTL };
821
- const cacheEnabled = cacheConfig.enabled !== false;
822
- const cacheTTL = cacheConfig.ttl || this.defaultCacheTTL;
823
- // 确保输出目录存在
824
- if (!fs.existsSync(outputDir)) {
825
- fs.mkdirSync(outputDir, { recursive: true });
826
- }
827
- // 清理过期缓存文件
828
- this.cleanExpiredCacheFiles(outputDir, cacheTTL);
829
- // 生成缓存文件名
830
- const cacheFileName = this.generateCacheFileName(options);
831
- const cacheFilePath = path.join(outputDir, cacheFileName);
832
- // 如果启用缓存,检查缓存文件是否存在
833
- if (cacheEnabled && this.isCacheFileExists(cacheFilePath)) {
834
- console.log('🎯 命中缓存,直接返回缓存文件');
835
- // 获取文件信息
836
- const stats = fs.statSync(cacheFilePath);
837
- const result = {
838
- success: true,
839
- outputPath: getFileUrl(cacheFilePath),
840
- fileSize: stats.size,
841
- fromCache: true
842
- };
843
- callbacks?.onComplete?.({
844
- success: true,
845
- outputPath: result.outputPath
846
- });
847
- return result;
848
- }
849
- // 检查FFmpeg是否可用
850
- const ffmpegAvailable = await this.checkFFmpegAvailable(options.ffmpegPath);
851
- if (!ffmpegAvailable) {
852
- throw new Error('FFmpeg不可用,请确保已安装FFmpeg或提供正确的路径');
853
- }
854
- // 生成临时文件名和最终输出路径
855
- const fileName = options.outputName || md5(options.url);
856
- const format = options.format || 'mp4';
857
- const tempPath = path.join(outputDir, `${fileName}.temp`);
858
- const finalOutputPath = path.join(outputDir, `${fileName}.${format}`);
859
- const outputPath = cacheEnabled ? cacheFilePath : finalOutputPath;
860
- console.log('📥 开始下载视频...');
861
- // 下载视频
862
- await this.downloadVideoFile(options.url, tempPath, callbacks?.onDownloadProgress);
863
- console.log('🔄 开始转码视频...');
864
- // 转码视频
865
- await this.transcodeVideo(tempPath, outputPath, {
866
- ...options,
867
- outputDir
868
- }, callbacks?.onTranscodeProgress);
869
- // 如果启用了缓存,将缓存文件复制到最终输出路径
870
- if (cacheEnabled && outputPath !== finalOutputPath) {
871
- fs.copyFileSync(outputPath, finalOutputPath);
872
- console.log('📋 缓存文件已复制到最终输出路径');
873
- }
874
- // 获取视频信息
875
- const videoInfo = await this.getVideoInfo(outputPath, options.ffmpegPath);
876
- // 清理临时文件
877
- if (!options.keepOriginal) {
878
- fs.unlinkSync(tempPath);
879
- }
880
- console.log('💾 文件已保存到缓存');
881
- const result = {
882
- success: true,
883
- outputPath: getFileUrl(cacheEnabled ? finalOutputPath : outputPath),
884
- originalPath: options.keepOriginal ? getFileUrl(tempPath) : undefined,
885
- duration: videoInfo.duration,
886
- fileSize: videoInfo.fileSize,
887
- fromCache: false
888
- };
889
- callbacks?.onComplete?.({
890
- success: true,
891
- outputPath: result.outputPath
892
- });
893
- return result;
894
- }
895
- catch (error) {
896
- const errorMessage = error instanceof Error ? error.message : '未知错误';
897
- const result = {
898
- success: false,
899
- error: errorMessage
900
- };
901
- callbacks?.onComplete?.({
902
- success: false,
903
- error: errorMessage
904
- });
905
- return result;
906
- }
907
- }
908
- /**
909
- * 清除指定目录的所有缓存文件
910
- */
911
- clearCache(outputDir) {
912
- try {
913
- const files = fs.readdirSync(outputDir);
914
- files.forEach(file => {
915
- const filePath = path.join(outputDir, file);
916
- try {
917
- fs.unlinkSync(filePath);
918
- }
919
- catch (error) {
920
- // 忽略单个文件删除错误
921
- }
922
- });
923
- console.log('🧹 缓存文件已清空');
924
- }
925
- catch (error) {
926
- console.log('⚠️ 清理缓存时发生错误:', error instanceof Error ? error.message : '未知错误');
927
- }
928
- }
929
- /**
930
- * 获取缓存统计信息
931
- */
932
- getCacheStats(outputDir) {
933
- try {
934
- const files = fs.readdirSync(outputDir);
935
- const entries = files.map(file => {
936
- const filePath = path.join(outputDir, file);
937
- try {
938
- const stats = fs.statSync(filePath);
939
- return {
940
- fileName: file,
941
- filePath: filePath,
942
- fileSize: stats.size,
943
- mtime: stats.mtimeMs
944
- };
945
- }
946
- catch {
947
- return null;
948
- }
949
- }).filter((entry) => entry !== null);
950
- return {
951
- size: entries.length,
952
- entries
953
- };
954
- }
955
- catch (error) {
956
- return { size: 0, entries: [] };
957
- }
958
- }
959
- /**
960
- * 删除指定URL的缓存文件
961
- */
962
- removeCache(options) {
963
- try {
964
- const cacheFileName = this.generateCacheFileName(options);
965
- const cacheFilePath = path.join(options.outputDir, cacheFileName);
966
- if (fs.existsSync(cacheFilePath)) {
967
- fs.unlinkSync(cacheFilePath);
968
- console.log(`🗑️ 删除缓存文件: ${cacheFileName}`);
969
- return true;
970
- }
971
- return false;
972
- }
973
- catch (error) {
974
- console.log('⚠️ 删除缓存文件时发生错误:', error instanceof Error ? error.message : '未知错误');
975
- return false;
976
- }
977
- }
978
- }
979
- /**
980
- * 下载视频并转码
981
- * @param options 下载和转码选项
982
- * @param callbacks 进度回调
983
- * @returns 转码结果
984
- */
985
- const downloadVideo = async (options, callbacks) => {
986
- const downloader = VideoDownloader.getInstance();
987
- return downloader.downloadAndTranscode(options, callbacks);
988
- };
989
- /**
990
- * 清除指定目录的所有缓存文件
991
- */
992
- const clearVideoCache = (outputDir) => {
993
- const downloader = VideoDownloader.getInstance();
994
- downloader.clearCache(outputDir);
995
- };
996
- /**
997
- * 获取缓存统计信息
998
- */
999
- const getVideoCacheStats = (outputDir) => {
1000
- const downloader = VideoDownloader.getInstance();
1001
- return downloader.getCacheStats(outputDir);
1002
- };
1003
- /**
1004
- * 删除指定URL的缓存文件
1005
- */
1006
- const removeVideoCache = (options) => {
1007
- const downloader = VideoDownloader.getInstance();
1008
- return downloader.removeCache(options);
1009
- };
1010
327
  /**
1011
328
  * 获取定制Session
1012
329
  */
@@ -1032,18 +349,13 @@ const index = new Proxy({}, {
1032
349
  }
1033
350
  });
1034
351
 
1035
- exports.ResourceCache = ResourceCache;
1036
- exports.clearVideoCache = clearVideoCache;
352
+ exports.ResourceCache = resourceCache.ResourceCache;
1037
353
  exports.default = index;
1038
- exports.downloadVideo = downloadVideo;
1039
354
  exports.enable = enable;
1040
355
  exports.getCustomSession = getCustomSession;
1041
- exports.getFileUrl = getFileUrl;
1042
356
  exports.getRandomUUID = getRandomUUID;
1043
357
  exports.getSDK = getSDK;
1044
- exports.getVideoCacheStats = getVideoCacheStats;
1045
358
  exports.initialize = initialize;
1046
359
  exports.registerProtocol = registerProtocol;
1047
- exports.removeVideoCache = removeVideoCache;
1048
360
  exports.serve = serve;
1049
361
  //# sourceMappingURL=index.js.map