-
-
About
-
+
+
About
+
-
- setActiveTab("bug_report")} className="rounded-b-none">
-
- Bug Report
-
- setActiveTab("feature_request")} className="rounded-b-none">
-
- Feature Request
-
- setActiveTab("faq")} className="rounded-b-none">
-
- FAQ
-
- setActiveTab("projects")} className="rounded-b-none">
-
- Other Projects
-
- setActiveTab("support")} className="rounded-b-none">
-
- Support Us
-
-
+
+ setActiveTab("bug_report")} className="rounded-b-none">
+
+ Bug Report
+
+ setActiveTab("feature_request")} className="rounded-b-none">
+
+ Feature Request
+
+ setActiveTab("faq")} className="rounded-b-none">
+
+ FAQ
+
+ setActiveTab("projects")} className="rounded-b-none">
+
+ Other Projects
+
+ setActiveTab("support")} className="rounded-b-none">
+
+ Support Me
+
+
-
- {activeTab === "bug_report" && (
-
-
-
-
- Problem
-
-
- Additional Context
-
-
-
-
-
Type
-
{
+
+ {activeTab === "bug_report" && (
+
+
+
+
+ Problem
+
+
+ Additional Context
+
+
+
+
+ Type
+ {
if (val)
setBugType(val);
}} className="justify-start w-full cursor-pointer">
- Track
- Album
- Playlist
- Artist
-
-
-
- Spotify URL
- setSpotifyUrl(e.target.value)}/>
-
-
-
-
+
+ Track
+
+
+ Album
+
+
+ Playlist
+
+
+ Artist
+
+
-
-
- Create Issue on GitHub
-
+
+ Spotify URL
+ setSpotifyUrl(e.target.value)}/>
-
)}
-
- {activeTab === "feature_request" && (
-
-
-
-
- Description
-
-
- Use Case
-
-
- Additional Context
-
-
-
-
-
-
-
- Create Issue on GitHub
-
-
-
)}
-
- {activeTab === "faq" && (
-
-
-
- Frequently Asked Questions
-
-
- {faqs.map((faq, index) => ())}
-
-
-
- )}
-
- {activeTab === "projects" && (
-
-
-
openExternal("https://exyezed.cc/")}>
-
- Browser Extensions & Scripts
-
-
-
-
-
-
-
-
openExternal("https://spotubedl.com/")}>
-
- SpotubeDL
- Download Spotify Tracks, Albums, Playlists as MP3/OGG/Opus with High Quality.
-
-
-
-
openExternal("https://github.com/afkarxyz/SpotiDownloader")}>
-
- SpotiDownloader
- Get Spotify tracks in MP3 and FLAC via spotidownloader.com
-
- {repoStats['SpotiDownloader'] && (
-
- {repoStats['SpotiDownloader'].languages?.map((lang: string) => ({lang} ))}
-
-
- {formatNumber(repoStats['SpotiDownloader'].stars)}
- {repoStats['SpotiDownloader'].forks}
- {formatTimeAgo(repoStats['SpotiDownloader'].createdAt)}
-
-
- TOTAL: {formatNumber(repoStats['SpotiDownloader'].totalDownloads)}
- LATEST: {formatNumber(repoStats['SpotiDownloader'].latestDownloads)}
-
- )}
-
-
openExternal("https://github.com/spotiverse/SpotiFLAC-Next")}>
-
- SpotiFLAC Next
- Get Spotify tracks in Hi-Res lossless FLACs — no account required.
-
- {repoStats['SpotiFLAC-Next'] && (
-
- {repoStats['SpotiFLAC-Next'].languages?.map((lang: string) => ({lang} ))}
-
-
- {formatNumber(repoStats['SpotiFLAC-Next'].stars)}
- {repoStats['SpotiFLAC-Next'].forks}
- {formatTimeAgo(repoStats['SpotiFLAC-Next'].createdAt)}
-
-
- TOTAL: {formatNumber(repoStats['SpotiFLAC-Next'].totalDownloads)}
- LATEST: {formatNumber(repoStats['SpotiFLAC-Next'].latestDownloads)}
-
- )}
-
-
openExternal("https://github.com/afkarxyz/Twitter-X-Media-Batch-Downloader")}>
-
- Twitter/X Media Batch Downloader
- A GUI tool to download original-quality images and videos from Twitter/X accounts, powered by gallery-dl by @mikf
-
- {repoStats['Twitter-X-Media-Batch-Downloader'] && (
-
- {repoStats['Twitter-X-Media-Batch-Downloader'].languages?.map((lang: string) => ({lang} ))}
-
-
- {formatNumber(repoStats['Twitter-X-Media-Batch-Downloader'].stars)}
- {repoStats['Twitter-X-Media-Batch-Downloader'].forks}
- {formatTimeAgo(repoStats['Twitter-X-Media-Batch-Downloader'].createdAt)}
-
-
- TOTAL: {formatNumber(repoStats['Twitter-X-Media-Batch-Downloader'].totalDownloads)}
- LATEST: {formatNumber(repoStats['Twitter-X-Media-Batch-Downloader'].latestDownloads)}
-
- )}
-
-
-
)}
-
-
- {activeTab === "support" && (
-
-
Support Our Work
-
- If this software is useful and brings you value, consider supporting the project by buying me a coffee. Your support helps keep development going.
-
+
+
+
+
+
+ Create Issue on GitHub
+
+
+ )}
-
-
openExternal("https://ko-fi.com/afkarxyz")}>
-
- Support me on Ko-fi
-
-
-
openExternal("https://buymeacoffee.com/afkarxyz")}>
-
- Buy Me a Coffee
-
+ {activeTab === "feature_request" && (
+
+
+
+
+ Description
+
+
+ Use Case
+
+
+ Additional Context
+
+
-
)}
-
+
+
+
+
+ Create Issue on GitHub
+
+
+
)}
+
+ {activeTab === "faq" && (
+
+
+
+ Frequently Asked Questions
+
+
+ {faqs.map((faq, index) => (
+
+ {faq.q}
+
+
+ {faq.a}
+
+
))}
+
+
+
+ )}
+
+ {activeTab === "projects" && (
+
+
+
openExternal("https://exyezed.cc/")}>
+
+ Browser Extensions & Scripts
+
+
+
+
+
+
+
+
openExternal("https://spotubedl.com/")}>
+
+
+ {" "}
+ SpotubeDL
+
+
+ Download Spotify Tracks, Albums, Playlists as MP3/OGG/Opus
+ with High Quality.
+
+
+
+
+
openExternal("https://github.com/afkarxyz/SpotiDownloader")}>
+
+
+ {" "}
+ SpotiDownloader
+
+
+ Get Spotify tracks in MP3 and FLAC via spotidownloader.com
+
+
+ {repoStats["SpotiDownloader"] && (
+
+ {repoStats["SpotiDownloader"].languages?.map((lang: string) => (
+ {lang}
+ ))}
+
+
+
+ {" "}
+ {formatNumber(repoStats["SpotiDownloader"].stars)}
+
+
+ {" "}
+ {repoStats["SpotiDownloader"].forks}
+
+
+ {" "}
+ {formatTimeAgo(repoStats["SpotiDownloader"].createdAt)}
+
+
+
+
+ TOTAL:{" "}
+ {formatNumber(repoStats["SpotiDownloader"].totalDownloads)}
+
+
+ LATEST:{" "}
+ {formatNumber(repoStats["SpotiDownloader"].latestDownloads)}
+
+
+ )}
+
+
openExternal("https://github.com/spotiverse/SpotiFLAC-Next")}>
+
+
+ {" "}
+ SpotiFLAC Next
+
+
+ Get Spotify tracks in Hi-Res lossless FLACs — no account
+ required.
+
+
+ {repoStats["SpotiFLAC-Next"] && (
+
+ {repoStats["SpotiFLAC-Next"].languages?.map((lang: string) => (
+ {lang}
+ ))}
+
+
+
+ {" "}
+ {formatNumber(repoStats["SpotiFLAC-Next"].stars)}
+
+
+ {" "}
+ {repoStats["SpotiFLAC-Next"].forks}
+
+
+ {" "}
+ {formatTimeAgo(repoStats["SpotiFLAC-Next"].createdAt)}
+
+
+
+
+ TOTAL:{" "}
+ {formatNumber(repoStats["SpotiFLAC-Next"].totalDownloads)}
+
+
+ LATEST:{" "}
+ {formatNumber(repoStats["SpotiFLAC-Next"].latestDownloads)}
+
+
+ )}
+
+
openExternal("https://github.com/afkarxyz/Twitter-X-Media-Batch-Downloader")}>
+
+
+ {" "}
+ Twitter/X Media Batch Downloader
+
+
+ A GUI tool to download original-quality images and videos
+ from Twitter/X accounts, powered by gallery-dl by @mikf
+
+
+ {repoStats["Twitter-X-Media-Batch-Downloader"] && (
+
+ {repoStats["Twitter-X-Media-Batch-Downloader"].languages?.map((lang: string) => (
+ {lang}
+ ))}
+
+
+
+ {" "}
+ {formatNumber(repoStats["Twitter-X-Media-Batch-Downloader"].stars)}
+
+
+ {" "}
+ {repoStats["Twitter-X-Media-Batch-Downloader"].forks}
+
+
+ {" "}
+ {formatTimeAgo(repoStats["Twitter-X-Media-Batch-Downloader"]
+ .createdAt)}
+
+
+
+
+ TOTAL:{" "}
+ {formatNumber(repoStats["Twitter-X-Media-Batch-Downloader"]
+ .totalDownloads)}
+
+
+ LATEST:{" "}
+ {formatNumber(repoStats["Twitter-X-Media-Batch-Downloader"]
+ .latestDownloads)}
+
+
+ )}
+
+
+
)}
+
+ {activeTab === "support" && (
+
+
Support Me
+
+ If this software is useful and brings you value, consider
+ supporting the project on Ko-fi. Your support helps keep
+ development going.
+
+
+
+
+
openExternal("https://ko-fi.com/afkarxyz")}>
+
+ Support me on Ko-fi
+
+
+
)}
+
);
}
diff --git a/frontend/src/components/SettingsPage.tsx b/frontend/src/components/SettingsPage.tsx
index 75e2c52..cd2fbd0 100644
--- a/frontend/src/components/SettingsPage.tsx
+++ b/frontend/src/components/SettingsPage.tsx
@@ -451,6 +451,15 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
Embed Max Quality Cover
+
+ setTempSettings((prev) => ({
+ ...prev,
+ useSingleGenre: checked,
+ }))}/>
+
+ Use Single Genre
+
+
)}
@@ -539,6 +548,8 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
Use First Artist Only
+
+
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 782c63b..6530fea 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -10,9 +10,6 @@ import { BadgeAlertIcon } from "@/components/ui/badge-alert";
import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
import { openExternal } from "@/lib/utils";
-import BmcLogo from "@/assets/bmc-logo-side.svg";
-import BmcLogoWhite from "@/assets/bmc-logo-side-white.svg";
-import KofiLogo from "@/assets/kofi_symbol.svg";
export type PageType = "main" | "settings" | "debug" | "audio-analysis" | "audio-converter" | "file-manager" | "about" | "history";
interface SidebarProps {
currentPage: PageType;
@@ -20,118 +17,106 @@ interface SidebarProps {
}
export function Sidebar({ currentPage, onPageChange }: SidebarProps) {
return (
-
+
+
+
+ onPageChange("main")}>
+
+
+
+
+ Home
+
+
-
-
- onPageChange("main")}>
-
-
-
-
- Home
-
-
+
+
+ onPageChange("history")}>
+
+
+
+
+ History
+
+
-
-
- onPageChange("history")}>
-
-
-
-
- History
-
-
+
+
+ onPageChange("audio-analysis")}>
+
+
+
+
+ Audio Quality Analyzer
+
+
-
-
- onPageChange("audio-analysis")}>
-
-
-
-
- Audio Quality Analyzer
-
-
+
+
+ onPageChange("audio-converter")}>
+
+
+
+
+ Audio Converter
+
+
-
-
- onPageChange("audio-converter")}>
-
-
-
-
- Audio Converter
-
-
+
+
+ onPageChange("file-manager")}>
+
+
+
+
+ File Manager
+
+
-
-
- onPageChange("file-manager")}>
-
-
-
-
- File Manager
-
-
+
+
+ onPageChange("debug")}>
+
+
+
+
+ Debug Logs
+
+
-
-
- onPageChange("debug")}>
-
-
-
-
- Debug Logs
-
-
-
-
-
- onPageChange("settings")}>
-
-
-
-
- Settings
-
-
-
-
-
-
-
-
- onPageChange("about")}>
-
-
-
-
- About
-
-
-
-
-
-
-
-
-
-
-
-
openExternal("https://ko-fi.com/afkarxyz")} className="flex items-center gap-2 px-3 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground rounded-sm transition-colors text-left w-full">
-
- Support me on Ko-fi
-
-
openExternal("https://buymeacoffee.com/afkarxyz")} className="flex items-center gap-2 px-3 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground rounded-sm transition-colors text-left w-full">
-
-
- Buy Me a Coffee
-
-
+
+
+ onPageChange("settings")}>
+
+
+
+
+ Settings
+
+
-
-
);
+
+
+
+
+ onPageChange("about")}>
+
+
+
+
+ About
+
+
+
+
+ openExternal("https://ko-fi.com/afkarxyz")}>
+
+
+
+
+ Support me on Ko-fi
+
+
+
+
);
}
diff --git a/frontend/src/hooks/useDownload.ts b/frontend/src/hooks/useDownload.ts
index 0b3aa9d..f881b38 100644
--- a/frontend/src/hooks/useDownload.ts
+++ b/frontend/src/hooks/useDownload.ts
@@ -201,6 +201,7 @@ export function useDownload(region: string) {
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
+ use_single_genre: settings.useSingleGenre,
});
if (response.success) {
logger.success(`tidal: ${trackName} - ${artistName}`);
@@ -242,6 +243,7 @@ export function useDownload(region: string) {
spotify_total_discs: spotifyTotalDiscs,
copyright: copyright,
publisher: publisher,
+ use_single_genre: settings.useSingleGenre,
});
if (response.success) {
logger.success(`amazon: ${trackName} - ${artistName}`);
@@ -283,6 +285,7 @@ export function useDownload(region: string) {
spotify_total_discs: spotifyTotalDiscs,
copyright: copyright,
publisher: publisher,
+ use_single_genre: settings.useSingleGenre,
});
if (response.success) {
logger.success(`qobuz: ${trackName} - ${artistName}`);
@@ -337,6 +340,7 @@ export function useDownload(region: string) {
spotify_total_discs: spotifyTotalDiscs,
copyright: copyright,
publisher: publisher,
+ use_single_genre: settings.useSingleGenre,
});
if (!singleServiceResponse.success && itemID) {
const { MarkDownloadItemFailed } = await import("../../wailsjs/go/main/App");
@@ -451,6 +455,7 @@ export function useDownload(region: string) {
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
+ use_single_genre: settings.useSingleGenre,
});
if (response.success) {
return response;
@@ -490,6 +495,7 @@ export function useDownload(region: string) {
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
+ use_single_genre: settings.useSingleGenre,
});
if (response.success) {
return response;
@@ -530,6 +536,7 @@ export function useDownload(region: string) {
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
+ use_single_genre: settings.useSingleGenre,
});
if (response.success) {
return response;
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index c1f96c4..e46a9ed 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -13,6 +13,9 @@ export async function fetchSpotifyMetadata(url: string, batch: boolean = true, d
}
export async function downloadTrack(request: DownloadRequest): Promise
{
const req = new main.DownloadRequest(request);
+ if (request.use_single_genre !== undefined) {
+ (req as any).use_single_genre = request.use_single_genre;
+ }
return await DownloadTrack(req);
}
export async function checkHealth(): Promise {
diff --git a/frontend/src/lib/settings.ts b/frontend/src/lib/settings.ts
index 540053e..6dc0e05 100644
--- a/frontend/src/lib/settings.ts
+++ b/frontend/src/lib/settings.ts
@@ -31,6 +31,7 @@ export interface Settings {
createPlaylistFolder: boolean;
createM3u8File: boolean;
useFirstArtistOnly: boolean;
+ useSingleGenre: boolean;
}
export const FOLDER_PRESETS: Record {
if (!('useFirstArtistOnly' in parsed)) {
parsed.useFirstArtistOnly = false;
}
+ if (!('useSingleGenre' in parsed)) {
+ parsed.useSingleGenre = false;
+ }
cachedSettings = { ...DEFAULT_SETTINGS, ...parsed };
return cachedSettings!;
}
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index e9da5fa..79edc78 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -138,6 +138,7 @@ export interface DownloadRequest {
publisher?: string;
spotify_url?: string;
use_first_artist_only?: boolean;
+ use_single_genre?: boolean;
}
export interface DownloadResponse {
success: boolean;
diff --git a/go.mod b/go.mod
index fc92ade..640ceb3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
-module spotiflac
+module github.com/afkarxyz/SpotiFLAC
-go 1.25.5
+go 1.26
require (
github.com/bogem/id3v2/v2 v2.1.4
@@ -12,6 +12,7 @@ require (
github.com/ulikunitz/xz v0.5.15
github.com/wailsapp/wails/v2 v2.11.0
go.etcd.io/bbolt v1.4.3
+ golang.org/x/text v0.31.0
)
require (
@@ -45,5 +46,4 @@ require (
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
- golang.org/x/text v0.31.0 // indirect
)
diff --git a/main.go b/main.go
index 597a07c..62c9f9e 100644
--- a/main.go
+++ b/main.go
@@ -2,8 +2,11 @@ package main
import (
"embed"
+ "encoding/json"
"log"
+ "github.com/afkarxyz/SpotiFLAC/backend"
+
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
@@ -13,8 +16,21 @@ import (
//go:embed all:frontend/dist
var assets embed.FS
+//go:embed wails.json
+var wailsJSON []byte
+
func main() {
+ type wailsInfo struct {
+ Info struct {
+ ProductVersion string `json:"productVersion"`
+ } `json:"info"`
+ }
+ var config wailsInfo
+ if err := json.Unmarshal(wailsJSON, &config); err == nil && config.Info.ProductVersion != "" {
+ backend.AppVersion = config.Info.ProductVersion
+ }
+
app := NewApp()
err := wails.Run(&options.App{
diff --git a/wails.json b/wails.json
index 4d98716..978c3c6 100644
--- a/wails.json
+++ b/wails.json
@@ -12,10 +12,10 @@
},
"info": {
"productName": "SpotiFLAC",
- "productVersion": "7.0.9",
+ "productVersion": "7.1.0",
"copyright": "© 2026 afkarxyz"
},
"wailsjsdir": "./frontend",
"assetdir": "./frontend/dist",
"reloaddirs": "./frontend/src"
-}
\ No newline at end of file
+}