v6.9
This commit is contained in:
+78
-26
@@ -30,7 +30,8 @@ func decodeBase64(encoded string) (string, error) {
|
|||||||
const (
|
const (
|
||||||
ffmpegWindowsURL = "aHR0cHM6Ly9naXRodWIuY29tL0J0Yk4vRkZtcGVnLUJ1aWxkcy9yZWxlYXNlcy9kb3dubG9hZC9sYXRlc3QvZmZtcGVnLW1hc3Rlci1sYXRlc3Qtd2luNjQtZ3BsLnppcA=="
|
ffmpegWindowsURL = "aHR0cHM6Ly9naXRodWIuY29tL0J0Yk4vRkZtcGVnLUJ1aWxkcy9yZWxlYXNlcy9kb3dubG9hZC9sYXRlc3QvZmZtcGVnLW1hc3Rlci1sYXRlc3Qtd2luNjQtZ3BsLnppcA=="
|
||||||
ffmpegLinuxURL = "aHR0cHM6Ly9naXRodWIuY29tL0J0Yk4vRkZtcGVnLUJ1aWxkcy9yZWxlYXNlcy9kb3dubG9hZC9sYXRlc3QvZmZtcGVnLW1hc3Rlci1sYXRlc3QtbGludXg2NC1ncGwudGFyLnh6"
|
ffmpegLinuxURL = "aHR0cHM6Ly9naXRodWIuY29tL0J0Yk4vRkZtcGVnLUJ1aWxkcy9yZWxlYXNlcy9kb3dubG9hZC9sYXRlc3QvZmZtcGVnLW1hc3Rlci1sYXRlc3QtbGludXg2NC1ncGwudGFyLnh6"
|
||||||
ffmpegMacOSURL = "aHR0cHM6Ly9ldmVybWVldC5jeC9mZm1wZWcvZ2V0cmVsZWFzZS9mZm1wZWcvemlw"
|
ffmpegMacOSURL = "aHR0cHM6Ly9ldmVybWVldC5jeC9mZm1wZWcvZ2V0cmVsZWFzZS96aXA="
|
||||||
|
ffprobeMacOSURL = "aHR0cHM6Ly9ldmVybWVldC5jeC9mZm1wZWcvZ2V0cmVsZWFzZS9mZnByb2JlL3ppcA=="
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetFFmpegDir returns the directory where ffmpeg should be stored
|
// GetFFmpegDir returns the directory where ffmpeg should be stored
|
||||||
@@ -126,15 +127,49 @@ func DownloadFFmpeg(progressCallback func(int)) error {
|
|||||||
return fmt.Errorf("failed to create ffmpeg directory: %w", err)
|
return fmt.Errorf("failed to create ffmpeg directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the appropriate URL for the current OS
|
// For macOS, download ffmpeg and ffprobe separately (only if not already installed)
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
ffmpegInstalled, _ := IsFFmpegInstalled()
|
||||||
|
ffprobeInstalled, _ := IsFFprobeInstalled()
|
||||||
|
|
||||||
|
if !ffmpegInstalled && !ffprobeInstalled {
|
||||||
|
// Download both
|
||||||
|
ffmpegURL, _ := decodeBase64(ffmpegMacOSURL)
|
||||||
|
fmt.Printf("[FFmpeg] Downloading ffmpeg from: %s\n", ffmpegURL)
|
||||||
|
if err := downloadAndExtract(ffmpegURL, ffmpegDir, progressCallback, 0, 50); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ffprobeURL, _ := decodeBase64(ffprobeMacOSURL)
|
||||||
|
fmt.Printf("[FFmpeg] Downloading ffprobe from: %s\n", ffprobeURL)
|
||||||
|
if err := downloadAndExtract(ffprobeURL, ffmpegDir, progressCallback, 50, 100); err != nil {
|
||||||
|
return fmt.Errorf("failed to download ffprobe: %w", err)
|
||||||
|
}
|
||||||
|
} else if !ffmpegInstalled {
|
||||||
|
// Only download ffmpeg
|
||||||
|
ffmpegURL, _ := decodeBase64(ffmpegMacOSURL)
|
||||||
|
fmt.Printf("[FFmpeg] Downloading ffmpeg from: %s\n", ffmpegURL)
|
||||||
|
if err := downloadAndExtract(ffmpegURL, ffmpegDir, progressCallback, 0, 100); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if !ffprobeInstalled {
|
||||||
|
// Only download ffprobe
|
||||||
|
ffprobeURL, _ := decodeBase64(ffprobeMacOSURL)
|
||||||
|
fmt.Printf("[FFmpeg] Downloading ffprobe from: %s\n", ffprobeURL)
|
||||||
|
if err := downloadAndExtract(ffprobeURL, ffmpegDir, progressCallback, 0, 100); err != nil {
|
||||||
|
return fmt.Errorf("failed to download ffprobe: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Windows/Linux: single archive contains both ffmpeg and ffprobe
|
||||||
var encodedURL string
|
var encodedURL string
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
encodedURL = ffmpegWindowsURL
|
encodedURL = ffmpegWindowsURL
|
||||||
case "linux":
|
case "linux":
|
||||||
encodedURL = ffmpegLinuxURL
|
encodedURL = ffmpegLinuxURL
|
||||||
case "darwin":
|
|
||||||
encodedURL = ffmpegMacOSURL
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||||||
}
|
}
|
||||||
@@ -147,6 +182,15 @@ func DownloadFFmpeg(progressCallback func(int)) error {
|
|||||||
|
|
||||||
fmt.Printf("[FFmpeg] Downloading from: %s\n", url)
|
fmt.Printf("[FFmpeg] Downloading from: %s\n", url)
|
||||||
|
|
||||||
|
if err := downloadAndExtract(url, ffmpegDir, progressCallback, 0, 100); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadAndExtract downloads a file and extracts it
|
||||||
|
func downloadAndExtract(url, destDir string, progressCallback func(int), progressStart, progressEnd int) error {
|
||||||
// Create temporary file for download
|
// Create temporary file for download
|
||||||
tmpFile, err := os.CreateTemp("", "ffmpeg-*")
|
tmpFile, err := os.CreateTemp("", "ffmpeg-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,12 +202,12 @@ func DownloadFFmpeg(progressCallback func(int)) error {
|
|||||||
// Download the file
|
// Download the file
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download ffmpeg: %w", err)
|
return fmt.Errorf("failed to download: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("failed to download ffmpeg: HTTP %d", resp.StatusCode)
|
return fmt.Errorf("failed to download: HTTP %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSize := resp.ContentLength
|
totalSize := resp.ContentLength
|
||||||
@@ -180,8 +224,10 @@ func DownloadFFmpeg(progressCallback func(int)) error {
|
|||||||
}
|
}
|
||||||
downloaded += int64(n)
|
downloaded += int64(n)
|
||||||
if totalSize > 0 && progressCallback != nil {
|
if totalSize > 0 && progressCallback != nil {
|
||||||
progress := int(float64(downloaded) / float64(totalSize) * 100)
|
// Scale progress between progressStart and progressEnd
|
||||||
progressCallback(progress)
|
rawProgress := float64(downloaded) / float64(totalSize)
|
||||||
|
scaledProgress := progressStart + int(rawProgress*float64(progressEnd-progressStart))
|
||||||
|
progressCallback(scaledProgress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -196,18 +242,14 @@ func DownloadFFmpeg(progressCallback func(int)) error {
|
|||||||
|
|
||||||
fmt.Printf("[FFmpeg] Download complete, extracting...\n")
|
fmt.Printf("[FFmpeg] Download complete, extracting...\n")
|
||||||
|
|
||||||
// Extract the archive
|
// Extract the archive based on file type
|
||||||
switch runtime.GOOS {
|
if strings.HasSuffix(url, ".tar.xz") || runtime.GOOS == "linux" {
|
||||||
case "windows", "darwin":
|
return extractTarXz(tmpFile.Name(), destDir)
|
||||||
return extractZip(tmpFile.Name(), ffmpegDir)
|
|
||||||
case "linux":
|
|
||||||
return extractTarXz(tmpFile.Name(), ffmpegDir)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
|
||||||
}
|
}
|
||||||
|
return extractZip(tmpFile.Name(), destDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractZip extracts ffmpeg and ffprobe from a zip archive
|
// extractZip extracts ffmpeg and ffprobe from a zip archive (skips ffplay)
|
||||||
func extractZip(zipPath, destDir string) error {
|
func extractZip(zipPath, destDir string) error {
|
||||||
r, err := zip.OpenReader(zipPath)
|
r, err := zip.OpenReader(zipPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -239,6 +281,7 @@ func extractZip(zipPath, destDir string) error {
|
|||||||
destPath = filepath.Join(destDir, ffprobeName)
|
destPath = filepath.Join(destDir, ffprobeName)
|
||||||
foundFFprobe = true
|
foundFFprobe = true
|
||||||
} else {
|
} else {
|
||||||
|
// Skip ffplay and other files
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,18 +309,22 @@ func extractZip(zipPath, destDir string) error {
|
|||||||
fmt.Printf("[FFmpeg] Extracted to: %s\n", destPath)
|
fmt.Printf("[FFmpeg] Extracted to: %s\n", destPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundFFmpeg {
|
// At least one of ffmpeg or ffprobe should be found
|
||||||
return fmt.Errorf("ffmpeg executable not found in archive")
|
if !foundFFmpeg && !foundFFprobe {
|
||||||
|
return fmt.Errorf("neither ffmpeg nor ffprobe found in archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundFFprobe {
|
if foundFFmpeg {
|
||||||
fmt.Printf("[FFmpeg] Warning: ffprobe not found in archive\n")
|
fmt.Printf("[FFmpeg] ffmpeg extracted successfully\n")
|
||||||
|
}
|
||||||
|
if foundFFprobe {
|
||||||
|
fmt.Printf("[FFmpeg] ffprobe extracted successfully\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractTarXz extracts ffmpeg and ffprobe from a tar.xz archive
|
// extractTarXz extracts ffmpeg and ffprobe from a tar.xz archive (skips ffplay)
|
||||||
func extractTarXz(tarXzPath, destDir string) error {
|
func extractTarXz(tarXzPath, destDir string) error {
|
||||||
file, err := os.Open(tarXzPath)
|
file, err := os.Open(tarXzPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -320,6 +367,7 @@ func extractTarXz(tarXzPath, destDir string) error {
|
|||||||
destPath = filepath.Join(destDir, ffprobeName)
|
destPath = filepath.Join(destDir, ffprobeName)
|
||||||
foundFFprobe = true
|
foundFFprobe = true
|
||||||
} else {
|
} else {
|
||||||
|
// Skip ffplay and other files
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,12 +388,16 @@ func extractTarXz(tarXzPath, destDir string) error {
|
|||||||
fmt.Printf("[FFmpeg] Extracted to: %s\n", destPath)
|
fmt.Printf("[FFmpeg] Extracted to: %s\n", destPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundFFmpeg {
|
// At least one of ffmpeg or ffprobe should be found
|
||||||
return fmt.Errorf("ffmpeg executable not found in archive")
|
if !foundFFmpeg && !foundFFprobe {
|
||||||
|
return fmt.Errorf("neither ffmpeg nor ffprobe found in archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundFFprobe {
|
if foundFFmpeg {
|
||||||
fmt.Printf("[FFmpeg] Warning: ffprobe not found in archive\n")
|
fmt.Printf("[FFmpeg] ffmpeg extracted successfully\n")
|
||||||
|
}
|
||||||
|
if foundFFprobe {
|
||||||
|
fmt.Printf("[FFmpeg] ffprobe extracted successfully\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -194,18 +194,26 @@ export function FileManagerPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await ListDirectoryFiles(rootPath);
|
const result = await ListDirectoryFiles(rootPath);
|
||||||
|
// Handle null/undefined result (can happen on Linux)
|
||||||
|
if (!result || !Array.isArray(result)) {
|
||||||
|
setFiles([]);
|
||||||
|
setSelectedFiles(new Set());
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Filter to only show audio files and folders containing audio files
|
// Filter to only show audio files and folders containing audio files
|
||||||
const filtered = filterAudioFiles(result as FileNode[]);
|
const filtered = filterAudioFiles(result as FileNode[]);
|
||||||
setFiles(filtered);
|
setFiles(filtered);
|
||||||
setSelectedFiles(new Set());
|
setSelectedFiles(new Set());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Don't show error toast for empty directory or no files found
|
// Don't show error toast for empty directory or no files found
|
||||||
const errorMsg = err instanceof Error ? err.message : "";
|
const errorMsg = err instanceof Error ? err.message : String(err || "");
|
||||||
if (!errorMsg.toLowerCase().includes("empty") && !errorMsg.toLowerCase().includes("no file")) {
|
if (!errorMsg.toLowerCase().includes("empty") && !errorMsg.toLowerCase().includes("no file")) {
|
||||||
toast.error("Failed to load files", {
|
toast.error("Failed to load files", {
|
||||||
description: errorMsg || "Unknown error",
|
description: errorMsg || "Unknown error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
setFiles([]);
|
||||||
|
setSelectedFiles(new Set());
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user