@huyooo/file-explorer-core 0.3.0 → 0.4.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.
- package/dist/index.cjs +1380 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +427 -12
- package/dist/index.d.ts +427 -12
- package/dist/index.js +1381 -127
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -34,19 +34,29 @@ __export(index_exports, {
|
|
|
34
34
|
APP_PROTOCOL_PREFIX: () => APP_PROTOCOL_PREFIX,
|
|
35
35
|
APP_PROTOCOL_SCHEME: () => APP_PROTOCOL_SCHEME,
|
|
36
36
|
FileType: () => FileType,
|
|
37
|
+
MediaService: () => MediaService,
|
|
37
38
|
SqliteThumbnailDatabase: () => SqliteThumbnailDatabase,
|
|
38
39
|
ThumbnailService: () => ThumbnailService,
|
|
40
|
+
WatchManager: () => WatchManager,
|
|
41
|
+
cleanupAllTranscodedFiles: () => cleanupAllTranscodedFiles,
|
|
42
|
+
cleanupTranscodedFile: () => cleanupTranscodedFile,
|
|
43
|
+
closeThumbnailDatabase: () => closeThumbnailDatabase,
|
|
44
|
+
compressFiles: () => compressFiles,
|
|
39
45
|
copyFiles: () => copyFiles,
|
|
40
46
|
copyFilesToClipboard: () => copyFilesToClipboard,
|
|
41
47
|
createFfmpegVideoProcessor: () => createFfmpegVideoProcessor,
|
|
42
48
|
createFile: () => createFile,
|
|
43
49
|
createFolder: () => createFolder,
|
|
50
|
+
createMediaService: () => createMediaService,
|
|
44
51
|
createSharpImageProcessor: () => createSharpImageProcessor,
|
|
45
52
|
createSqliteThumbnailDatabase: () => createSqliteThumbnailDatabase,
|
|
46
53
|
decodeFileUrl: () => decodeFileUrl,
|
|
47
54
|
deleteFiles: () => deleteFiles,
|
|
55
|
+
detectArchiveFormat: () => detectArchiveFormat,
|
|
56
|
+
detectTranscodeNeeds: () => detectTranscodeNeeds,
|
|
48
57
|
encodeFileUrl: () => encodeFileUrl,
|
|
49
58
|
exists: () => exists,
|
|
59
|
+
extractArchive: () => extractArchive,
|
|
50
60
|
formatDate: () => formatDate,
|
|
51
61
|
formatDateTime: () => formatDateTime,
|
|
52
62
|
formatFileSize: () => formatFileSize,
|
|
@@ -54,28 +64,39 @@ __export(index_exports, {
|
|
|
54
64
|
getApplicationIcon: () => getApplicationIcon,
|
|
55
65
|
getClipboardFiles: () => getClipboardFiles,
|
|
56
66
|
getFileHash: () => getFileHash,
|
|
57
|
-
getFileHashes: () => getFileHashes,
|
|
58
67
|
getFileInfo: () => getFileInfo,
|
|
59
68
|
getFileType: () => getFileType,
|
|
60
69
|
getHomeDirectory: () => getHomeDirectory,
|
|
61
|
-
|
|
70
|
+
getMediaFormat: () => getMediaFormat,
|
|
71
|
+
getMediaService: () => getMediaService,
|
|
72
|
+
getMediaTypeByExtension: () => getMediaTypeByExtension,
|
|
73
|
+
getPlatform: () => getPlatform2,
|
|
62
74
|
getSqliteThumbnailDatabase: () => getSqliteThumbnailDatabase,
|
|
63
75
|
getSystemPath: () => getSystemPath,
|
|
64
76
|
getThumbnailService: () => getThumbnailService,
|
|
77
|
+
getWatchManager: () => getWatchManager,
|
|
78
|
+
initMediaService: () => initMediaService,
|
|
65
79
|
initThumbnailService: () => initThumbnailService,
|
|
66
80
|
isAppProtocolUrl: () => isAppProtocolUrl,
|
|
81
|
+
isArchiveFile: () => isArchiveFile,
|
|
67
82
|
isDirectory: () => isDirectory,
|
|
68
83
|
isMediaFile: () => isMediaFile,
|
|
69
84
|
isPreviewable: () => isPreviewable,
|
|
70
85
|
moveFiles: () => moveFiles,
|
|
86
|
+
openInEditor: () => openInEditor,
|
|
87
|
+
openInTerminal: () => openInTerminal,
|
|
71
88
|
pasteFiles: () => pasteFiles,
|
|
72
89
|
readDirectory: () => readDirectory,
|
|
73
90
|
readFileContent: () => readFileContent,
|
|
74
91
|
readImageAsBase64: () => readImageAsBase64,
|
|
75
92
|
renameFile: () => renameFile,
|
|
93
|
+
revealInFileManager: () => revealInFileManager,
|
|
76
94
|
searchFiles: () => searchFiles,
|
|
77
95
|
searchFilesStream: () => searchFilesStream,
|
|
78
96
|
searchFilesSync: () => searchFilesSync,
|
|
97
|
+
showFileInfo: () => showFileInfo,
|
|
98
|
+
transcodeMedia: () => transcodeMedia,
|
|
99
|
+
watchDirectory: () => watchDirectory,
|
|
79
100
|
writeFileContent: () => writeFileContent
|
|
80
101
|
});
|
|
81
102
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -107,22 +128,22 @@ function encodeFileUrl(filePath) {
|
|
|
107
128
|
}
|
|
108
129
|
function decodeFileUrl(url) {
|
|
109
130
|
if (!url) return "";
|
|
110
|
-
let
|
|
111
|
-
if (
|
|
112
|
-
|
|
131
|
+
let path18 = url;
|
|
132
|
+
if (path18.startsWith(APP_PROTOCOL_PREFIX)) {
|
|
133
|
+
path18 = path18.slice(APP_PROTOCOL_PREFIX.length);
|
|
113
134
|
}
|
|
114
|
-
const hashIndex =
|
|
135
|
+
const hashIndex = path18.indexOf("#");
|
|
115
136
|
if (hashIndex !== -1) {
|
|
116
|
-
|
|
137
|
+
path18 = path18.substring(0, hashIndex);
|
|
117
138
|
}
|
|
118
|
-
const queryIndex =
|
|
139
|
+
const queryIndex = path18.indexOf("?");
|
|
119
140
|
if (queryIndex !== -1) {
|
|
120
|
-
|
|
141
|
+
path18 = path18.substring(0, queryIndex);
|
|
121
142
|
}
|
|
122
143
|
try {
|
|
123
|
-
return decodeURIComponent(
|
|
144
|
+
return decodeURIComponent(path18);
|
|
124
145
|
} catch {
|
|
125
|
-
return
|
|
146
|
+
return path18;
|
|
126
147
|
}
|
|
127
148
|
}
|
|
128
149
|
function isAppProtocolUrl(url) {
|
|
@@ -470,16 +491,16 @@ async function createFile(filePath, content = "") {
|
|
|
470
491
|
await import_node_fs2.promises.writeFile(filePath, content, "utf-8");
|
|
471
492
|
return { success: true, data: { finalPath: filePath } };
|
|
472
493
|
}
|
|
473
|
-
const
|
|
494
|
+
const dirname3 = import_node_path3.default.dirname(filePath);
|
|
474
495
|
const ext = import_node_path3.default.extname(filePath);
|
|
475
|
-
const
|
|
496
|
+
const basename2 = import_node_path3.default.basename(filePath, ext);
|
|
476
497
|
let counter = 2;
|
|
477
|
-
let finalPath = import_node_path3.default.join(
|
|
498
|
+
let finalPath = import_node_path3.default.join(dirname3, `${basename2} ${counter}${ext}`);
|
|
478
499
|
while (true) {
|
|
479
500
|
try {
|
|
480
501
|
await import_node_fs2.promises.access(finalPath);
|
|
481
502
|
counter++;
|
|
482
|
-
finalPath = import_node_path3.default.join(
|
|
503
|
+
finalPath = import_node_path3.default.join(dirname3, `${basename2} ${counter}${ext}`);
|
|
483
504
|
} catch {
|
|
484
505
|
break;
|
|
485
506
|
}
|
|
@@ -655,6 +676,468 @@ async function isDirectory(filePath) {
|
|
|
655
676
|
}
|
|
656
677
|
}
|
|
657
678
|
|
|
679
|
+
// src/operations/shell.ts
|
|
680
|
+
var import_node_child_process = require("child_process");
|
|
681
|
+
var import_node_util = require("util");
|
|
682
|
+
var path6 = __toESM(require("path"), 1);
|
|
683
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
|
|
684
|
+
function getPlatform() {
|
|
685
|
+
switch (process.platform) {
|
|
686
|
+
case "darwin":
|
|
687
|
+
return "mac";
|
|
688
|
+
case "win32":
|
|
689
|
+
return "windows";
|
|
690
|
+
default:
|
|
691
|
+
return "linux";
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
async function showFileInfo(filePath) {
|
|
695
|
+
const platform2 = getPlatform();
|
|
696
|
+
try {
|
|
697
|
+
switch (platform2) {
|
|
698
|
+
case "mac": {
|
|
699
|
+
const script = `tell application "Finder"
|
|
700
|
+
activate
|
|
701
|
+
set theFile to POSIX file "${filePath}" as alias
|
|
702
|
+
open information window of theFile
|
|
703
|
+
end tell`;
|
|
704
|
+
await execAsync(`osascript -e '${script}'`);
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
case "windows": {
|
|
708
|
+
const escapedPath = filePath.replace(/'/g, "''");
|
|
709
|
+
const psScript = `
|
|
710
|
+
$shell = New-Object -ComObject Shell.Application
|
|
711
|
+
$folder = $shell.Namespace((Split-Path '${escapedPath}'))
|
|
712
|
+
$item = $folder.ParseName((Split-Path '${escapedPath}' -Leaf))
|
|
713
|
+
$item.InvokeVerb('properties')
|
|
714
|
+
`;
|
|
715
|
+
await execAsync(`powershell -Command "${psScript.replace(/\n/g, " ")}"`);
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
case "linux": {
|
|
719
|
+
const commands = [
|
|
720
|
+
`nautilus --select "${filePath}" && nautilus -q`,
|
|
721
|
+
// GNOME
|
|
722
|
+
`dolphin --select "${filePath}"`,
|
|
723
|
+
// KDE
|
|
724
|
+
`thunar --quit && thunar "${path6.dirname(filePath)}"`,
|
|
725
|
+
// XFCE
|
|
726
|
+
`xdg-open "${path6.dirname(filePath)}"`
|
|
727
|
+
// 通用
|
|
728
|
+
];
|
|
729
|
+
let lastError = null;
|
|
730
|
+
for (const cmd of commands) {
|
|
731
|
+
try {
|
|
732
|
+
await execAsync(cmd);
|
|
733
|
+
return { success: true };
|
|
734
|
+
} catch (e) {
|
|
735
|
+
lastError = e;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
throw lastError || new Error("No file manager found");
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return { success: true };
|
|
742
|
+
} catch (error) {
|
|
743
|
+
return {
|
|
744
|
+
success: false,
|
|
745
|
+
error: error instanceof Error ? error.message : String(error)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async function revealInFileManager(filePath) {
|
|
750
|
+
const platform2 = getPlatform();
|
|
751
|
+
try {
|
|
752
|
+
switch (platform2) {
|
|
753
|
+
case "mac": {
|
|
754
|
+
await execAsync(`open -R "${filePath}"`);
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
case "windows": {
|
|
758
|
+
await execAsync(`explorer.exe /select,"${filePath}"`);
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
case "linux": {
|
|
762
|
+
await execAsync(`xdg-open "${path6.dirname(filePath)}"`);
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return { success: true };
|
|
767
|
+
} catch (error) {
|
|
768
|
+
return {
|
|
769
|
+
success: false,
|
|
770
|
+
error: error instanceof Error ? error.message : String(error)
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async function openInTerminal(dirPath) {
|
|
775
|
+
const platform2 = getPlatform();
|
|
776
|
+
try {
|
|
777
|
+
switch (platform2) {
|
|
778
|
+
case "mac": {
|
|
779
|
+
try {
|
|
780
|
+
const itermScript = `tell application "iTerm"
|
|
781
|
+
activate
|
|
782
|
+
try
|
|
783
|
+
set newWindow to (create window with default profile)
|
|
784
|
+
tell current session of newWindow
|
|
785
|
+
write text "cd '${dirPath}'"
|
|
786
|
+
end tell
|
|
787
|
+
on error
|
|
788
|
+
tell current window
|
|
789
|
+
create tab with default profile
|
|
790
|
+
tell current session
|
|
791
|
+
write text "cd '${dirPath}'"
|
|
792
|
+
end tell
|
|
793
|
+
end tell
|
|
794
|
+
end try
|
|
795
|
+
end tell`;
|
|
796
|
+
await execAsync(`osascript -e '${itermScript}'`);
|
|
797
|
+
} catch {
|
|
798
|
+
const terminalScript = `tell application "Terminal"
|
|
799
|
+
activate
|
|
800
|
+
do script "cd '${dirPath}'"
|
|
801
|
+
end tell`;
|
|
802
|
+
await execAsync(`osascript -e '${terminalScript}'`);
|
|
803
|
+
}
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
case "windows": {
|
|
807
|
+
try {
|
|
808
|
+
await execAsync(`wt -d "${dirPath}"`);
|
|
809
|
+
} catch {
|
|
810
|
+
await execAsync(`start cmd /K "cd /d ${dirPath}"`);
|
|
811
|
+
}
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
case "linux": {
|
|
815
|
+
const terminals = [
|
|
816
|
+
`gnome-terminal --working-directory="${dirPath}"`,
|
|
817
|
+
`konsole --workdir "${dirPath}"`,
|
|
818
|
+
`xfce4-terminal --working-directory="${dirPath}"`,
|
|
819
|
+
`xterm -e "cd '${dirPath}' && $SHELL"`
|
|
820
|
+
];
|
|
821
|
+
let lastError = null;
|
|
822
|
+
for (const cmd of terminals) {
|
|
823
|
+
try {
|
|
824
|
+
await execAsync(cmd);
|
|
825
|
+
return { success: true };
|
|
826
|
+
} catch (e) {
|
|
827
|
+
lastError = e;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
throw lastError || new Error("No terminal found");
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return { success: true };
|
|
834
|
+
} catch (error) {
|
|
835
|
+
return {
|
|
836
|
+
success: false,
|
|
837
|
+
error: error instanceof Error ? error.message : String(error)
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function openInEditor(targetPath) {
|
|
842
|
+
const platform2 = getPlatform();
|
|
843
|
+
try {
|
|
844
|
+
const editors = platform2 === "mac" ? [
|
|
845
|
+
// macOS: 优先 Cursor,回退到 VSCode
|
|
846
|
+
`open -a "Cursor" "${targetPath}"`,
|
|
847
|
+
`open -a "Visual Studio Code" "${targetPath}"`,
|
|
848
|
+
`code "${targetPath}"`
|
|
849
|
+
] : platform2 === "windows" ? [
|
|
850
|
+
// Windows
|
|
851
|
+
`cursor "${targetPath}"`,
|
|
852
|
+
`code "${targetPath}"`
|
|
853
|
+
] : [
|
|
854
|
+
// Linux
|
|
855
|
+
`cursor "${targetPath}"`,
|
|
856
|
+
`code "${targetPath}"`
|
|
857
|
+
];
|
|
858
|
+
let lastError = null;
|
|
859
|
+
for (const cmd of editors) {
|
|
860
|
+
try {
|
|
861
|
+
await execAsync(cmd);
|
|
862
|
+
return { success: true };
|
|
863
|
+
} catch (e) {
|
|
864
|
+
lastError = e;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
throw lastError || new Error("No editor found");
|
|
868
|
+
} catch (error) {
|
|
869
|
+
return {
|
|
870
|
+
success: false,
|
|
871
|
+
error: error instanceof Error ? error.message : String(error)
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/operations/compress.ts
|
|
877
|
+
var compressing = __toESM(require("compressing"), 1);
|
|
878
|
+
var path7 = __toESM(require("path"), 1);
|
|
879
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
880
|
+
var import_node_fs7 = require("fs");
|
|
881
|
+
var import_promises = require("stream/promises");
|
|
882
|
+
function getExtension(format) {
|
|
883
|
+
switch (format) {
|
|
884
|
+
case "zip":
|
|
885
|
+
return ".zip";
|
|
886
|
+
case "tar":
|
|
887
|
+
return ".tar";
|
|
888
|
+
case "tgz":
|
|
889
|
+
return ".tar.gz";
|
|
890
|
+
case "tarbz2":
|
|
891
|
+
return ".tar.bz2";
|
|
892
|
+
default:
|
|
893
|
+
return ".zip";
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function detectArchiveFormat(filePath) {
|
|
897
|
+
const lowerPath = filePath.toLowerCase();
|
|
898
|
+
if (lowerPath.endsWith(".zip")) return "zip";
|
|
899
|
+
if (lowerPath.endsWith(".tar.gz") || lowerPath.endsWith(".tgz")) return "tgz";
|
|
900
|
+
if (lowerPath.endsWith(".tar.bz2") || lowerPath.endsWith(".tbz2")) return "tarbz2";
|
|
901
|
+
if (lowerPath.endsWith(".tar")) return "tar";
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
function isArchiveFile(filePath) {
|
|
905
|
+
return detectArchiveFormat(filePath) !== null;
|
|
906
|
+
}
|
|
907
|
+
async function getAllFiles(dirPath) {
|
|
908
|
+
const files = [];
|
|
909
|
+
const entries = await import_node_fs7.promises.readdir(dirPath, { withFileTypes: true });
|
|
910
|
+
for (const entry of entries) {
|
|
911
|
+
const fullPath = path7.join(dirPath, entry.name);
|
|
912
|
+
if (entry.isDirectory()) {
|
|
913
|
+
files.push(...await getAllFiles(fullPath));
|
|
914
|
+
} else {
|
|
915
|
+
files.push(fullPath);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return files;
|
|
919
|
+
}
|
|
920
|
+
async function countFiles(sources) {
|
|
921
|
+
let count = 0;
|
|
922
|
+
for (const source of sources) {
|
|
923
|
+
const stats = await import_node_fs7.promises.stat(source);
|
|
924
|
+
if (stats.isDirectory()) {
|
|
925
|
+
const files = await getAllFiles(source);
|
|
926
|
+
count += files.length;
|
|
927
|
+
} else {
|
|
928
|
+
count += 1;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return count;
|
|
932
|
+
}
|
|
933
|
+
async function compressFiles(sources, options, onProgress) {
|
|
934
|
+
try {
|
|
935
|
+
const { format, level = "normal", outputName, outputDir, deleteSource } = options;
|
|
936
|
+
const ext = getExtension(format);
|
|
937
|
+
const finalName = outputName.endsWith(ext) ? outputName : outputName + ext;
|
|
938
|
+
const outputPath = path7.join(outputDir, finalName);
|
|
939
|
+
await import_node_fs7.promises.mkdir(outputDir, { recursive: true });
|
|
940
|
+
const totalCount = await countFiles(sources);
|
|
941
|
+
let processedCount = 0;
|
|
942
|
+
switch (format) {
|
|
943
|
+
case "zip": {
|
|
944
|
+
const stream = new compressing.zip.Stream();
|
|
945
|
+
for (const source of sources) {
|
|
946
|
+
const stats = await import_node_fs7.promises.stat(source);
|
|
947
|
+
const baseName = path7.basename(source);
|
|
948
|
+
if (stats.isDirectory()) {
|
|
949
|
+
const files = await getAllFiles(source);
|
|
950
|
+
for (const file of files) {
|
|
951
|
+
const relativePath = path7.relative(path7.dirname(source), file);
|
|
952
|
+
stream.addEntry(file, { relativePath });
|
|
953
|
+
processedCount++;
|
|
954
|
+
onProgress?.({
|
|
955
|
+
currentFile: path7.basename(file),
|
|
956
|
+
processedCount,
|
|
957
|
+
totalCount,
|
|
958
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
} else {
|
|
962
|
+
stream.addEntry(source, { relativePath: baseName });
|
|
963
|
+
processedCount++;
|
|
964
|
+
onProgress?.({
|
|
965
|
+
currentFile: baseName,
|
|
966
|
+
processedCount,
|
|
967
|
+
totalCount,
|
|
968
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const destStream = fs7.createWriteStream(outputPath);
|
|
973
|
+
await (0, import_promises.pipeline)(stream, destStream);
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
case "tar": {
|
|
977
|
+
const stream = new compressing.tar.Stream();
|
|
978
|
+
for (const source of sources) {
|
|
979
|
+
const stats = await import_node_fs7.promises.stat(source);
|
|
980
|
+
const baseName = path7.basename(source);
|
|
981
|
+
if (stats.isDirectory()) {
|
|
982
|
+
const files = await getAllFiles(source);
|
|
983
|
+
for (const file of files) {
|
|
984
|
+
const relativePath = path7.relative(path7.dirname(source), file);
|
|
985
|
+
stream.addEntry(file, { relativePath });
|
|
986
|
+
processedCount++;
|
|
987
|
+
onProgress?.({
|
|
988
|
+
currentFile: path7.basename(file),
|
|
989
|
+
processedCount,
|
|
990
|
+
totalCount,
|
|
991
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
} else {
|
|
995
|
+
stream.addEntry(source, { relativePath: baseName });
|
|
996
|
+
processedCount++;
|
|
997
|
+
onProgress?.({
|
|
998
|
+
currentFile: baseName,
|
|
999
|
+
processedCount,
|
|
1000
|
+
totalCount,
|
|
1001
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const destStream = fs7.createWriteStream(outputPath);
|
|
1006
|
+
await (0, import_promises.pipeline)(stream, destStream);
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
case "tgz": {
|
|
1010
|
+
const stream = new compressing.tgz.Stream();
|
|
1011
|
+
for (const source of sources) {
|
|
1012
|
+
const stats = await import_node_fs7.promises.stat(source);
|
|
1013
|
+
const baseName = path7.basename(source);
|
|
1014
|
+
if (stats.isDirectory()) {
|
|
1015
|
+
const files = await getAllFiles(source);
|
|
1016
|
+
for (const file of files) {
|
|
1017
|
+
const relativePath = path7.relative(path7.dirname(source), file);
|
|
1018
|
+
stream.addEntry(file, { relativePath });
|
|
1019
|
+
processedCount++;
|
|
1020
|
+
onProgress?.({
|
|
1021
|
+
currentFile: path7.basename(file),
|
|
1022
|
+
processedCount,
|
|
1023
|
+
totalCount,
|
|
1024
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
stream.addEntry(source, { relativePath: baseName });
|
|
1029
|
+
processedCount++;
|
|
1030
|
+
onProgress?.({
|
|
1031
|
+
currentFile: baseName,
|
|
1032
|
+
processedCount,
|
|
1033
|
+
totalCount,
|
|
1034
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
const destStream = fs7.createWriteStream(outputPath);
|
|
1039
|
+
await (0, import_promises.pipeline)(stream, destStream);
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
case "tarbz2": {
|
|
1043
|
+
console.warn("tar.bz2 format not fully supported, using tgz instead");
|
|
1044
|
+
const tgzStream = new compressing.tgz.Stream();
|
|
1045
|
+
for (const source of sources) {
|
|
1046
|
+
const stats = await import_node_fs7.promises.stat(source);
|
|
1047
|
+
const baseName = path7.basename(source);
|
|
1048
|
+
if (stats.isDirectory()) {
|
|
1049
|
+
const files = await getAllFiles(source);
|
|
1050
|
+
for (const file of files) {
|
|
1051
|
+
const relativePath = path7.relative(path7.dirname(source), file);
|
|
1052
|
+
tgzStream.addEntry(file, { relativePath });
|
|
1053
|
+
processedCount++;
|
|
1054
|
+
onProgress?.({
|
|
1055
|
+
currentFile: path7.basename(file),
|
|
1056
|
+
processedCount,
|
|
1057
|
+
totalCount,
|
|
1058
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
} else {
|
|
1062
|
+
tgzStream.addEntry(source, { relativePath: baseName });
|
|
1063
|
+
processedCount++;
|
|
1064
|
+
onProgress?.({
|
|
1065
|
+
currentFile: baseName,
|
|
1066
|
+
processedCount,
|
|
1067
|
+
totalCount,
|
|
1068
|
+
percent: Math.round(processedCount / totalCount * 100)
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
const destStream = fs7.createWriteStream(outputPath.replace(".tar.bz2", ".tar.gz"));
|
|
1073
|
+
await (0, import_promises.pipeline)(tgzStream, destStream);
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (deleteSource) {
|
|
1078
|
+
for (const source of sources) {
|
|
1079
|
+
const stats = await import_node_fs7.promises.stat(source);
|
|
1080
|
+
if (stats.isDirectory()) {
|
|
1081
|
+
await import_node_fs7.promises.rm(source, { recursive: true });
|
|
1082
|
+
} else {
|
|
1083
|
+
await import_node_fs7.promises.unlink(source);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return { success: true, outputPath };
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
return {
|
|
1090
|
+
success: false,
|
|
1091
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
async function extractArchive(archivePath, options, onProgress) {
|
|
1096
|
+
try {
|
|
1097
|
+
const { targetDir, deleteArchive } = options;
|
|
1098
|
+
const format = detectArchiveFormat(archivePath);
|
|
1099
|
+
if (!format) {
|
|
1100
|
+
return { success: false, error: "\u4E0D\u652F\u6301\u7684\u538B\u7F29\u683C\u5F0F" };
|
|
1101
|
+
}
|
|
1102
|
+
await import_node_fs7.promises.mkdir(targetDir, { recursive: true });
|
|
1103
|
+
onProgress?.({
|
|
1104
|
+
currentFile: path7.basename(archivePath),
|
|
1105
|
+
processedCount: 0,
|
|
1106
|
+
totalCount: 1,
|
|
1107
|
+
percent: 0
|
|
1108
|
+
});
|
|
1109
|
+
switch (format) {
|
|
1110
|
+
case "zip":
|
|
1111
|
+
await compressing.zip.uncompress(archivePath, targetDir);
|
|
1112
|
+
break;
|
|
1113
|
+
case "tar":
|
|
1114
|
+
await compressing.tar.uncompress(archivePath, targetDir);
|
|
1115
|
+
break;
|
|
1116
|
+
case "tgz":
|
|
1117
|
+
await compressing.tgz.uncompress(archivePath, targetDir);
|
|
1118
|
+
break;
|
|
1119
|
+
case "tarbz2":
|
|
1120
|
+
console.warn("tar.bz2 format not fully supported");
|
|
1121
|
+
return { success: false, error: "tar.bz2 \u683C\u5F0F\u6682\u4E0D\u652F\u6301" };
|
|
1122
|
+
}
|
|
1123
|
+
onProgress?.({
|
|
1124
|
+
currentFile: path7.basename(archivePath),
|
|
1125
|
+
processedCount: 1,
|
|
1126
|
+
totalCount: 1,
|
|
1127
|
+
percent: 100
|
|
1128
|
+
});
|
|
1129
|
+
if (deleteArchive) {
|
|
1130
|
+
await import_node_fs7.promises.unlink(archivePath);
|
|
1131
|
+
}
|
|
1132
|
+
return { success: true, outputPath: targetDir };
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
return {
|
|
1135
|
+
success: false,
|
|
1136
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
658
1141
|
// src/system-paths.ts
|
|
659
1142
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
660
1143
|
var import_node_os = __toESM(require("os"), 1);
|
|
@@ -705,14 +1188,14 @@ function getAllSystemPaths() {
|
|
|
705
1188
|
function getHomeDirectory() {
|
|
706
1189
|
return import_node_os.default.homedir();
|
|
707
1190
|
}
|
|
708
|
-
function
|
|
1191
|
+
function getPlatform2() {
|
|
709
1192
|
return process.platform;
|
|
710
1193
|
}
|
|
711
1194
|
|
|
712
1195
|
// src/search.ts
|
|
713
1196
|
var import_fdir = require("fdir");
|
|
714
1197
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
715
|
-
var
|
|
1198
|
+
var import_node_fs8 = require("fs");
|
|
716
1199
|
function patternToRegex(pattern) {
|
|
717
1200
|
return new RegExp(pattern.replace(/\*/g, ".*"), "i");
|
|
718
1201
|
}
|
|
@@ -736,7 +1219,7 @@ async function searchFilesStream(searchPath, pattern, onResults, maxResults = 10
|
|
|
736
1219
|
async function searchDir(dirPath) {
|
|
737
1220
|
if (stopped) return;
|
|
738
1221
|
try {
|
|
739
|
-
const entries = await
|
|
1222
|
+
const entries = await import_node_fs8.promises.readdir(dirPath, { withFileTypes: true });
|
|
740
1223
|
const matched = [];
|
|
741
1224
|
const subdirs = [];
|
|
742
1225
|
for (const entry of entries) {
|
|
@@ -784,38 +1267,23 @@ function searchFilesSync(searchPath, pattern, maxDepth) {
|
|
|
784
1267
|
}
|
|
785
1268
|
|
|
786
1269
|
// src/hash.ts
|
|
787
|
-
var
|
|
788
|
-
var
|
|
789
|
-
async function getFileHash(filePath) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
return (0, import_node_crypto.createHash)("md5").update(hashInput).digest("hex");
|
|
794
|
-
} catch (error) {
|
|
795
|
-
console.error(`Error computing hash for ${filePath}:`, error);
|
|
796
|
-
return (0, import_node_crypto.createHash)("md5").update(filePath).digest("hex");
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
async function getFileHashes(filePaths) {
|
|
800
|
-
const hashMap = /* @__PURE__ */ new Map();
|
|
801
|
-
await Promise.allSettled(
|
|
802
|
-
filePaths.map(async (filePath) => {
|
|
803
|
-
const hash = await getFileHash(filePath);
|
|
804
|
-
hashMap.set(filePath, hash);
|
|
805
|
-
})
|
|
806
|
-
);
|
|
807
|
-
return hashMap;
|
|
1270
|
+
var import_hash_wasm = require("hash-wasm");
|
|
1271
|
+
var import_promises2 = require("fs/promises");
|
|
1272
|
+
async function getFileHash(filePath, stats) {
|
|
1273
|
+
const fileStats = stats || await (0, import_promises2.stat)(filePath);
|
|
1274
|
+
const hashInput = `${filePath}:${fileStats.size}:${fileStats.mtime.getTime()}`;
|
|
1275
|
+
return await (0, import_hash_wasm.xxhash64)(hashInput);
|
|
808
1276
|
}
|
|
809
1277
|
|
|
810
1278
|
// src/clipboard.ts
|
|
811
|
-
var
|
|
1279
|
+
var import_node_fs9 = require("fs");
|
|
812
1280
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
813
1281
|
async function copyFilesToClipboard(filePaths, clipboard) {
|
|
814
1282
|
try {
|
|
815
1283
|
const cleanPaths = [];
|
|
816
1284
|
for (const p of filePaths) {
|
|
817
1285
|
try {
|
|
818
|
-
await
|
|
1286
|
+
await import_node_fs9.promises.access(p);
|
|
819
1287
|
cleanPaths.push(p);
|
|
820
1288
|
} catch {
|
|
821
1289
|
}
|
|
@@ -855,7 +1323,7 @@ async function pasteFiles(targetDir, sourcePaths) {
|
|
|
855
1323
|
let counter = 1;
|
|
856
1324
|
while (true) {
|
|
857
1325
|
try {
|
|
858
|
-
await
|
|
1326
|
+
await import_node_fs9.promises.access(destPath);
|
|
859
1327
|
const ext = import_node_path8.default.extname(fileName);
|
|
860
1328
|
const baseName = import_node_path8.default.basename(fileName, ext);
|
|
861
1329
|
destPath = import_node_path8.default.join(targetDir, `${baseName} ${++counter}${ext}`);
|
|
@@ -863,11 +1331,11 @@ async function pasteFiles(targetDir, sourcePaths) {
|
|
|
863
1331
|
break;
|
|
864
1332
|
}
|
|
865
1333
|
}
|
|
866
|
-
const stats = await
|
|
1334
|
+
const stats = await import_node_fs9.promises.stat(sourcePath);
|
|
867
1335
|
if (stats.isDirectory()) {
|
|
868
1336
|
await copyDirectory2(sourcePath, destPath);
|
|
869
1337
|
} else {
|
|
870
|
-
await
|
|
1338
|
+
await import_node_fs9.promises.copyFile(sourcePath, destPath);
|
|
871
1339
|
}
|
|
872
1340
|
pastedPaths.push(destPath);
|
|
873
1341
|
}
|
|
@@ -877,26 +1345,26 @@ async function pasteFiles(targetDir, sourcePaths) {
|
|
|
877
1345
|
}
|
|
878
1346
|
}
|
|
879
1347
|
async function copyDirectory2(source, dest) {
|
|
880
|
-
await
|
|
881
|
-
const entries = await
|
|
1348
|
+
await import_node_fs9.promises.mkdir(dest, { recursive: true });
|
|
1349
|
+
const entries = await import_node_fs9.promises.readdir(source, { withFileTypes: true });
|
|
882
1350
|
for (const entry of entries) {
|
|
883
1351
|
const sourcePath = import_node_path8.default.join(source, entry.name);
|
|
884
1352
|
const destPath = import_node_path8.default.join(dest, entry.name);
|
|
885
1353
|
if (entry.isDirectory()) {
|
|
886
1354
|
await copyDirectory2(sourcePath, destPath);
|
|
887
1355
|
} else {
|
|
888
|
-
await
|
|
1356
|
+
await import_node_fs9.promises.copyFile(sourcePath, destPath);
|
|
889
1357
|
}
|
|
890
1358
|
}
|
|
891
1359
|
}
|
|
892
1360
|
|
|
893
1361
|
// src/application-icon.ts
|
|
894
|
-
var
|
|
1362
|
+
var import_node_fs10 = require("fs");
|
|
895
1363
|
var import_node_path9 = __toESM(require("path"), 1);
|
|
896
1364
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
897
|
-
var
|
|
898
|
-
var
|
|
899
|
-
var
|
|
1365
|
+
var import_node_child_process2 = require("child_process");
|
|
1366
|
+
var import_node_util2 = require("util");
|
|
1367
|
+
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.exec);
|
|
900
1368
|
async function findAppIconPath(appPath) {
|
|
901
1369
|
const resourcesPath = import_node_path9.default.join(appPath, "Contents", "Resources");
|
|
902
1370
|
try {
|
|
@@ -908,7 +1376,7 @@ async function findAppIconPath(appPath) {
|
|
|
908
1376
|
];
|
|
909
1377
|
const infoPlistPath = import_node_path9.default.join(appPath, "Contents", "Info.plist");
|
|
910
1378
|
try {
|
|
911
|
-
const infoPlistContent = await
|
|
1379
|
+
const infoPlistContent = await import_node_fs10.promises.readFile(infoPlistPath, "utf-8");
|
|
912
1380
|
const iconFileMatch = infoPlistContent.match(/<key>CFBundleIconFile<\/key>\s*<string>([^<]+)<\/string>/);
|
|
913
1381
|
if (iconFileMatch && iconFileMatch[1]) {
|
|
914
1382
|
let iconFileName = iconFileMatch[1].trim();
|
|
@@ -917,7 +1385,7 @@ async function findAppIconPath(appPath) {
|
|
|
917
1385
|
}
|
|
918
1386
|
const iconPath = import_node_path9.default.join(resourcesPath, iconFileName);
|
|
919
1387
|
try {
|
|
920
|
-
await
|
|
1388
|
+
await import_node_fs10.promises.access(iconPath);
|
|
921
1389
|
return iconPath;
|
|
922
1390
|
} catch {
|
|
923
1391
|
}
|
|
@@ -927,14 +1395,14 @@ async function findAppIconPath(appPath) {
|
|
|
927
1395
|
for (const iconName of commonIconNames) {
|
|
928
1396
|
const iconPath = import_node_path9.default.join(resourcesPath, iconName);
|
|
929
1397
|
try {
|
|
930
|
-
await
|
|
1398
|
+
await import_node_fs10.promises.access(iconPath);
|
|
931
1399
|
return iconPath;
|
|
932
1400
|
} catch {
|
|
933
1401
|
continue;
|
|
934
1402
|
}
|
|
935
1403
|
}
|
|
936
1404
|
try {
|
|
937
|
-
const entries = await
|
|
1405
|
+
const entries = await import_node_fs10.promises.readdir(resourcesPath);
|
|
938
1406
|
const icnsFile = entries.find((entry) => entry.toLowerCase().endsWith(".icns"));
|
|
939
1407
|
if (icnsFile) {
|
|
940
1408
|
return import_node_path9.default.join(resourcesPath, icnsFile);
|
|
@@ -951,7 +1419,7 @@ async function getApplicationIcon(appPath) {
|
|
|
951
1419
|
return null;
|
|
952
1420
|
}
|
|
953
1421
|
try {
|
|
954
|
-
const stats = await
|
|
1422
|
+
const stats = await import_node_fs10.promises.stat(appPath);
|
|
955
1423
|
if (!stats.isDirectory() || !appPath.endsWith(".app")) {
|
|
956
1424
|
return null;
|
|
957
1425
|
}
|
|
@@ -964,12 +1432,12 @@ async function getApplicationIcon(appPath) {
|
|
|
964
1432
|
}
|
|
965
1433
|
try {
|
|
966
1434
|
const tempPngPath = import_node_path9.default.join(import_node_os2.default.tmpdir(), `app-icon-${Date.now()}.png`);
|
|
967
|
-
await
|
|
1435
|
+
await execAsync2(
|
|
968
1436
|
`sips -s format png "${iconPath}" --out "${tempPngPath}" --resampleHeightWidthMax 128`
|
|
969
1437
|
);
|
|
970
|
-
const pngBuffer = await
|
|
1438
|
+
const pngBuffer = await import_node_fs10.promises.readFile(tempPngPath);
|
|
971
1439
|
try {
|
|
972
|
-
await
|
|
1440
|
+
await import_node_fs10.promises.unlink(tempPngPath);
|
|
973
1441
|
} catch {
|
|
974
1442
|
}
|
|
975
1443
|
const base64 = pngBuffer.toString("base64");
|
|
@@ -979,11 +1447,150 @@ async function getApplicationIcon(appPath) {
|
|
|
979
1447
|
}
|
|
980
1448
|
}
|
|
981
1449
|
|
|
1450
|
+
// src/watch.ts
|
|
1451
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
1452
|
+
var path12 = __toESM(require("path"), 1);
|
|
1453
|
+
var debounceTimers = /* @__PURE__ */ new Map();
|
|
1454
|
+
var DEBOUNCE_DELAY = 100;
|
|
1455
|
+
function watchDirectory(dirPath, callback) {
|
|
1456
|
+
let watcher = null;
|
|
1457
|
+
try {
|
|
1458
|
+
watcher = fs11.watch(dirPath, { persistent: true }, (eventType, filename) => {
|
|
1459
|
+
if (!filename) return;
|
|
1460
|
+
const fullPath = path12.join(dirPath, filename);
|
|
1461
|
+
const key = `${dirPath}:${filename}`;
|
|
1462
|
+
const existingTimer = debounceTimers.get(key);
|
|
1463
|
+
if (existingTimer) {
|
|
1464
|
+
clearTimeout(existingTimer);
|
|
1465
|
+
}
|
|
1466
|
+
const timer = setTimeout(() => {
|
|
1467
|
+
debounceTimers.delete(key);
|
|
1468
|
+
fs11.access(fullPath, fs11.constants.F_OK, (err) => {
|
|
1469
|
+
let type;
|
|
1470
|
+
if (err) {
|
|
1471
|
+
type = "remove";
|
|
1472
|
+
} else if (eventType === "rename") {
|
|
1473
|
+
type = "add";
|
|
1474
|
+
} else {
|
|
1475
|
+
type = "change";
|
|
1476
|
+
}
|
|
1477
|
+
callback({
|
|
1478
|
+
type,
|
|
1479
|
+
path: fullPath,
|
|
1480
|
+
filename
|
|
1481
|
+
});
|
|
1482
|
+
});
|
|
1483
|
+
}, DEBOUNCE_DELAY);
|
|
1484
|
+
debounceTimers.set(key, timer);
|
|
1485
|
+
});
|
|
1486
|
+
watcher.on("error", (error) => {
|
|
1487
|
+
console.error("Watch error:", error);
|
|
1488
|
+
});
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
console.error("Failed to watch directory:", error);
|
|
1491
|
+
}
|
|
1492
|
+
return {
|
|
1493
|
+
close: () => {
|
|
1494
|
+
if (watcher) {
|
|
1495
|
+
watcher.close();
|
|
1496
|
+
watcher = null;
|
|
1497
|
+
}
|
|
1498
|
+
for (const [key, timer] of debounceTimers.entries()) {
|
|
1499
|
+
if (key.startsWith(`${dirPath}:`)) {
|
|
1500
|
+
clearTimeout(timer);
|
|
1501
|
+
debounceTimers.delete(key);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
path: dirPath
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
var WatchManager = class {
|
|
1509
|
+
watchers = /* @__PURE__ */ new Map();
|
|
1510
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
1511
|
+
/**
|
|
1512
|
+
* 开始监听目录
|
|
1513
|
+
*/
|
|
1514
|
+
watch(dirPath, callback) {
|
|
1515
|
+
const normalizedPath = path12.normalize(dirPath);
|
|
1516
|
+
let callbackSet = this.callbacks.get(normalizedPath);
|
|
1517
|
+
if (!callbackSet) {
|
|
1518
|
+
callbackSet = /* @__PURE__ */ new Set();
|
|
1519
|
+
this.callbacks.set(normalizedPath, callbackSet);
|
|
1520
|
+
}
|
|
1521
|
+
callbackSet.add(callback);
|
|
1522
|
+
let watcherInfo = this.watchers.get(normalizedPath);
|
|
1523
|
+
if (watcherInfo) {
|
|
1524
|
+
watcherInfo.refCount++;
|
|
1525
|
+
} else {
|
|
1526
|
+
const watcher = watchDirectory(normalizedPath, (event) => {
|
|
1527
|
+
const callbacks = this.callbacks.get(normalizedPath);
|
|
1528
|
+
if (callbacks) {
|
|
1529
|
+
for (const cb of callbacks) {
|
|
1530
|
+
try {
|
|
1531
|
+
cb(event);
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
console.error("Watch callback error:", error);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
watcherInfo = { watcher, refCount: 1 };
|
|
1539
|
+
this.watchers.set(normalizedPath, watcherInfo);
|
|
1540
|
+
}
|
|
1541
|
+
return () => {
|
|
1542
|
+
this.unwatch(normalizedPath, callback);
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* 停止监听
|
|
1547
|
+
*/
|
|
1548
|
+
unwatch(dirPath, callback) {
|
|
1549
|
+
const normalizedPath = path12.normalize(dirPath);
|
|
1550
|
+
const callbackSet = this.callbacks.get(normalizedPath);
|
|
1551
|
+
if (callbackSet) {
|
|
1552
|
+
callbackSet.delete(callback);
|
|
1553
|
+
if (callbackSet.size === 0) {
|
|
1554
|
+
this.callbacks.delete(normalizedPath);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const watcherInfo = this.watchers.get(normalizedPath);
|
|
1558
|
+
if (watcherInfo) {
|
|
1559
|
+
watcherInfo.refCount--;
|
|
1560
|
+
if (watcherInfo.refCount <= 0) {
|
|
1561
|
+
watcherInfo.watcher.close();
|
|
1562
|
+
this.watchers.delete(normalizedPath);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* 关闭所有监听器
|
|
1568
|
+
*/
|
|
1569
|
+
closeAll() {
|
|
1570
|
+
for (const [, watcherInfo] of this.watchers) {
|
|
1571
|
+
watcherInfo.watcher.close();
|
|
1572
|
+
}
|
|
1573
|
+
this.watchers.clear();
|
|
1574
|
+
this.callbacks.clear();
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
var globalWatchManager = null;
|
|
1578
|
+
function getWatchManager() {
|
|
1579
|
+
if (!globalWatchManager) {
|
|
1580
|
+
globalWatchManager = new WatchManager();
|
|
1581
|
+
}
|
|
1582
|
+
return globalWatchManager;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
982
1585
|
// src/thumbnail/service.ts
|
|
983
|
-
var
|
|
1586
|
+
var import_node_fs11 = require("fs");
|
|
984
1587
|
var import_node_path10 = __toESM(require("path"), 1);
|
|
985
|
-
|
|
986
|
-
|
|
1588
|
+
function isImageFile(filePath, fileType) {
|
|
1589
|
+
return fileType === "image" /* IMAGE */;
|
|
1590
|
+
}
|
|
1591
|
+
function isVideoFile(filePath, fileType) {
|
|
1592
|
+
return fileType === "video" /* VIDEO */;
|
|
1593
|
+
}
|
|
987
1594
|
var ThumbnailService = class {
|
|
988
1595
|
database;
|
|
989
1596
|
imageProcessor;
|
|
@@ -1002,7 +1609,7 @@ var ThumbnailService = class {
|
|
|
1002
1609
|
*/
|
|
1003
1610
|
async getCachedThumbnailUrl(filePath) {
|
|
1004
1611
|
try {
|
|
1005
|
-
const stats = await
|
|
1612
|
+
const stats = await import_node_fs11.promises.stat(filePath);
|
|
1006
1613
|
const fileType = getFileType(filePath, stats);
|
|
1007
1614
|
if (fileType === "application" /* APPLICATION */ && this.getApplicationIcon) {
|
|
1008
1615
|
return await this.getApplicationIcon(filePath);
|
|
@@ -1015,7 +1622,7 @@ var ThumbnailService = class {
|
|
|
1015
1622
|
if (cachedPath) {
|
|
1016
1623
|
return this.urlEncoder(cachedPath);
|
|
1017
1624
|
}
|
|
1018
|
-
getFileHash(filePath).then((fileHash) => {
|
|
1625
|
+
getFileHash(filePath, stats).then((fileHash) => {
|
|
1019
1626
|
this.generateThumbnail(filePath, fileHash, mtime).catch(() => {
|
|
1020
1627
|
});
|
|
1021
1628
|
}).catch(() => {
|
|
@@ -1030,7 +1637,7 @@ var ThumbnailService = class {
|
|
|
1030
1637
|
*/
|
|
1031
1638
|
async getThumbnailUrl(filePath) {
|
|
1032
1639
|
try {
|
|
1033
|
-
const stats = await
|
|
1640
|
+
const stats = await import_node_fs11.promises.stat(filePath);
|
|
1034
1641
|
const fileType = getFileType(filePath, stats);
|
|
1035
1642
|
if (fileType === "application" /* APPLICATION */ && this.getApplicationIcon) {
|
|
1036
1643
|
return await this.getApplicationIcon(filePath);
|
|
@@ -1043,7 +1650,7 @@ var ThumbnailService = class {
|
|
|
1043
1650
|
if (cachedPath) {
|
|
1044
1651
|
return this.urlEncoder(cachedPath);
|
|
1045
1652
|
}
|
|
1046
|
-
const fileHash = await getFileHash(filePath);
|
|
1653
|
+
const fileHash = await getFileHash(filePath, stats);
|
|
1047
1654
|
const thumbnailPath = await this.generateThumbnail(filePath, fileHash, mtime);
|
|
1048
1655
|
if (thumbnailPath) {
|
|
1049
1656
|
return this.urlEncoder(thumbnailPath);
|
|
@@ -1063,13 +1670,14 @@ var ThumbnailService = class {
|
|
|
1063
1670
|
return cachedPath;
|
|
1064
1671
|
}
|
|
1065
1672
|
try {
|
|
1066
|
-
const
|
|
1673
|
+
const stats = await import_node_fs11.promises.stat(filePath);
|
|
1674
|
+
const fileType = getFileType(filePath, stats);
|
|
1067
1675
|
const hashPrefix = fileHash.substring(0, 16);
|
|
1068
1676
|
const thumbnailFileName = `${hashPrefix}.jpg`;
|
|
1069
1677
|
const thumbnailPath = import_node_path10.default.join(this.database.getCacheDir(), thumbnailFileName);
|
|
1070
|
-
if (
|
|
1678
|
+
if (isImageFile(filePath, fileType) && this.imageProcessor) {
|
|
1071
1679
|
await this.imageProcessor.resize(filePath, thumbnailPath, 256);
|
|
1072
|
-
} else if (
|
|
1680
|
+
} else if (isVideoFile(filePath, fileType) && this.videoProcessor) {
|
|
1073
1681
|
await this.videoProcessor.screenshot(filePath, thumbnailPath, "00:00:01", "256x256");
|
|
1074
1682
|
} else {
|
|
1075
1683
|
return null;
|
|
@@ -1082,12 +1690,20 @@ var ThumbnailService = class {
|
|
|
1082
1690
|
}
|
|
1083
1691
|
}
|
|
1084
1692
|
/**
|
|
1085
|
-
*
|
|
1693
|
+
* 批量生成缩略图(带并发限制,避免资源耗尽)
|
|
1086
1694
|
*/
|
|
1087
|
-
async generateThumbnailsBatch(files) {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1695
|
+
async generateThumbnailsBatch(files, concurrency = 3) {
|
|
1696
|
+
const execute = async (file) => {
|
|
1697
|
+
try {
|
|
1698
|
+
await this.generateThumbnail(file.path, file.hash, file.mtime);
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
console.debug(`Failed to generate thumbnail for ${file.path}:`, error);
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
for (let i = 0; i < files.length; i += concurrency) {
|
|
1704
|
+
const batch = files.slice(i, i + concurrency);
|
|
1705
|
+
await Promise.allSettled(batch.map(execute));
|
|
1706
|
+
}
|
|
1091
1707
|
}
|
|
1092
1708
|
/**
|
|
1093
1709
|
* 删除缩略图
|
|
@@ -1114,14 +1730,19 @@ function getThumbnailService() {
|
|
|
1114
1730
|
// src/thumbnail/database.ts
|
|
1115
1731
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
1116
1732
|
var import_node_path11 = __toESM(require("path"), 1);
|
|
1117
|
-
var
|
|
1733
|
+
var import_node_fs12 = require("fs");
|
|
1118
1734
|
var SqliteThumbnailDatabase = class {
|
|
1119
1735
|
db = null;
|
|
1120
1736
|
cacheDir;
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1737
|
+
dbPath;
|
|
1738
|
+
constructor(userDataPath, options = {}) {
|
|
1739
|
+
const defaultDirName = options.dirName || "thumbnails";
|
|
1740
|
+
const defaultDbFileName = options.dbFileName || "thumbnails.db";
|
|
1741
|
+
const inferredDirFromDbPath = options.dbPath ? import_node_path11.default.dirname(options.dbPath) : null;
|
|
1742
|
+
this.cacheDir = options.thumbnailDir ? options.thumbnailDir : inferredDirFromDbPath || import_node_path11.default.join(userDataPath, defaultDirName);
|
|
1743
|
+
this.dbPath = options.dbPath ? options.dbPath : import_node_path11.default.join(this.cacheDir, defaultDbFileName);
|
|
1744
|
+
if (!(0, import_node_fs12.existsSync)(this.cacheDir)) {
|
|
1745
|
+
(0, import_node_fs12.mkdirSync)(this.cacheDir, { recursive: true });
|
|
1125
1746
|
}
|
|
1126
1747
|
}
|
|
1127
1748
|
/**
|
|
@@ -1129,8 +1750,7 @@ var SqliteThumbnailDatabase = class {
|
|
|
1129
1750
|
*/
|
|
1130
1751
|
init() {
|
|
1131
1752
|
if (this.db) return;
|
|
1132
|
-
|
|
1133
|
-
this.db = new import_better_sqlite3.default(dbPath, {
|
|
1753
|
+
this.db = new import_better_sqlite3.default(this.dbPath, {
|
|
1134
1754
|
fileMustExist: false
|
|
1135
1755
|
});
|
|
1136
1756
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -1162,7 +1782,7 @@ var SqliteThumbnailDatabase = class {
|
|
|
1162
1782
|
WHERE file_path = ? AND mtime = ?
|
|
1163
1783
|
`);
|
|
1164
1784
|
const row = stmt.get(filePath, mtime);
|
|
1165
|
-
if (row && (0,
|
|
1785
|
+
if (row && (0, import_node_fs12.existsSync)(row.thumbnail_path)) {
|
|
1166
1786
|
return row.thumbnail_path;
|
|
1167
1787
|
}
|
|
1168
1788
|
return null;
|
|
@@ -1175,7 +1795,7 @@ var SqliteThumbnailDatabase = class {
|
|
|
1175
1795
|
WHERE file_path = ? AND file_hash = ?
|
|
1176
1796
|
`);
|
|
1177
1797
|
const row = stmt.get(filePath, fileHash);
|
|
1178
|
-
if (row && row.mtime === mtime && (0,
|
|
1798
|
+
if (row && row.mtime === mtime && (0, import_node_fs12.existsSync)(row.thumbnail_path)) {
|
|
1179
1799
|
return row.thumbnail_path;
|
|
1180
1800
|
}
|
|
1181
1801
|
return null;
|
|
@@ -1202,15 +1822,19 @@ var SqliteThumbnailDatabase = class {
|
|
|
1202
1822
|
}
|
|
1203
1823
|
close() {
|
|
1204
1824
|
if (this.db) {
|
|
1825
|
+
try {
|
|
1826
|
+
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
1827
|
+
} catch (error) {
|
|
1828
|
+
}
|
|
1205
1829
|
this.db.close();
|
|
1206
1830
|
this.db = null;
|
|
1207
1831
|
}
|
|
1208
1832
|
}
|
|
1209
1833
|
};
|
|
1210
1834
|
var thumbnailDb = null;
|
|
1211
|
-
function createSqliteThumbnailDatabase(
|
|
1835
|
+
function createSqliteThumbnailDatabase(options) {
|
|
1212
1836
|
if (!thumbnailDb) {
|
|
1213
|
-
thumbnailDb = new SqliteThumbnailDatabase(userDataPath);
|
|
1837
|
+
thumbnailDb = new SqliteThumbnailDatabase(options.userDataPath, options);
|
|
1214
1838
|
thumbnailDb.init();
|
|
1215
1839
|
}
|
|
1216
1840
|
return thumbnailDb;
|
|
@@ -1218,59 +1842,699 @@ function createSqliteThumbnailDatabase(userDataPath) {
|
|
|
1218
1842
|
function getSqliteThumbnailDatabase() {
|
|
1219
1843
|
return thumbnailDb;
|
|
1220
1844
|
}
|
|
1845
|
+
function closeThumbnailDatabase() {
|
|
1846
|
+
if (thumbnailDb) {
|
|
1847
|
+
thumbnailDb.close();
|
|
1848
|
+
thumbnailDb = null;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1221
1851
|
|
|
1222
1852
|
// src/thumbnail/processors.ts
|
|
1223
|
-
var
|
|
1224
|
-
var import_node_util2 = require("util");
|
|
1225
|
-
var execFileAsync = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
1853
|
+
var import_node_child_process3 = require("child_process");
|
|
1226
1854
|
function createSharpImageProcessor(sharp) {
|
|
1227
1855
|
return {
|
|
1228
1856
|
async resize(filePath, outputPath, size) {
|
|
1229
|
-
await sharp(filePath).resize(
|
|
1230
|
-
|
|
1857
|
+
await sharp(filePath).resize({
|
|
1858
|
+
width: size,
|
|
1231
1859
|
withoutEnlargement: true
|
|
1232
|
-
}).jpeg({
|
|
1860
|
+
}).jpeg({
|
|
1861
|
+
quality: 80,
|
|
1862
|
+
optimiseCoding: true
|
|
1863
|
+
// 优化编码,提升压缩率
|
|
1864
|
+
}).toFile(outputPath);
|
|
1233
1865
|
}
|
|
1234
1866
|
};
|
|
1235
1867
|
}
|
|
1236
1868
|
function createFfmpegVideoProcessor(ffmpegPath) {
|
|
1237
1869
|
return {
|
|
1238
1870
|
async screenshot(filePath, outputPath, timestamp, size) {
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1871
|
+
const width = size.split("x")[0];
|
|
1872
|
+
return new Promise((resolve, reject) => {
|
|
1873
|
+
const ffmpeg = (0, import_node_child_process3.spawn)(ffmpegPath, [
|
|
1874
|
+
"-y",
|
|
1875
|
+
"-ss",
|
|
1876
|
+
timestamp,
|
|
1877
|
+
"-i",
|
|
1878
|
+
filePath,
|
|
1879
|
+
"-vframes",
|
|
1880
|
+
"1",
|
|
1881
|
+
"-vf",
|
|
1882
|
+
`thumbnail,scale=${width}:-1:force_original_aspect_ratio=decrease`,
|
|
1883
|
+
// 使用 mjpeg 编码器,性能更好(借鉴 pixflow)
|
|
1884
|
+
"-c:v",
|
|
1885
|
+
"mjpeg",
|
|
1886
|
+
"-q:v",
|
|
1887
|
+
"6",
|
|
1888
|
+
// 质量参数,6 表示中等质量(范围 2-31,数值越小质量越高)
|
|
1889
|
+
outputPath
|
|
1890
|
+
]);
|
|
1891
|
+
const timeout = setTimeout(() => {
|
|
1892
|
+
ffmpeg.kill();
|
|
1893
|
+
reject(new Error("Video thumbnail generation timeout"));
|
|
1894
|
+
}, 3e4);
|
|
1895
|
+
ffmpeg.stderr.on("data", (data) => {
|
|
1896
|
+
const output = data.toString();
|
|
1897
|
+
if (output.includes("Unsupported pixel format")) {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
ffmpeg.on("close", (code) => {
|
|
1902
|
+
clearTimeout(timeout);
|
|
1903
|
+
if (code === 0) {
|
|
1904
|
+
resolve();
|
|
1905
|
+
} else {
|
|
1906
|
+
reject(new Error(`ffmpeg exited with code ${code}`));
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
ffmpeg.on("error", (error) => {
|
|
1910
|
+
clearTimeout(timeout);
|
|
1911
|
+
reject(error);
|
|
1912
|
+
});
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// src/media/format-detector.ts
|
|
1919
|
+
var import_node_child_process4 = require("child_process");
|
|
1920
|
+
var import_node_util3 = require("util");
|
|
1921
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1922
|
+
var execFileAsync = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
|
|
1923
|
+
var BROWSER_VIDEO_CONTAINERS = /* @__PURE__ */ new Set(["mp4", "webm", "ogg", "ogv"]);
|
|
1924
|
+
var BROWSER_VIDEO_CODECS = /* @__PURE__ */ new Set(["h264", "avc1", "vp8", "vp9", "theora", "av1"]);
|
|
1925
|
+
var BROWSER_AUDIO_CODECS = /* @__PURE__ */ new Set(["aac", "mp3", "opus", "vorbis", "flac"]);
|
|
1926
|
+
var BROWSER_AUDIO_CONTAINERS = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "oga", "webm", "m4a", "aac", "flac"]);
|
|
1927
|
+
var BROWSER_AUDIO_ONLY_CODECS = /* @__PURE__ */ new Set(["mp3", "aac", "opus", "vorbis", "flac", "pcm_s16le", "pcm_s24le"]);
|
|
1928
|
+
var REMUXABLE_VIDEO_CODECS = /* @__PURE__ */ new Set(["h264", "avc1", "hevc", "h265"]);
|
|
1929
|
+
var REMUXABLE_AUDIO_CODECS = /* @__PURE__ */ new Set(["aac", "alac"]);
|
|
1930
|
+
var VIDEO_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
1931
|
+
"mp4",
|
|
1932
|
+
"mkv",
|
|
1933
|
+
"avi",
|
|
1934
|
+
"mov",
|
|
1935
|
+
"wmv",
|
|
1936
|
+
"flv",
|
|
1937
|
+
"webm",
|
|
1938
|
+
"ogv",
|
|
1939
|
+
"ogg",
|
|
1940
|
+
"m4v",
|
|
1941
|
+
"mpeg",
|
|
1942
|
+
"mpg",
|
|
1943
|
+
"3gp",
|
|
1944
|
+
"ts",
|
|
1945
|
+
"mts",
|
|
1946
|
+
"m2ts",
|
|
1947
|
+
"vob",
|
|
1948
|
+
"rmvb",
|
|
1949
|
+
"rm"
|
|
1950
|
+
]);
|
|
1951
|
+
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1952
|
+
"mp3",
|
|
1953
|
+
"wav",
|
|
1954
|
+
"flac",
|
|
1955
|
+
"aac",
|
|
1956
|
+
"m4a",
|
|
1957
|
+
"ogg",
|
|
1958
|
+
"oga",
|
|
1959
|
+
"wma",
|
|
1960
|
+
"ape",
|
|
1961
|
+
"alac",
|
|
1962
|
+
"aiff",
|
|
1963
|
+
"aif",
|
|
1964
|
+
"opus",
|
|
1965
|
+
"mid",
|
|
1966
|
+
"midi",
|
|
1967
|
+
"wv",
|
|
1968
|
+
"mka"
|
|
1969
|
+
]);
|
|
1970
|
+
function getMediaTypeByExtension(filePath) {
|
|
1971
|
+
const ext = import_node_path12.default.extname(filePath).toLowerCase().slice(1);
|
|
1972
|
+
if (VIDEO_EXTENSIONS2.has(ext)) return "video";
|
|
1973
|
+
if (AUDIO_EXTENSIONS.has(ext)) return "audio";
|
|
1974
|
+
return null;
|
|
1975
|
+
}
|
|
1976
|
+
async function getMediaFormat(filePath, ffprobePath) {
|
|
1977
|
+
try {
|
|
1978
|
+
const { stdout } = await execFileAsync(ffprobePath, [
|
|
1979
|
+
"-v",
|
|
1980
|
+
"quiet",
|
|
1981
|
+
"-print_format",
|
|
1982
|
+
"json",
|
|
1983
|
+
"-show_format",
|
|
1984
|
+
"-show_streams",
|
|
1985
|
+
filePath
|
|
1986
|
+
]);
|
|
1987
|
+
const data = JSON.parse(stdout);
|
|
1988
|
+
const format = data.format || {};
|
|
1989
|
+
const streams = data.streams || [];
|
|
1990
|
+
const videoStream = streams.find((s) => s.codec_type === "video");
|
|
1991
|
+
const audioStream = streams.find((s) => s.codec_type === "audio");
|
|
1992
|
+
const type = videoStream ? "video" : "audio";
|
|
1993
|
+
const formatName = format.format_name || "";
|
|
1994
|
+
const container = formatName.split(",")[0].toLowerCase();
|
|
1995
|
+
return {
|
|
1996
|
+
type,
|
|
1997
|
+
container,
|
|
1998
|
+
videoCodec: videoStream?.codec_name?.toLowerCase(),
|
|
1999
|
+
audioCodec: audioStream?.codec_name?.toLowerCase(),
|
|
2000
|
+
duration: parseFloat(format.duration) || void 0,
|
|
2001
|
+
width: videoStream?.width,
|
|
2002
|
+
height: videoStream?.height,
|
|
2003
|
+
bitrate: parseInt(format.bit_rate) || void 0
|
|
2004
|
+
};
|
|
2005
|
+
} catch {
|
|
2006
|
+
const type = getMediaTypeByExtension(filePath);
|
|
2007
|
+
if (!type) return null;
|
|
2008
|
+
const ext = import_node_path12.default.extname(filePath).toLowerCase().slice(1);
|
|
2009
|
+
return {
|
|
2010
|
+
type,
|
|
2011
|
+
container: ext
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
function canPlayVideoDirectly(format) {
|
|
2016
|
+
if (!BROWSER_VIDEO_CONTAINERS.has(format.container)) {
|
|
2017
|
+
return false;
|
|
2018
|
+
}
|
|
2019
|
+
if (format.videoCodec && !BROWSER_VIDEO_CODECS.has(format.videoCodec)) {
|
|
2020
|
+
return false;
|
|
2021
|
+
}
|
|
2022
|
+
if (format.audioCodec && !BROWSER_AUDIO_CODECS.has(format.audioCodec)) {
|
|
2023
|
+
return false;
|
|
2024
|
+
}
|
|
2025
|
+
return true;
|
|
2026
|
+
}
|
|
2027
|
+
function canPlayAudioDirectly(format) {
|
|
2028
|
+
if (!BROWSER_AUDIO_CONTAINERS.has(format.container)) {
|
|
2029
|
+
return false;
|
|
2030
|
+
}
|
|
2031
|
+
if (format.audioCodec && !BROWSER_AUDIO_ONLY_CODECS.has(format.audioCodec)) {
|
|
2032
|
+
return false;
|
|
2033
|
+
}
|
|
2034
|
+
return true;
|
|
2035
|
+
}
|
|
2036
|
+
function canRemuxVideo(format) {
|
|
2037
|
+
if (!format.videoCodec || !REMUXABLE_VIDEO_CODECS.has(format.videoCodec)) {
|
|
2038
|
+
return false;
|
|
2039
|
+
}
|
|
2040
|
+
if (format.audioCodec) {
|
|
2041
|
+
const audioOk = BROWSER_AUDIO_CODECS.has(format.audioCodec) || REMUXABLE_AUDIO_CODECS.has(format.audioCodec);
|
|
2042
|
+
if (!audioOk) {
|
|
2043
|
+
return false;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return true;
|
|
2047
|
+
}
|
|
2048
|
+
function canRemuxAudio(format) {
|
|
2049
|
+
return format.audioCodec ? REMUXABLE_AUDIO_CODECS.has(format.audioCodec) : false;
|
|
2050
|
+
}
|
|
2051
|
+
function estimateTranscodeTime(duration, method) {
|
|
2052
|
+
if (!duration || method === "direct") return void 0;
|
|
2053
|
+
if (method === "remux") {
|
|
2054
|
+
return Math.ceil(duration / 50);
|
|
2055
|
+
}
|
|
2056
|
+
return Math.ceil(duration / 3);
|
|
2057
|
+
}
|
|
2058
|
+
async function detectTranscodeNeeds(filePath, ffprobePath) {
|
|
2059
|
+
const formatInfo = await getMediaFormat(filePath, ffprobePath);
|
|
2060
|
+
if (!formatInfo) {
|
|
2061
|
+
const type2 = getMediaTypeByExtension(filePath) || "video";
|
|
2062
|
+
return {
|
|
2063
|
+
type: type2,
|
|
2064
|
+
needsTranscode: false,
|
|
2065
|
+
method: "direct"
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
const { type } = formatInfo;
|
|
2069
|
+
if (type === "video") {
|
|
2070
|
+
if (canPlayVideoDirectly(formatInfo)) {
|
|
2071
|
+
return {
|
|
2072
|
+
type,
|
|
2073
|
+
needsTranscode: false,
|
|
2074
|
+
method: "direct",
|
|
2075
|
+
formatInfo
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
if (canRemuxVideo(formatInfo)) {
|
|
2079
|
+
return {
|
|
2080
|
+
type,
|
|
2081
|
+
needsTranscode: true,
|
|
2082
|
+
method: "remux",
|
|
2083
|
+
formatInfo,
|
|
2084
|
+
targetFormat: "mp4",
|
|
2085
|
+
estimatedTime: estimateTranscodeTime(formatInfo.duration, "remux")
|
|
2086
|
+
};
|
|
1252
2087
|
}
|
|
2088
|
+
return {
|
|
2089
|
+
type,
|
|
2090
|
+
needsTranscode: true,
|
|
2091
|
+
method: "transcode",
|
|
2092
|
+
formatInfo,
|
|
2093
|
+
targetFormat: "mp4",
|
|
2094
|
+
estimatedTime: estimateTranscodeTime(formatInfo.duration, "transcode")
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
if (canPlayAudioDirectly(formatInfo)) {
|
|
2098
|
+
return {
|
|
2099
|
+
type,
|
|
2100
|
+
needsTranscode: false,
|
|
2101
|
+
method: "direct",
|
|
2102
|
+
formatInfo
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
if (canRemuxAudio(formatInfo)) {
|
|
2106
|
+
return {
|
|
2107
|
+
type,
|
|
2108
|
+
needsTranscode: true,
|
|
2109
|
+
method: "remux",
|
|
2110
|
+
formatInfo,
|
|
2111
|
+
targetFormat: "m4a",
|
|
2112
|
+
estimatedTime: estimateTranscodeTime(formatInfo.duration, "remux")
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
return {
|
|
2116
|
+
type,
|
|
2117
|
+
needsTranscode: true,
|
|
2118
|
+
method: "transcode",
|
|
2119
|
+
formatInfo,
|
|
2120
|
+
targetFormat: "mp3",
|
|
2121
|
+
estimatedTime: estimateTranscodeTime(formatInfo.duration, "transcode")
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// src/media/transcoder.ts
|
|
2126
|
+
var import_node_child_process5 = require("child_process");
|
|
2127
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
2128
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2129
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
2130
|
+
async function getTempOutputPath(sourceFile, targetFormat, tempDir) {
|
|
2131
|
+
const dir = tempDir || import_node_path13.default.join(import_node_os3.default.tmpdir(), "file-explorer-media");
|
|
2132
|
+
await import_promises3.default.mkdir(dir, { recursive: true });
|
|
2133
|
+
const baseName = import_node_path13.default.basename(sourceFile, import_node_path13.default.extname(sourceFile));
|
|
2134
|
+
const timestamp = Date.now();
|
|
2135
|
+
const outputName = `${baseName}_${timestamp}.${targetFormat}`;
|
|
2136
|
+
return import_node_path13.default.join(dir, outputName);
|
|
2137
|
+
}
|
|
2138
|
+
function parseProgress(stderr, duration) {
|
|
2139
|
+
const timeMatch = stderr.match(/time=(\d+):(\d+):(\d+)\.(\d+)/);
|
|
2140
|
+
if (!timeMatch) return null;
|
|
2141
|
+
const hours = parseInt(timeMatch[1]);
|
|
2142
|
+
const minutes = parseInt(timeMatch[2]);
|
|
2143
|
+
const seconds = parseInt(timeMatch[3]);
|
|
2144
|
+
const ms = parseInt(timeMatch[4]);
|
|
2145
|
+
const currentTime = hours * 3600 + minutes * 60 + seconds + ms / 100;
|
|
2146
|
+
const speedMatch = stderr.match(/speed=\s*([\d.]+)x/);
|
|
2147
|
+
const speed = speedMatch ? `${speedMatch[1]}x` : void 0;
|
|
2148
|
+
let percent = 0;
|
|
2149
|
+
if (duration && duration > 0) {
|
|
2150
|
+
percent = Math.min(100, Math.round(currentTime / duration * 100));
|
|
2151
|
+
}
|
|
2152
|
+
return {
|
|
2153
|
+
percent,
|
|
2154
|
+
time: currentTime,
|
|
2155
|
+
duration,
|
|
2156
|
+
speed
|
|
1253
2157
|
};
|
|
1254
2158
|
}
|
|
2159
|
+
async function remuxVideo(ffmpegPath, inputPath, outputPath, duration, onProgress) {
|
|
2160
|
+
return new Promise((resolve, reject) => {
|
|
2161
|
+
const args = [
|
|
2162
|
+
"-i",
|
|
2163
|
+
inputPath,
|
|
2164
|
+
"-c",
|
|
2165
|
+
"copy",
|
|
2166
|
+
// 复制流,不重新编码
|
|
2167
|
+
"-movflags",
|
|
2168
|
+
"+faststart",
|
|
2169
|
+
// 优化 MP4 播放
|
|
2170
|
+
"-y",
|
|
2171
|
+
// 覆盖输出文件
|
|
2172
|
+
outputPath
|
|
2173
|
+
];
|
|
2174
|
+
const ffmpeg = (0, import_node_child_process5.spawn)(ffmpegPath, args);
|
|
2175
|
+
let stderrBuffer = "";
|
|
2176
|
+
ffmpeg.stderr.on("data", (data) => {
|
|
2177
|
+
stderrBuffer += data.toString();
|
|
2178
|
+
if (onProgress) {
|
|
2179
|
+
const progress = parseProgress(stderrBuffer, duration);
|
|
2180
|
+
if (progress) {
|
|
2181
|
+
onProgress(progress);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2185
|
+
ffmpeg.on("close", (code) => {
|
|
2186
|
+
if (code === 0) {
|
|
2187
|
+
resolve();
|
|
2188
|
+
} else {
|
|
2189
|
+
reject(new Error(`ffmpeg exited with code ${code}`));
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2192
|
+
ffmpeg.on("error", reject);
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
async function transcodeVideo(ffmpegPath, inputPath, outputPath, duration, onProgress) {
|
|
2196
|
+
return new Promise((resolve, reject) => {
|
|
2197
|
+
const args = [
|
|
2198
|
+
"-i",
|
|
2199
|
+
inputPath,
|
|
2200
|
+
"-c:v",
|
|
2201
|
+
"libx264",
|
|
2202
|
+
// H.264 编码
|
|
2203
|
+
"-preset",
|
|
2204
|
+
"fast",
|
|
2205
|
+
// 编码速度预设
|
|
2206
|
+
"-crf",
|
|
2207
|
+
"23",
|
|
2208
|
+
// 质量(18-28,越小越好)
|
|
2209
|
+
"-c:a",
|
|
2210
|
+
"aac",
|
|
2211
|
+
// AAC 音频
|
|
2212
|
+
"-b:a",
|
|
2213
|
+
"192k",
|
|
2214
|
+
// 音频比特率
|
|
2215
|
+
"-movflags",
|
|
2216
|
+
"+faststart",
|
|
2217
|
+
"-y",
|
|
2218
|
+
outputPath
|
|
2219
|
+
];
|
|
2220
|
+
const ffmpeg = (0, import_node_child_process5.spawn)(ffmpegPath, args);
|
|
2221
|
+
let stderrBuffer = "";
|
|
2222
|
+
ffmpeg.stderr.on("data", (data) => {
|
|
2223
|
+
stderrBuffer += data.toString();
|
|
2224
|
+
if (onProgress) {
|
|
2225
|
+
const progress = parseProgress(stderrBuffer, duration);
|
|
2226
|
+
if (progress) {
|
|
2227
|
+
onProgress(progress);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
});
|
|
2231
|
+
ffmpeg.on("close", (code) => {
|
|
2232
|
+
if (code === 0) {
|
|
2233
|
+
resolve();
|
|
2234
|
+
} else {
|
|
2235
|
+
reject(new Error(`ffmpeg exited with code ${code}`));
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
ffmpeg.on("error", reject);
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
async function remuxAudio(ffmpegPath, inputPath, outputPath, duration, onProgress) {
|
|
2242
|
+
return new Promise((resolve, reject) => {
|
|
2243
|
+
const args = [
|
|
2244
|
+
"-i",
|
|
2245
|
+
inputPath,
|
|
2246
|
+
"-c",
|
|
2247
|
+
"copy",
|
|
2248
|
+
"-y",
|
|
2249
|
+
outputPath
|
|
2250
|
+
];
|
|
2251
|
+
const ffmpeg = (0, import_node_child_process5.spawn)(ffmpegPath, args);
|
|
2252
|
+
let stderrBuffer = "";
|
|
2253
|
+
ffmpeg.stderr.on("data", (data) => {
|
|
2254
|
+
stderrBuffer += data.toString();
|
|
2255
|
+
if (onProgress) {
|
|
2256
|
+
const progress = parseProgress(stderrBuffer, duration);
|
|
2257
|
+
if (progress) {
|
|
2258
|
+
onProgress(progress);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
ffmpeg.on("close", (code) => {
|
|
2263
|
+
if (code === 0) {
|
|
2264
|
+
resolve();
|
|
2265
|
+
} else {
|
|
2266
|
+
reject(new Error(`ffmpeg exited with code ${code}`));
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
ffmpeg.on("error", reject);
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
async function transcodeAudio(ffmpegPath, inputPath, outputPath, duration, onProgress) {
|
|
2273
|
+
return new Promise((resolve, reject) => {
|
|
2274
|
+
const ext = import_node_path13.default.extname(outputPath).toLowerCase();
|
|
2275
|
+
const isM4a = ext === ".m4a";
|
|
2276
|
+
const args = [
|
|
2277
|
+
"-i",
|
|
2278
|
+
inputPath,
|
|
2279
|
+
"-c:a",
|
|
2280
|
+
isM4a ? "aac" : "libmp3lame",
|
|
2281
|
+
"-b:a",
|
|
2282
|
+
"192k",
|
|
2283
|
+
"-y",
|
|
2284
|
+
outputPath
|
|
2285
|
+
];
|
|
2286
|
+
const ffmpeg = (0, import_node_child_process5.spawn)(ffmpegPath, args);
|
|
2287
|
+
let stderrBuffer = "";
|
|
2288
|
+
ffmpeg.stderr.on("data", (data) => {
|
|
2289
|
+
stderrBuffer += data.toString();
|
|
2290
|
+
if (onProgress) {
|
|
2291
|
+
const progress = parseProgress(stderrBuffer, duration);
|
|
2292
|
+
if (progress) {
|
|
2293
|
+
onProgress(progress);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2297
|
+
ffmpeg.on("close", (code) => {
|
|
2298
|
+
if (code === 0) {
|
|
2299
|
+
resolve();
|
|
2300
|
+
} else {
|
|
2301
|
+
reject(new Error(`ffmpeg exited with code ${code}`));
|
|
2302
|
+
}
|
|
2303
|
+
});
|
|
2304
|
+
ffmpeg.on("error", reject);
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
async function transcodeMedia(ffmpegPath, inputPath, transcodeInfo, tempDir, onProgress) {
|
|
2308
|
+
try {
|
|
2309
|
+
if (!transcodeInfo.needsTranscode) {
|
|
2310
|
+
return {
|
|
2311
|
+
success: true,
|
|
2312
|
+
outputPath: inputPath
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
const targetFormat = transcodeInfo.targetFormat || (transcodeInfo.type === "video" ? "mp4" : "mp3");
|
|
2316
|
+
const outputPath = await getTempOutputPath(inputPath, targetFormat, tempDir);
|
|
2317
|
+
const duration = transcodeInfo.formatInfo?.duration;
|
|
2318
|
+
if (transcodeInfo.type === "video") {
|
|
2319
|
+
if (transcodeInfo.method === "remux") {
|
|
2320
|
+
await remuxVideo(ffmpegPath, inputPath, outputPath, duration, onProgress);
|
|
2321
|
+
} else {
|
|
2322
|
+
await transcodeVideo(ffmpegPath, inputPath, outputPath, duration, onProgress);
|
|
2323
|
+
}
|
|
2324
|
+
} else {
|
|
2325
|
+
if (transcodeInfo.method === "remux") {
|
|
2326
|
+
await remuxAudio(ffmpegPath, inputPath, outputPath, duration, onProgress);
|
|
2327
|
+
} else {
|
|
2328
|
+
await transcodeAudio(ffmpegPath, inputPath, outputPath, duration, onProgress);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
if (onProgress) {
|
|
2332
|
+
onProgress({ percent: 100, duration });
|
|
2333
|
+
}
|
|
2334
|
+
return {
|
|
2335
|
+
success: true,
|
|
2336
|
+
outputPath
|
|
2337
|
+
};
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
return {
|
|
2340
|
+
success: false,
|
|
2341
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
async function cleanupTranscodedFile(filePath) {
|
|
2346
|
+
try {
|
|
2347
|
+
if (filePath.includes("file-explorer-media")) {
|
|
2348
|
+
await import_promises3.default.unlink(filePath);
|
|
2349
|
+
}
|
|
2350
|
+
} catch {
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
async function cleanupAllTranscodedFiles(tempDir) {
|
|
2354
|
+
const dir = tempDir || import_node_path13.default.join(import_node_os3.default.tmpdir(), "file-explorer-media");
|
|
2355
|
+
try {
|
|
2356
|
+
const files = await import_promises3.default.readdir(dir);
|
|
2357
|
+
await Promise.all(
|
|
2358
|
+
files.map((file) => import_promises3.default.unlink(import_node_path13.default.join(dir, file)).catch(() => {
|
|
2359
|
+
}))
|
|
2360
|
+
);
|
|
2361
|
+
} catch {
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// src/media/service.ts
|
|
2366
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2367
|
+
var import_node_child_process6 = require("child_process");
|
|
2368
|
+
var import_node_util4 = require("util");
|
|
2369
|
+
var execFileAsync2 = (0, import_node_util4.promisify)(import_node_child_process6.execFile);
|
|
2370
|
+
var mediaServiceInstance = null;
|
|
2371
|
+
var MediaService = class {
|
|
2372
|
+
ffmpegPath;
|
|
2373
|
+
ffprobePath;
|
|
2374
|
+
tempDir;
|
|
2375
|
+
urlEncoder;
|
|
2376
|
+
// 缓存转码信息,避免重复检测
|
|
2377
|
+
transcodeInfoCache = /* @__PURE__ */ new Map();
|
|
2378
|
+
// 缓存已转码文件路径
|
|
2379
|
+
transcodedFiles = /* @__PURE__ */ new Map();
|
|
2380
|
+
constructor(options) {
|
|
2381
|
+
this.ffmpegPath = options.ffmpegPath;
|
|
2382
|
+
this.ffprobePath = options.ffprobePath || import_node_path14.default.join(import_node_path14.default.dirname(options.ffmpegPath), "ffprobe");
|
|
2383
|
+
this.tempDir = options.tempDir;
|
|
2384
|
+
this.urlEncoder = options.urlEncoder;
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* 检测文件是否需要转码
|
|
2388
|
+
*/
|
|
2389
|
+
async needsTranscode(filePath) {
|
|
2390
|
+
const cached = this.transcodeInfoCache.get(filePath);
|
|
2391
|
+
if (cached) {
|
|
2392
|
+
return cached;
|
|
2393
|
+
}
|
|
2394
|
+
const info = await detectTranscodeNeeds(filePath, this.ffprobePath);
|
|
2395
|
+
this.transcodeInfoCache.set(filePath, info);
|
|
2396
|
+
return info;
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* 执行转码并返回可播放的 URL
|
|
2400
|
+
*/
|
|
2401
|
+
async transcode(filePath, onProgress) {
|
|
2402
|
+
const existingOutput = this.transcodedFiles.get(filePath);
|
|
2403
|
+
if (existingOutput) {
|
|
2404
|
+
return {
|
|
2405
|
+
success: true,
|
|
2406
|
+
outputPath: existingOutput,
|
|
2407
|
+
url: this.urlEncoder ? this.urlEncoder(existingOutput) : `file://${existingOutput}`
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
const transcodeInfo = await this.needsTranscode(filePath);
|
|
2411
|
+
if (!transcodeInfo.needsTranscode) {
|
|
2412
|
+
const url = this.urlEncoder ? this.urlEncoder(filePath) : `file://${filePath}`;
|
|
2413
|
+
return {
|
|
2414
|
+
success: true,
|
|
2415
|
+
outputPath: filePath,
|
|
2416
|
+
url
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
const result = await transcodeMedia(
|
|
2420
|
+
this.ffmpegPath,
|
|
2421
|
+
filePath,
|
|
2422
|
+
transcodeInfo,
|
|
2423
|
+
this.tempDir,
|
|
2424
|
+
onProgress
|
|
2425
|
+
);
|
|
2426
|
+
if (result.success && result.outputPath) {
|
|
2427
|
+
this.transcodedFiles.set(filePath, result.outputPath);
|
|
2428
|
+
result.url = this.urlEncoder ? this.urlEncoder(result.outputPath) : `file://${result.outputPath}`;
|
|
2429
|
+
}
|
|
2430
|
+
return result;
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* 获取媒体元数据
|
|
2434
|
+
*/
|
|
2435
|
+
async getMetadata(filePath) {
|
|
2436
|
+
try {
|
|
2437
|
+
const { stdout } = await execFileAsync2(this.ffprobePath, [
|
|
2438
|
+
"-v",
|
|
2439
|
+
"quiet",
|
|
2440
|
+
"-print_format",
|
|
2441
|
+
"json",
|
|
2442
|
+
"-show_format",
|
|
2443
|
+
"-show_streams",
|
|
2444
|
+
filePath
|
|
2445
|
+
]);
|
|
2446
|
+
const data = JSON.parse(stdout);
|
|
2447
|
+
const format = data.format || {};
|
|
2448
|
+
const tags = format.tags || {};
|
|
2449
|
+
const formatInfo = await getMediaFormat(filePath, this.ffprobePath);
|
|
2450
|
+
if (!formatInfo) return null;
|
|
2451
|
+
return {
|
|
2452
|
+
filePath,
|
|
2453
|
+
type: formatInfo.type,
|
|
2454
|
+
duration: parseFloat(format.duration) || 0,
|
|
2455
|
+
format: formatInfo,
|
|
2456
|
+
title: tags.title || tags.TITLE,
|
|
2457
|
+
artist: tags.artist || tags.ARTIST,
|
|
2458
|
+
album: tags.album || tags.ALBUM,
|
|
2459
|
+
year: tags.date || tags.DATE || tags.year || tags.YEAR
|
|
2460
|
+
};
|
|
2461
|
+
} catch {
|
|
2462
|
+
return null;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* 获取可播放的 URL
|
|
2467
|
+
* 如果文件需要转码,则执行转码;否则直接返回文件 URL
|
|
2468
|
+
*/
|
|
2469
|
+
async getPlayableUrl(filePath, onProgress) {
|
|
2470
|
+
const result = await this.transcode(filePath, onProgress);
|
|
2471
|
+
return result.success ? result.url || null : null;
|
|
2472
|
+
}
|
|
2473
|
+
/**
|
|
2474
|
+
* 清理指定文件的转码缓存
|
|
2475
|
+
*/
|
|
2476
|
+
async cleanupFile(filePath) {
|
|
2477
|
+
const transcodedPath = this.transcodedFiles.get(filePath);
|
|
2478
|
+
if (transcodedPath) {
|
|
2479
|
+
await cleanupTranscodedFile(transcodedPath);
|
|
2480
|
+
this.transcodedFiles.delete(filePath);
|
|
2481
|
+
}
|
|
2482
|
+
this.transcodeInfoCache.delete(filePath);
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* 清理所有转码缓存
|
|
2486
|
+
*/
|
|
2487
|
+
async cleanup() {
|
|
2488
|
+
await cleanupAllTranscodedFiles(this.tempDir);
|
|
2489
|
+
this.transcodedFiles.clear();
|
|
2490
|
+
this.transcodeInfoCache.clear();
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* 清除缓存(不删除文件)
|
|
2494
|
+
*/
|
|
2495
|
+
clearCache() {
|
|
2496
|
+
this.transcodeInfoCache.clear();
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
function initMediaService(options) {
|
|
2500
|
+
mediaServiceInstance = new MediaService(options);
|
|
2501
|
+
return mediaServiceInstance;
|
|
2502
|
+
}
|
|
2503
|
+
function getMediaService() {
|
|
2504
|
+
return mediaServiceInstance;
|
|
2505
|
+
}
|
|
2506
|
+
function createMediaService(options) {
|
|
2507
|
+
return new MediaService(options);
|
|
2508
|
+
}
|
|
1255
2509
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1256
2510
|
0 && (module.exports = {
|
|
1257
2511
|
APP_PROTOCOL_HOST,
|
|
1258
2512
|
APP_PROTOCOL_PREFIX,
|
|
1259
2513
|
APP_PROTOCOL_SCHEME,
|
|
1260
2514
|
FileType,
|
|
2515
|
+
MediaService,
|
|
1261
2516
|
SqliteThumbnailDatabase,
|
|
1262
2517
|
ThumbnailService,
|
|
2518
|
+
WatchManager,
|
|
2519
|
+
cleanupAllTranscodedFiles,
|
|
2520
|
+
cleanupTranscodedFile,
|
|
2521
|
+
closeThumbnailDatabase,
|
|
2522
|
+
compressFiles,
|
|
1263
2523
|
copyFiles,
|
|
1264
2524
|
copyFilesToClipboard,
|
|
1265
2525
|
createFfmpegVideoProcessor,
|
|
1266
2526
|
createFile,
|
|
1267
2527
|
createFolder,
|
|
2528
|
+
createMediaService,
|
|
1268
2529
|
createSharpImageProcessor,
|
|
1269
2530
|
createSqliteThumbnailDatabase,
|
|
1270
2531
|
decodeFileUrl,
|
|
1271
2532
|
deleteFiles,
|
|
2533
|
+
detectArchiveFormat,
|
|
2534
|
+
detectTranscodeNeeds,
|
|
1272
2535
|
encodeFileUrl,
|
|
1273
2536
|
exists,
|
|
2537
|
+
extractArchive,
|
|
1274
2538
|
formatDate,
|
|
1275
2539
|
formatDateTime,
|
|
1276
2540
|
formatFileSize,
|
|
@@ -1278,28 +2542,39 @@ function createFfmpegVideoProcessor(ffmpegPath) {
|
|
|
1278
2542
|
getApplicationIcon,
|
|
1279
2543
|
getClipboardFiles,
|
|
1280
2544
|
getFileHash,
|
|
1281
|
-
getFileHashes,
|
|
1282
2545
|
getFileInfo,
|
|
1283
2546
|
getFileType,
|
|
1284
2547
|
getHomeDirectory,
|
|
2548
|
+
getMediaFormat,
|
|
2549
|
+
getMediaService,
|
|
2550
|
+
getMediaTypeByExtension,
|
|
1285
2551
|
getPlatform,
|
|
1286
2552
|
getSqliteThumbnailDatabase,
|
|
1287
2553
|
getSystemPath,
|
|
1288
2554
|
getThumbnailService,
|
|
2555
|
+
getWatchManager,
|
|
2556
|
+
initMediaService,
|
|
1289
2557
|
initThumbnailService,
|
|
1290
2558
|
isAppProtocolUrl,
|
|
2559
|
+
isArchiveFile,
|
|
1291
2560
|
isDirectory,
|
|
1292
2561
|
isMediaFile,
|
|
1293
2562
|
isPreviewable,
|
|
1294
2563
|
moveFiles,
|
|
2564
|
+
openInEditor,
|
|
2565
|
+
openInTerminal,
|
|
1295
2566
|
pasteFiles,
|
|
1296
2567
|
readDirectory,
|
|
1297
2568
|
readFileContent,
|
|
1298
2569
|
readImageAsBase64,
|
|
1299
2570
|
renameFile,
|
|
2571
|
+
revealInFileManager,
|
|
1300
2572
|
searchFiles,
|
|
1301
2573
|
searchFilesStream,
|
|
1302
2574
|
searchFilesSync,
|
|
2575
|
+
showFileInfo,
|
|
2576
|
+
transcodeMedia,
|
|
2577
|
+
watchDirectory,
|
|
1303
2578
|
writeFileContent
|
|
1304
2579
|
});
|
|
1305
2580
|
//# sourceMappingURL=index.cjs.map
|