.update ffmpeg + linux arm build
This commit is contained in:
+56
-41
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user