This commit is contained in:
afkarxyz
2025-12-20 04:49:58 +07:00
parent 66c30de2db
commit 4fee88329b
2 changed files with 87 additions and 27 deletions
+78 -26
View File
@@ -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
+9 -1
View File
@@ -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);
} }