.update ffmpeg + linux arm build

This commit is contained in:
afkarxyz
2026-04-14 05:49:23 +07:00
parent 42d25abe0c
commit 1858fd6f12
3 changed files with 162 additions and 163 deletions
+56 -41
View File
@@ -81,13 +81,13 @@ jobs:
- name: Prepare artifacts - name: Prepare artifacts
run: | run: |
mkdir -p dist mkdir -p dist
Copy-Item -Path "build\bin\SpotiFLAC.exe" -Destination "dist\SpotiFLAC.exe" Compress-Archive -Path "build\bin\SpotiFLAC.exe" -DestinationPath "dist\SpotiFLAC-windows.zip" -Force
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: windows-portable name: windows-bundle
path: dist/SpotiFLAC.exe path: dist/SpotiFLAC-windows.zip
retention-days: 7 retention-days: 7
build-macos: build-macos:
@@ -147,36 +147,33 @@ jobs:
- name: Build application - name: Build application
run: wails build -platform darwin/universal run: wails build -platform darwin/universal
- name: Create DMG - name: Create macOS bundle
run: | run: |
mkdir -p dist mkdir -p dist
# Install create-dmg if not available ditto -c -k --sequesterRsrc --keepParent "build/bin/SpotiFLAC.app" "dist/SpotiFLAC-macos-bundle.zip"
brew install create-dmg || true
# Create DMG
create-dmg \
--volname "SpotiFLAC" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "SpotiFLAC.app" 175 120 \
--hide-extension "SpotiFLAC.app" \
--app-drop-link 425 120 \
"dist/SpotiFLAC.dmg" \
"build/bin/SpotiFLAC.app" || \
# Fallback to hdiutil if create-dmg fails
hdiutil create -volname SpotiFLAC -srcfolder build/bin/SpotiFLAC.app -ov -format UDZO dist/SpotiFLAC.dmg
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: macos-portable name: macos-bundle
path: dist/SpotiFLAC.dmg path: dist/SpotiFLAC-macos-bundle.zip
retention-days: 7 retention-days: 7
build-linux: build-linux:
name: Build Linux name: Build Linux (${{ matrix.arch }})
runs-on: ubuntu-24.04 runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
goarch: amd64
runner: ubuntu-24.04
appimage_arch: x86_64
- arch: arm64
goarch: arm64
runner: ubuntu-24.04-arm
appimage_arch: aarch64
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -222,10 +219,15 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libfuse2 imagemagick upx-ucl PACKAGES="libgtk-3-dev libwebkit2gtk-4.1-dev libfuse2 imagemagick"
if [ "${{ matrix.goarch }}" = "amd64" ]; then
PACKAGES="$PACKAGES upx-ucl"
fi
sudo apt-get install -y $PACKAGES
# Create symlink for webkit2gtk-4.0 -> webkit2gtk-4.1 (Ubuntu 24.04 compatibility) # Create symlink for webkit2gtk-4.0 -> webkit2gtk-4.1 (Ubuntu 24.04 compatibility)
sudo ln -sf /usr/lib/x86_64-linux-gnu/pkgconfig/webkit2gtk-4.1.pc /usr/lib/x86_64-linux-gnu/pkgconfig/webkit2gtk-4.0.pc MULTIARCH="$(dpkg-architecture -qDEB_HOST_MULTIARCH)"
sudo ln -sf "/usr/lib/${MULTIARCH}/pkgconfig/webkit2gtk-4.1.pc" "/usr/lib/${MULTIARCH}/pkgconfig/webkit2gtk-4.0.pc"
- name: Install Wails CLI - name: Install Wails CLI
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
@@ -237,9 +239,10 @@ jobs:
pnpm run generate-icon pnpm run generate-icon
- name: Build application - name: Build application
run: wails build -platform linux/amd64 run: wails build -platform linux/${{ matrix.goarch }}
- name: Compress with UPX - name: Compress with UPX
if: matrix.goarch == 'amd64'
run: | run: |
upx --best --lzma build/bin/SpotiFLAC upx --best --lzma build/bin/SpotiFLAC
@@ -248,13 +251,13 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: appimagetool path: appimagetool
key: appimagetool-x86_64-v1 key: appimagetool-${{ matrix.appimage_arch }}-v1
- name: Download appimagetool - name: Download appimagetool
if: steps.cache-appimagetool.outputs.cache-hit != 'true' if: steps.cache-appimagetool.outputs.cache-hit != 'true'
run: | run: |
wget --timeout=30 --tries=5 --retry-connrefused --waitretry=5 -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage || \ wget --timeout=30 --tries=5 --retry-connrefused --waitretry=5 -O appimagetool https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.appimage_arch }}.AppImage || \
wget --timeout=30 --tries=5 --retry-connrefused --waitretry=5 -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage wget --timeout=30 --tries=5 --retry-connrefused --waitretry=5 -O appimagetool https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-${{ matrix.appimage_arch }}.AppImage
- name: Make appimagetool executable - name: Make appimagetool executable
run: chmod +x appimagetool run: chmod +x appimagetool
@@ -309,13 +312,13 @@ jobs:
# Create AppImage # Create AppImage
mkdir -p dist mkdir -p dist
ARCH=x86_64 ./appimagetool --no-appstream AppDir dist/SpotiFLAC.AppImage ARCH=${{ matrix.appimage_arch }} ./appimagetool --no-appstream AppDir dist/SpotiFLAC-linux-${{ matrix.arch }}.AppImage
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: linux-portable name: linux-appimage-${{ matrix.arch }}
path: dist/SpotiFLAC.AppImage path: dist/SpotiFLAC-linux-${{ matrix.arch }}.AppImage
retention-days: 7 retention-days: 7
create-release: create-release:
@@ -343,6 +346,13 @@ jobs:
- name: Display structure of downloaded files - name: Display structure of downloaded files
run: ls -R artifacts run: ls -R artifacts
- name: Create Linux bundle
run: |
mkdir -p release/SpotiFLAC-linux-bundle
cp artifacts/linux-appimage-amd64/*.AppImage release/SpotiFLAC-linux-bundle/
cp artifacts/linux-appimage-arm64/*.AppImage release/SpotiFLAC-linux-bundle/
tar -czf release/SpotiFLAC-linux-bundle.tar.gz -C release SpotiFLAC-linux-bundle
- name: Create Release - name: Create Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
@@ -354,15 +364,20 @@ jobs:
## Downloads ## Downloads
- `SpotiFLAC.exe` - Windows - `SpotiFLAC-windows.zip` - amd64
- `SpotiFLAC.dmg` - macOS - `SpotiFLAC-macos-bundle.zip` - amd64 + arm64
- `SpotiFLAC.AppImage` - Linux - `SpotiFLAC-linux-bundle.tar.gz` - amd64 + arm64v8
<details> <details>
<summary><b>Linux Requirements</b></summary> <summary><b>Linux Requirements</b></summary>
The AppImage requires `webkit2gtk-4.1` to be installed on your system: The AppImage requires `webkit2gtk-4.1` to be installed on your system:
Choose the correct AppImage after extracting the bundle:
- `SpotiFLAC-linux-amd64.AppImage` - amd64
- `SpotiFLAC-linux-arm64.AppImage` - arm64v8
**Ubuntu/Debian:** **Ubuntu/Debian:**
```bash ```bash
sudo apt install libwebkit2gtk-4.1-0 sudo apt install libwebkit2gtk-4.1-0
@@ -380,14 +395,14 @@ jobs:
After installing the dependency, make the AppImage executable: After installing the dependency, make the AppImage executable:
```bash ```bash
chmod +x SpotiFLAC.AppImage tar -xzf SpotiFLAC-linux-bundle.tar.gz
./SpotiFLAC.AppImage chmod +x SpotiFLAC-linux-*.AppImage
``` ```
</details> </details>
files: | files: |
artifacts/windows-portable/*.exe artifacts/windows-bundle/*.zip
artifacts/macos-portable/*.dmg artifacts/macos-bundle/*.zip
artifacts/linux-portable/*.AppImage release/SpotiFLAC-linux-bundle.tar.gz
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+96 -103
View File
@@ -83,6 +83,37 @@ func GetFFmpegDir() (string, error) {
return EnsureAppDir() return EnsureAppDir()
} }
func resolveSystemExecutable(executableName string) string {
if runtime.GOOS == "darwin" {
candidates := []string{
"/opt/homebrew/bin/" + executableName,
"/usr/local/bin/" + executableName,
}
for _, candidate := range candidates {
if _, err := os.Stat(candidate); err == nil {
return candidate
}
}
}
if runtime.GOOS != "windows" {
path, err := exec.Command("which", executableName).Output()
if err == nil {
trimmed := strings.TrimSpace(string(path))
if trimmed != "" {
return trimmed
}
}
}
path, err := exec.LookPath(executableName)
if err == nil {
return path
}
return ""
}
func GetFFmpegPath() (string, error) { func GetFFmpegPath() (string, error) {
ffmpegDir, err := GetFFmpegDir() ffmpegDir, err := GetFFmpegDir()
if err != nil { if err != nil {
@@ -94,38 +125,15 @@ func GetFFmpegPath() (string, error) {
ffmpegName = "ffmpeg.exe" ffmpegName = "ffmpeg.exe"
} }
if path := resolveSystemExecutable(ffmpegName); path != "" {
return path, nil
}
localPath := filepath.Join(ffmpegDir, ffmpegName) localPath := filepath.Join(ffmpegDir, ffmpegName)
if _, err := os.Stat(localPath); err == nil { if _, err := os.Stat(localPath); err == nil {
return localPath, nil return localPath, nil
} }
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
homebrewPath := "/opt/homebrew/bin/" + ffmpegName
if _, err := os.Stat(homebrewPath); err == nil {
return homebrewPath, nil
}
} else if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" {
homebrewPath := "/usr/local/bin/" + ffmpegName
if _, err := os.Stat(homebrewPath); err == nil {
return homebrewPath, nil
}
}
if runtime.GOOS != "windows" {
path, err := exec.Command("which", ffmpegName).Output()
if err == nil {
trimmed := strings.TrimSpace(string(path))
if trimmed != "" {
return trimmed, nil
}
}
}
path, err := exec.LookPath(ffmpegName)
if err == nil {
return path, nil
}
return localPath, nil return localPath, nil
} }
@@ -140,38 +148,15 @@ func GetFFprobePath() (string, error) {
ffprobeName = "ffprobe.exe" ffprobeName = "ffprobe.exe"
} }
if path := resolveSystemExecutable(ffprobeName); path != "" {
return path, nil
}
localPath := filepath.Join(ffmpegDir, ffprobeName) localPath := filepath.Join(ffmpegDir, ffprobeName)
if _, err := os.Stat(localPath); err == nil { if _, err := os.Stat(localPath); err == nil {
return localPath, nil return localPath, nil
} }
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
homebrewPath := "/opt/homebrew/bin/" + ffprobeName
if _, err := os.Stat(homebrewPath); err == nil {
return homebrewPath, nil
}
} else if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" {
homebrewPath := "/usr/local/bin/" + ffprobeName
if _, err := os.Stat(homebrewPath); err == nil {
return homebrewPath, nil
}
}
if runtime.GOOS != "windows" {
path, err := exec.Command("which", ffprobeName).Output()
if err == nil {
trimmed := strings.TrimSpace(string(path))
if trimmed != "" {
return trimmed, nil
}
}
}
path, err := exec.LookPath(ffprobeName)
if err == nil {
return path, nil
}
return localPath, fmt.Errorf("ffprobe not found in app directory or system path") return localPath, fmt.Errorf("ffprobe not found in app directory or system path")
} }
@@ -205,7 +190,11 @@ func IsFFmpegInstalled() (bool, error) {
setHideWindow(cmd) setHideWindow(cmd)
err = cmd.Run() err = cmd.Run()
return err == nil, nil if err != nil {
return false, nil
}
return IsFFprobeInstalled()
} }
func GetBrewPath() string { func GetBrewPath() string {
@@ -255,10 +244,38 @@ func InstallFFmpegWithBrew(progressCallback func(int, string)) error {
return nil return nil
} }
const ( const ffmpegReleaseBaseURL = "https://github.com/afkarxyz/ffmpeg-binaries/releases/download/v8.1"
ffmpegWindowsURL = "https://github.com/afkarxyz/ffmpeg-binaries/releases/download/v8.0/ffmpeg-windows-amd64.zip"
ffmpegLinuxURL = "https://github.com/afkarxyz/ffmpeg-binaries/releases/download/v8.0/ffmpeg-linux-amd64.tar.xz" func buildFFmpegReleaseURL(assetName string) string {
) return ffmpegReleaseBaseURL + "/" + assetName
}
func getFFmpegDownloadURLs() ([]string, []string, error) {
switch runtime.GOOS {
case "windows":
return []string{buildFFmpegReleaseURL("ffmpeg-windows.zip")}, []string{buildFFmpegReleaseURL("ffprobe-windows.zip")}, nil
case "linux":
switch runtime.GOARCH {
case "amd64":
return []string{buildFFmpegReleaseURL("ffmpeg-linux-amd64.zip")}, []string{buildFFmpegReleaseURL("ffprobe-linux-amd64.zip")}, nil
case "arm64":
return []string{buildFFmpegReleaseURL("ffmpeg-linux-arm64v8.zip")}, []string{buildFFmpegReleaseURL("ffprobe-linux-arm64v8.zip")}, nil
default:
return nil, nil, fmt.Errorf("unsupported Linux architecture: %s", runtime.GOARCH)
}
case "darwin":
switch runtime.GOARCH {
case "amd64":
return []string{buildFFmpegReleaseURL("ffmpeg-macos-amd64.zip")}, []string{buildFFmpegReleaseURL("ffprobe-macos-amd64.zip")}, nil
case "arm64":
return []string{buildFFmpegReleaseURL("ffmpeg-macos-arm64.zip")}, []string{buildFFmpegReleaseURL("ffprobe-macos-arm64.zip")}, nil
default:
return nil, nil, fmt.Errorf("unsupported macOS architecture: %s", runtime.GOARCH)
}
default:
return nil, nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
}
}
func DownloadFFmpeg(progressCallback func(int)) error { func DownloadFFmpeg(progressCallback func(int)) error {
@@ -276,57 +293,30 @@ 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)
} }
if runtime.GOOS == "darwin" { ffmpegInstalled, _ := IsFFmpegInstalled()
ffmpegInstalled, _ := IsFFmpegInstalled() ffprobeInstalled, _ := IsFFprobeInstalled()
ffprobeInstalled, _ := IsFFprobeInstalled()
isARM := runtime.GOARCH == "arm64" ffmpegURLs, ffprobeURLs, err := getFFmpegDownloadURLs()
if err != nil {
return err
}
var macFFmpegURLs []string if !ffmpegInstalled && !ffprobeInstalled {
var macFFprobeURLs []string if err := downloadWithFallback(ffmpegURLs, ffmpegDir, progressCallback, 0, 50); err != nil {
return err
if isARM {
macFFmpegURLs = []string{"https://github.com/afkarxyz/ffmpeg-binaries/releases/download/v8.0/ffmpeg-macos-arm64.zip"}
macFFprobeURLs = []string{"https://github.com/afkarxyz/ffmpeg-binaries/releases/download/v8.0/ffprobe-macos-arm64.zip"}
} else {
macFFmpegURLs = []string{"https://github.com/afkarxyz/ffmpeg-binaries/releases/download/v8.0/ffmpeg-macos-intel.zip"}
macFFprobeURLs = []string{"https://github.com/afkarxyz/ffmpeg-binaries/releases/download/v8.0/ffprobe-macos-intel.zip"}
} }
if err := downloadWithFallback(ffprobeURLs, ffmpegDir, progressCallback, 50, 100); err != nil {
if !ffmpegInstalled && !ffprobeInstalled { return err
if err := downloadWithFallback(macFFmpegURLs, ffmpegDir, progressCallback, 0, 50); err != nil {
return err
}
if err := downloadWithFallback(macFFprobeURLs, ffmpegDir, progressCallback, 50, 100); err != nil {
return err
}
} else if !ffmpegInstalled {
if err := downloadWithFallback(macFFmpegURLs, ffmpegDir, progressCallback, 0, 100); err != nil {
return err
}
} else if !ffprobeInstalled {
if err := downloadWithFallback(macFFprobeURLs, ffmpegDir, progressCallback, 0, 100); err != nil {
return err
}
} }
return nil return nil
} }
var url string if !ffmpegInstalled {
switch runtime.GOOS { return downloadWithFallback(ffmpegURLs, ffmpegDir, progressCallback, 0, 100)
case "windows":
url = ffmpegWindowsURL
case "linux":
url = ffmpegLinuxURL
default:
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
} }
fmt.Printf("[FFmpeg] Downloading from: %s\n", url) if !ffprobeInstalled {
if err := downloadAndExtract(url, ffmpegDir, progressCallback, 0, 100); err != nil { return downloadWithFallback(ffprobeURLs, ffmpegDir, progressCallback, 0, 100)
return err
} }
return nil return nil
@@ -452,10 +442,13 @@ func downloadAndExtract(url, destDir string, progressCallback func(int), progres
} }
fmt.Printf("[FFmpeg] Extracting...\n") fmt.Printf("[FFmpeg] Extracting...\n")
if strings.HasSuffix(url, ".tar.xz") || runtime.GOOS == "linux" { if strings.HasSuffix(url, ".tar.xz") {
return extractTarXz(tmpFile.Name(), destDir) return extractTarXz(tmpFile.Name(), destDir)
} }
return extractZip(tmpFile.Name(), destDir) if strings.HasSuffix(url, ".zip") {
return extractZip(tmpFile.Name(), destDir)
}
return fmt.Errorf("unsupported archive format for %s", url)
} }
func extractZip(zipPath, destDir string) error { func extractZip(zipPath, destDir string) error {
+10 -19
View File
@@ -5,7 +5,7 @@ import { Search, X, ArrowUp } from "lucide-react";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { getSettings, getSettingsWithDefaults, loadSettings, saveSettings, applyThemeMode, applyFont } from "@/lib/settings"; import { getSettings, getSettingsWithDefaults, loadSettings, saveSettings, applyThemeMode, applyFont } from "@/lib/settings";
import { applyTheme } from "@/lib/themes"; import { applyTheme } from "@/lib/themes";
import { OpenFolder, CheckFFmpegInstalled, DownloadFFmpeg, GetBrewPath, GetRecentFetches, InstallFFmpegWithBrew, SaveRecentFetches } from "../wailsjs/go/main/App"; import { OpenFolder, CheckFFmpegInstalled, DownloadFFmpeg, GetRecentFetches, SaveRecentFetches } from "../wailsjs/go/main/App";
import { EventsOn, EventsOff, Quit } from "../wailsjs/runtime/runtime"; import { EventsOn, EventsOff, Quit } from "../wailsjs/runtime/runtime";
import { toastWithSound as toast } from "@/lib/toast-with-sound"; import { toastWithSound as toast } from "@/lib/toast-with-sound";
import { TitleBar } from "@/components/TitleBar"; import { TitleBar } from "@/components/TitleBar";
@@ -153,7 +153,6 @@ function App() {
const downloadQueue = useDownloadQueueDialog(); const downloadQueue = useDownloadQueueDialog();
const downloadProgress = useDownloadProgress(); const downloadProgress = useDownloadProgress();
const [isFFmpegInstalled, setIsFFmpegInstalled] = useState<boolean | null>(null); const [isFFmpegInstalled, setIsFFmpegInstalled] = useState<boolean | null>(null);
const [brewPath, setBrewPath] = useState<string>("");
const [isInstallingFFmpeg, setIsInstallingFFmpeg] = useState(false); const [isInstallingFFmpeg, setIsInstallingFFmpeg] = useState(false);
const [ffmpegInstallProgress, setFfmpegInstallProgress] = useState(0); const [ffmpegInstallProgress, setFfmpegInstallProgress] = useState(0);
const [ffmpegInstallStatus, setFfmpegInstallStatus] = useState(""); const [ffmpegInstallStatus, setFfmpegInstallStatus] = useState("");
@@ -181,8 +180,6 @@ function App() {
try { try {
const installed = await CheckFFmpegInstalled(); const installed = await CheckFFmpegInstalled();
setIsFFmpegInstalled(installed); setIsFFmpegInstalled(installed);
const brew = await GetBrewPath();
setBrewPath(brew);
} }
catch (err) { catch (err) {
console.error("Failed to check FFmpeg:", err); console.error("Failed to check FFmpeg:", err);
@@ -263,7 +260,7 @@ function App() {
localStorage.removeItem(HISTORY_KEY); localStorage.removeItem(HISTORY_KEY);
} }
}, [persistRecentHistory]); }, [persistRecentHistory]);
const handleInstallFFmpeg = async (useBrew: boolean = false) => { const handleInstallFFmpeg = async () => {
setIsInstallingFFmpeg(true); setIsInstallingFFmpeg(true);
setFfmpegInstallProgress(0); setFfmpegInstallProgress(0);
setFfmpegInstallStatus("starting"); setFfmpegInstallStatus("starting");
@@ -280,11 +277,11 @@ function App() {
EventsOn("ffmpeg:status", (status: string) => { EventsOn("ffmpeg:status", (status: string) => {
setFfmpegInstallStatus(status); setFfmpegInstallStatus(status);
}); });
const response = useBrew ? await InstallFFmpegWithBrew() : await DownloadFFmpeg(); const response = await DownloadFFmpeg();
EventsOff("ffmpeg:progress"); EventsOff("ffmpeg:progress");
EventsOff("ffmpeg:status"); EventsOff("ffmpeg:status");
if (response.success) { if (response.success) {
toast.success(useBrew ? "FFmpeg installed successfully via Homebrew!" : "FFmpeg installed successfully!"); toast.success("FFmpeg installed successfully!");
setIsFFmpegInstalled(true); setIsFFmpegInstalled(true);
} }
else { else {
@@ -664,13 +661,9 @@ function App() {
FFmpeg Required FFmpeg Required
</DialogTitle> </DialogTitle>
<DialogDescription className="text-sm text-foreground/70 leading-relaxed font-normal"> <DialogDescription className="text-sm text-foreground/70 leading-relaxed font-normal">
{brewPath ? (<> SpotiFLAC checks your system for FFmpeg and FFprobe first.
FFmpeg is essential for SpotiFLAC to function properly. If they are not available, the required binaries will be downloaded from GitHub.
Homebrew detected. Recommended: <span className="text-foreground font-semibold">brew install ffmpeg</span> This setup downloads about <span className="text-foreground font-semibold">30-40MB</span> of data.
</>) : (<>
FFmpeg is essential for SpotiFLAC to function properly.
This setup will download about <span className="text-foreground font-semibold">100-200MB</span> of data.
</>)}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -698,15 +691,13 @@ function App() {
</div>)} </div>)}
</div>)} </div>)}
<DialogFooter className={`flex-row gap-3 pt-2 ${brewPath ? 'flex-col' : ''}`}> <DialogFooter className="flex-row gap-3 pt-2">
{!isInstallingFFmpeg && (<Button variant="outline" className="flex-1 h-11 text-sm font-bold transition-colors" onClick={() => Quit()}> {!isInstallingFFmpeg && (<Button variant="outline" className="flex-1 h-11 text-sm font-bold transition-colors" onClick={() => Quit()}>
Exit Exit
</Button>)} </Button>)}
{brewPath ? (<Button className="flex-1 h-11 text-sm font-bold shadow-lg shadow-primary/10" onClick={() => handleInstallFFmpeg(true)} disabled={isInstallingFFmpeg}> <Button className={`${isInstallingFFmpeg ? 'w-full' : 'flex-1'} h-11 text-sm font-bold shadow-lg shadow-primary/10`} onClick={handleInstallFFmpeg} disabled={isInstallingFFmpeg}>
{isInstallingFFmpeg ? "Installing..." : "Install via Homebrew"}
</Button>) : (<Button className={`${isInstallingFFmpeg ? 'w-full' : 'flex-1'} h-11 text-sm font-bold shadow-lg shadow-primary/10`} onClick={() => handleInstallFFmpeg(false)} disabled={isInstallingFFmpeg}>
{isInstallingFFmpeg ? "Installing..." : "Install now"} {isInstallingFFmpeg ? "Installing..." : "Install now"}
</Button>)} </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>