.tidal alt

This commit is contained in:
afkarxyz
2026-04-19 23:12:06 +07:00
parent a24ca370eb
commit 7c52c2d9b4
9 changed files with 369 additions and 285 deletions
+30 -11
View File
@@ -102,6 +102,9 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
const handleTidalQualityChange = async (value: "LOSSLESS" | "HI_RES_LOSSLESS") => {
setTempSettings((prev) => ({ ...prev, tidalQuality: value }));
};
const handleTidalVariantChange = (value: "tidal" | "alt") => {
setTempSettings((prev) => ({ ...prev, tidalVariant: value }));
};
const handleQobuzQualityChange = (value: "6" | "7" | "27") => {
setTempSettings((prev) => ({ ...prev, qobuzQuality: value }));
};
@@ -424,17 +427,19 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
</Select>
</>)}
{tempSettings.downloader === "tidal" && (<Select value={tempSettings.tidalQuality} onValueChange={handleTidalQualityChange}>
<SelectTrigger className="h-9 w-fit">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="LOSSLESS">16-bit/44.1kHz</SelectItem>
<SelectItem value="HI_RES_LOSSLESS">
24-bit/48kHz
</SelectItem>
</SelectContent>
</Select>)}
{tempSettings.downloader === "tidal" && (tempSettings.tidalVariant === "alt" ? (<div className="h-9 px-3 flex items-center text-sm font-medium border border-input rounded-md bg-muted/30 text-muted-foreground whitespace-nowrap cursor-default">
16-bit/44.1kHz
</div>) : (<Select value={tempSettings.tidalQuality} onValueChange={handleTidalQualityChange}>
<SelectTrigger className="h-9 w-fit">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="LOSSLESS">16-bit/44.1kHz</SelectItem>
<SelectItem value="HI_RES_LOSSLESS">
24-bit/48kHz
</SelectItem>
</SelectContent>
</Select>))}
{tempSettings.downloader === "qobuz" && (<Select value={tempSettings.qobuzQuality} onValueChange={handleQobuzQualityChange}>
<SelectTrigger className="h-9 w-fit">
@@ -452,7 +457,21 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
</div>
{(tempSettings.downloader === "tidal" || tempSettings.downloader === "auto") && (<div className="space-y-2 pt-2">
<Label htmlFor="tidal-variant">Tidal Variant</Label>
<Select value={tempSettings.tidalVariant || "tidal"} onValueChange={handleTidalVariantChange}>
<SelectTrigger id="tidal-variant" className="h-9 w-fit min-w-[160px]">
<SelectValue placeholder="Select Tidal variant"/>
</SelectTrigger>
<SelectContent>
<SelectItem value="tidal">Tidal</SelectItem>
<SelectItem value="alt">Tidal Alt.</SelectItem>
</SelectContent>
</Select>
</div>)}
{((tempSettings.downloader === "tidal" &&
tempSettings.tidalVariant !== "alt" &&
tempSettings.tidalQuality === "HI_RES_LOSSLESS") ||
(tempSettings.downloader === "qobuz" &&
tempSettings.qobuzQuality === "27") ||
+51 -24
View File
@@ -52,6 +52,24 @@ async function resolveTemplateISRC(settings: {
return "";
}
}
function getTidalVariant(settings: any): "tidal" | "alt" {
return settings?.tidalVariant === "alt" ? "alt" : "tidal";
}
function isTidalAltVariant(settings: any): boolean {
return getTidalVariant(settings) === "alt";
}
function getTidalAudioFormat(settings: any, mode: "single" | "auto"): "LOSSLESS" | "HI_RES_LOSSLESS" {
if (isTidalAltVariant(settings)) {
return "LOSSLESS";
}
if (mode === "auto") {
return (settings.autoQuality || "24") === "24" ? "HI_RES_LOSSLESS" : "LOSSLESS";
}
return settings.tidalQuality || "LOSSLESS";
}
function shouldFetchStreamingURLs(order: string[], settings: any): boolean {
return order.includes("amazon") || (order.includes("tidal") && !isTidalAltVariant(settings));
}
export function useDownload(region: string) {
const [downloadProgress, setDownloadProgress] = useState<number>(0);
const [isDownloading, setIsDownloading] = useState(false);
@@ -170,8 +188,11 @@ export function useDownload(region: string) {
itemID = await AddToDownloadQueue(id, trackName || "", displayArtist || "", albumName || "");
}
if (service === "auto") {
const order = (settings.autoOrder || "tidal-amazon-qobuz").split("-");
const tidalVariant = getTidalVariant(settings);
const tidalLabel = tidalVariant === "alt" ? "Tidal Alt." : "Tidal";
let streamingURLs: any = null;
if (spotifyId) {
if (spotifyId && shouldFetchStreamingURLs(order, settings)) {
try {
const { GetStreamingURLs } = await import("../../wailsjs/go/main/App");
const urlsJson = await GetStreamingURLs(spotifyId, region);
@@ -182,16 +203,15 @@ export function useDownload(region: string) {
}
}
const durationSeconds = durationMs ? Math.round(durationMs / 1000) : undefined;
const order = (settings.autoOrder || "tidal-amazon-qobuz").split("-");
let lastResponse: any = { success: false, error: "No matching services found" };
const fallbackErrors: string[] = [];
const tidalQuality = getTidalAudioFormat(settings, "auto");
const is24Bit = (settings.autoQuality || "24") === "24";
const tidalQuality = is24Bit ? "HI_RES_LOSSLESS" : "LOSSLESS";
const qobuzQuality = is24Bit ? "27" : "6";
for (const s of order) {
if (s === "tidal" && streamingURLs?.tidal_url) {
if (s === "tidal" && ((tidalVariant === "alt" && spotifyId) || streamingURLs?.tidal_url)) {
try {
logger.debug(`trying tidal for: ${trackName} - ${artistName}`);
logger.debug(`trying ${tidalLabel} for: ${trackName} - ${artistName}`);
const response = await downloadTrack({
service: "tidal",
query,
@@ -209,7 +229,8 @@ export function useDownload(region: string) {
spotify_id: spotifyId,
embed_lyrics: settings.embedLyrics,
embed_max_quality_cover: settings.embedMaxQualityCover,
service_url: streamingURLs.tidal_url,
service_url: tidalVariant === "alt" ? undefined : streamingURLs?.tidal_url,
tidal_variant: tidalVariant,
duration: durationSeconds,
item_id: itemID,
audio_format: tidalQuality,
@@ -225,17 +246,17 @@ export function useDownload(region: string) {
embed_genre: settings.embedGenre,
});
if (response.success) {
logger.success(`tidal: ${trackName} - ${artistName}`);
logger.success(`${tidalLabel}: ${trackName} - ${artistName}`);
return response;
}
const errMsg = response.error || response.message || "Failed";
fallbackErrors.push(`[Tidal] ${errMsg}`);
fallbackErrors.push(`[${tidalLabel}] ${errMsg}`);
lastResponse = response;
logger.warning(`tidal failed, trying next...`);
logger.warning(`${tidalLabel} failed, trying next...`);
}
catch (err) {
logger.error(`tidal error: ${err}`);
fallbackErrors.push(`[Tidal] ${String(err)}`);
logger.error(`${tidalLabel} error: ${err}`);
fallbackErrors.push(`[${tidalLabel}] ${String(err)}`);
lastResponse = { success: false, error: String(err) };
}
}
@@ -344,7 +365,7 @@ export function useDownload(region: string) {
const durationSecondsForFallback = durationMs ? Math.round(durationMs / 1000) : undefined;
let audioFormat: string | undefined;
if (service === "tidal") {
audioFormat = settings.tidalQuality || "LOSSLESS";
audioFormat = getTidalAudioFormat(settings, "single");
}
else if (service === "qobuz") {
audioFormat = settings.qobuzQuality || "6";
@@ -373,6 +394,7 @@ export function useDownload(region: string) {
duration: durationSecondsForFallback,
item_id: itemID,
audio_format: audioFormat,
tidal_variant: service === "tidal" ? getTidalVariant(settings) : undefined,
spotify_track_number: spotifyTrackNumber,
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
@@ -380,6 +402,7 @@ export function useDownload(region: string) {
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
use_single_genre: settings.useSingleGenre,
embed_genre: settings.embedGenre,
});
@@ -451,8 +474,11 @@ export function useDownload(region: string) {
}
}
if (service === "auto") {
const order = (settings.autoOrder || "tidal-amazon-qobuz").split("-");
const tidalVariant = getTidalVariant(settings);
const tidalLabel = tidalVariant === "alt" ? "Tidal Alt." : "Tidal";
let streamingURLs: any = null;
if (spotifyId) {
if (spotifyId && shouldFetchStreamingURLs(order, settings)) {
try {
const { GetStreamingURLs } = await import("../../wailsjs/go/main/App");
const urlsJson = await GetStreamingURLs(spotifyId, region);
@@ -463,16 +489,15 @@ export function useDownload(region: string) {
}
}
const durationSeconds = durationMs ? Math.round(durationMs / 1000) : undefined;
const order = (settings.autoOrder || "tidal-amazon-qobuz").split("-");
let lastResponse: any = { success: false, error: "No matching services found" };
const fallbackErrors: string[] = [];
const tidalQuality = getTidalAudioFormat(settings, "auto");
const is24Bit = (settings.autoQuality || "24") === "24";
const tidalQuality = is24Bit ? "HI_RES_LOSSLESS" : "LOSSLESS";
const qobuzQuality = is24Bit ? "27" : "6";
for (const s of order) {
if (s === "tidal" && streamingURLs?.tidal_url) {
if (s === "tidal" && ((tidalVariant === "alt" && spotifyId) || streamingURLs?.tidal_url)) {
try {
logger.debug(`trying tidal for: ${trackName} - ${artistName}`);
logger.debug(`trying ${tidalLabel} for: ${trackName} - ${artistName}`);
const response = await downloadTrack({
service: "tidal",
query,
@@ -490,7 +515,8 @@ export function useDownload(region: string) {
spotify_id: spotifyId,
embed_lyrics: settings.embedLyrics,
embed_max_quality_cover: settings.embedMaxQualityCover,
service_url: streamingURLs.tidal_url,
service_url: tidalVariant === "alt" ? undefined : streamingURLs?.tidal_url,
tidal_variant: tidalVariant,
duration: durationSeconds,
item_id: itemID,
audio_format: tidalQuality,
@@ -506,17 +532,17 @@ export function useDownload(region: string) {
embed_genre: settings.embedGenre,
});
if (response.success) {
logger.success(`tidal: ${trackName} - ${artistName}`);
logger.success(`${tidalLabel}: ${trackName} - ${artistName}`);
return response;
}
const errMsg = response.error || response.message || "Failed";
fallbackErrors.push(`[Tidal] ${errMsg}`);
fallbackErrors.push(`[${tidalLabel}] ${errMsg}`);
lastResponse = response;
logger.warning(`tidal failed, trying next...`);
logger.warning(`${tidalLabel} failed, trying next...`);
}
catch (err) {
logger.error(`tidal error: ${err}`);
fallbackErrors.push(`[Tidal] ${String(err)}`);
logger.error(`${tidalLabel} error: ${err}`);
fallbackErrors.push(`[${tidalLabel}] ${String(err)}`);
lastResponse = { success: false, error: String(err) };
}
}
@@ -628,7 +654,7 @@ export function useDownload(region: string) {
const durationSecondsForFallback = durationMs ? Math.round(durationMs / 1000) : undefined;
let audioFormat: string | undefined;
if (service === "tidal") {
audioFormat = settings.tidalQuality || "LOSSLESS";
audioFormat = getTidalAudioFormat(settings, "single");
}
else if (service === "qobuz") {
audioFormat = settings.qobuzQuality || "6";
@@ -653,6 +679,7 @@ export function useDownload(region: string) {
duration: durationSecondsForFallback,
item_id: itemID,
audio_format: audioFormat,
tidal_variant: service === "tidal" ? getTidalVariant(settings) : undefined,
spotify_track_number: spotifyTrackNumber,
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
+3
View File
@@ -13,6 +13,9 @@ export async function fetchSpotifyMetadata(url: string, batch: boolean = true, d
}
export async function downloadTrack(request: DownloadRequest): Promise<DownloadResponse> {
const req = new main.DownloadRequest(request);
if (request.tidal_variant !== undefined) {
(req as any).tidal_variant = request.tidal_variant;
}
if (request.use_single_genre !== undefined) {
(req as any).use_single_genre = request.use_single_genre;
}
+8
View File
@@ -22,6 +22,7 @@ export interface Settings {
embedLyrics: boolean;
embedMaxQualityCover: boolean;
operatingSystem: "Windows" | "linux/MacOS";
tidalVariant: "tidal" | "alt";
tidalQuality: "LOSSLESS" | "HI_RES_LOSSLESS";
qobuzQuality: "6" | "7" | "27";
amazonQuality: "original";
@@ -110,6 +111,7 @@ export const DEFAULT_SETTINGS: Settings = {
embedLyrics: false,
embedMaxQualityCover: false,
operatingSystem: detectOS(),
tidalVariant: "tidal",
tidalQuality: "LOSSLESS",
qobuzQuality: "6",
amazonQuality: "original",
@@ -215,6 +217,9 @@ function getSettingsFromLocalStorage(): Settings {
if (!('tidalQuality' in parsed)) {
parsed.tidalQuality = "LOSSLESS";
}
if (!('tidalVariant' in parsed)) {
parsed.tidalVariant = "tidal";
}
if (!('qobuzQuality' in parsed)) {
parsed.qobuzQuality = "6";
}
@@ -306,6 +311,9 @@ export async function loadSettings(): Promise<Settings> {
if (!('tidalQuality' in parsed)) {
parsed.tidalQuality = "LOSSLESS";
}
if (!('tidalVariant' in parsed)) {
parsed.tidalVariant = "tidal";
}
if (!('qobuzQuality' in parsed)) {
parsed.qobuzQuality = "6";
}
+1
View File
@@ -120,6 +120,7 @@ export interface DownloadRequest {
release_date?: string;
cover_url?: string;
tidal_api_url?: string;
tidal_variant?: "tidal" | "alt";
output_dir?: string;
audio_format?: string;
folder_name?: string;