.redownlaod with suffix, isrc variable

This commit is contained in:
afkarxyz
2026-04-13 21:53:47 +07:00
parent 7792a69d33
commit db8f82aa17
15 changed files with 298 additions and 62 deletions
+4 -2
View File
@@ -36,6 +36,7 @@ interface FileMetadata {
track_number: number;
disc_number: number;
year: string;
isrc?: string;
}
type TabType = "track" | "lyric" | "cover";
const FORMAT_PRESETS: Record<string, {
@@ -549,7 +550,7 @@ export function FileManagerPage() {
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help"/>
</TooltipTrigger>
<TooltipContent side="right">
<p className="text-xs whitespace-nowrap">Variables: {"{title}"}, {"{artist}"}, {"{album}"}, {"{album_artist}"}, {"{track}"}, {"{disc}"}, {"{year}"}, {"{date}"}</p>
<p className="text-xs whitespace-nowrap">Variables: {"{title}"}, {"{artist}"}, {"{album}"}, {"{album_artist}"}, {"{track}"}, {"{disc}"}, {"{year}"}, {"{date}"}, {"{isrc}"}</p>
</TooltipContent>
</Tooltip>
</div>
@@ -571,7 +572,7 @@ export function FileManagerPage() {
</Tooltip>
</div>
<p className="text-xs text-muted-foreground">
Preview: <span className="font-mono">{renameFormat.replace(/\{title\}/g, "All The Stars").replace(/\{artist\}/g, "Kendrick Lamar, SZA").replace(/\{album\}/g, "Black Panther").replace(/\{album_artist\}/g, "Kendrick Lamar").replace(/\{track\}/g, "01").replace(/\{disc\}/g, "1").replace(/\{year\}/g, "2018").replace(/\{date\}/g, "2018-02-09")}.flac</span>
Preview: <span className="font-mono">{renameFormat.replace(/\{title\}/g, "All The Stars").replace(/\{artist\}/g, "Kendrick Lamar, SZA").replace(/\{album\}/g, "Black Panther").replace(/\{album_artist\}/g, "Kendrick Lamar").replace(/\{track\}/g, "01").replace(/\{disc\}/g, "1").replace(/\{year\}/g, "2018").replace(/\{date\}/g, "2018-02-09").replace(/\{isrc\}/g, "USUM71801234")}.flac</span>
</p>
</div>)}
@@ -660,6 +661,7 @@ export function FileManagerPage() {
<div className="grid grid-cols-[100px_1fr] gap-2 text-sm"><span className="text-muted-foreground">Track</span><span>{metadataInfo.track_number || "-"}</span></div>
<div className="grid grid-cols-[100px_1fr] gap-2 text-sm"><span className="text-muted-foreground">Disc</span><span>{metadataInfo.disc_number || "-"}</span></div>
<div className="grid grid-cols-[100px_1fr] gap-2 text-sm"><span className="text-muted-foreground">Year</span><span>{metadataInfo.year ? metadataInfo.year.substring(0, 4) : "-"}</span></div>
<div className="grid grid-cols-[100px_1fr] gap-2 text-sm"><span className="text-muted-foreground">ISRC</span><span>{metadataInfo.isrc || "-"}</span></div>
</div>) : (<div className="text-center py-4 text-muted-foreground">No metadata available</div>)}
<DialogFooter><Button onClick={() => setShowMetadata(false)}>Close</Button></DialogFooter>
</DialogContent>
+14 -2
View File
@@ -568,7 +568,8 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
.replace(/\{track\}/g, "01")
.replace(/\{disc\}/g, "1")
.replace(/\{year\}/g, "2018")
.replace(/\{date\}/g, "2018-02-09")}
.replace(/\{date\}/g, "2018-02-09")
.replace(/\{isrc\}/g, "USUM71801234")}
/
</span>
</p>)}
@@ -614,6 +615,16 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
</Label>
</div>
<div className="flex items-center gap-3">
<Switch id="redownload-with-suffix" checked={tempSettings.redownloadWithSuffix} onCheckedChange={(checked) => setTempSettings((prev) => ({
...prev,
redownloadWithSuffix: checked,
}))}/>
<Label htmlFor="redownload-with-suffix" className="text-sm cursor-pointer font-normal">
Redownload With Suffix
</Label>
</div>
</div>
@@ -686,7 +697,8 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
.replace(/\{track\}/g, "01")
.replace(/\{disc\}/g, "1")
.replace(/\{year\}/g, "2018")
.replace(/\{date\}/g, "2018-02-09")}
.replace(/\{date\}/g, "2018-02-09")
.replace(/\{isrc\}/g, "USUM71801234")}
.flac
</span>
</p>)}
+32
View File
@@ -12,6 +12,7 @@ interface CheckFileExistenceRequest {
album_name?: string;
album_artist?: string;
release_date?: string;
isrc?: string;
track_number?: number;
disc_number?: number;
position?: number;
@@ -31,6 +32,24 @@ interface FileExistenceResult {
const CheckFilesExistence = (outputDir: string, rootDir: string, tracks: CheckFileExistenceRequest[]): Promise<FileExistenceResult[]> => (window as any)["go"]["main"]["App"]["CheckFilesExistence"](outputDir, rootDir, tracks);
const SkipDownloadItem = (itemID: string, filePath: string): Promise<void> => (window as any)["go"]["main"]["App"]["SkipDownloadItem"](itemID, filePath);
const CreateM3U8File = (playlistName: string, outputDir: string, filePaths: string[]): Promise<void> => (window as any)["go"]["main"]["App"]["CreateM3U8File"](playlistName, outputDir, filePaths);
const GetTrackISRC = (spotifyId: string): Promise<string> => (window as any)["go"]["main"]["App"]["GetTrackISRC"](spotifyId);
async function resolveTemplateISRC(settings: { folderTemplate?: string; filenameTemplate?: string }, spotifyId?: string): Promise<string> {
if (!spotifyId) {
return "";
}
const folderTemplate = settings.folderTemplate || "";
const filenameTemplate = settings.filenameTemplate || "";
if (!folderTemplate.includes("{isrc}") && !filenameTemplate.includes("{isrc}")) {
return "";
}
try {
return await GetTrackISRC(spotifyId);
}
catch {
return "";
}
}
export function useDownload(region: string) {
const [downloadProgress, setDownloadProgress] = useState<number>(0);
const [isDownloading, setIsDownloading] = useState(false);
@@ -81,11 +100,13 @@ export function useDownload(region: string) {
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist
? getFirstArtist(albumArtist)
: albumArtist;
const resolvedTemplateISRC = await resolveTemplateISRC(settings, spotifyId || id);
const templateData: TemplateData = {
artist: displayArtist?.replace(/\//g, placeholder),
album: albumName?.replace(/\//g, placeholder),
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
title: trackName?.replace(/\//g, placeholder),
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
track: trackNumberForTemplate,
year: yearValue,
date: releaseDate,
@@ -117,6 +138,7 @@ export function useDownload(region: string) {
album_name: albumName,
album_artist: displayAlbumArtist,
release_date: finalReleaseDate || releaseDate,
isrc: resolvedTemplateISRC || undefined,
track_number: finalTrackNumber || spotifyTrackNumber || 0,
disc_number: spotifyDiscNumber || 0,
position: trackNumberForTemplate,
@@ -193,6 +215,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
@@ -240,6 +263,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_single_genre: settings.useSingleGenre,
@@ -286,6 +310,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_single_genre: settings.useSingleGenre,
@@ -350,6 +375,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_single_genre: settings.useSingleGenre,
@@ -395,11 +421,13 @@ export function useDownload(region: string) {
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist
? getFirstArtist(albumArtist)
: albumArtist;
const resolvedTemplateISRC = await resolveTemplateISRC(settings, spotifyId);
const templateData: TemplateData = {
artist: displayArtist?.replace(/\//g, placeholder),
album: albumName?.replace(/\//g, placeholder),
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
title: trackName?.replace(/\//g, placeholder),
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
track: trackNumberForTemplate,
year: yearValue,
date: releaseDate,
@@ -468,6 +496,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
@@ -515,6 +544,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
@@ -563,6 +593,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
@@ -624,6 +655,7 @@ export function useDownload(region: string) {
spotify_disc_number: spotifyDiscNumber,
spotify_total_tracks: spotifyTotalTracks,
spotify_total_discs: spotifyTotalDiscs,
isrc: resolvedTemplateISRC || undefined,
copyright: copyright,
publisher: publisher,
use_first_artist_only: settings.useFirstArtistOnly,
+24
View File
@@ -5,6 +5,24 @@ import { toastWithSound as toast } from "@/lib/toast-with-sound";
import { joinPath, sanitizePath, getFirstArtist } from "@/lib/utils";
import { logger } from "@/lib/logger";
import type { TrackMetadata } from "@/types/api";
const GetTrackISRC = (spotifyId: string): Promise<string> => (window as any)["go"]["main"]["App"]["GetTrackISRC"](spotifyId);
async function resolveTemplateISRC(settings: { folderTemplate?: string; filenameTemplate?: string }, spotifyId?: string): Promise<string> {
if (!spotifyId) {
return "";
}
const folderTemplate = settings.folderTemplate || "";
const filenameTemplate = settings.filenameTemplate || "";
if (!folderTemplate.includes("{isrc}") && !filenameTemplate.includes("{isrc}")) {
return "";
}
try {
return await GetTrackISRC(spotifyId);
}
catch {
return "";
}
}
export function useLyrics() {
const [downloadingLyricsTrack, setDownloadingLyricsTrack] = useState<string | null>(null);
const [downloadedLyrics, setDownloadedLyrics] = useState<Set<string>>(new Set());
@@ -28,11 +46,13 @@ export function useLyrics() {
const yearValue = releaseDate?.substring(0, 4);
const displayArtist = settings.useFirstArtistOnly && artistName ? getFirstArtist(artistName) : artistName;
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist ? getFirstArtist(albumArtist) : albumArtist;
const resolvedTemplateISRC = await resolveTemplateISRC(settings, spotifyId);
const templateData: TemplateData = {
artist: displayArtist?.replace(/\//g, placeholder),
album: albumName?.replace(/\//g, placeholder),
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
title: trackName?.replace(/\//g, placeholder),
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
track: position,
year: yearValue,
date: releaseDate,
@@ -61,6 +81,7 @@ export function useLyrics() {
album_name: albumName,
album_artist: displayAlbumArtist,
release_date: releaseDate,
isrc: resolvedTemplateISRC || undefined,
output_dir: outputDir,
filename_format: settings.filenameTemplate || "{title}",
track_number: settings.trackNumber,
@@ -129,11 +150,13 @@ export function useLyrics() {
const yearValue = track.release_date?.substring(0, 4);
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
const displayAlbumArtist = settings.useFirstArtistOnly && track.album_artist ? getFirstArtist(track.album_artist) : track.album_artist;
const resolvedTemplateISRC = await resolveTemplateISRC(settings, id);
const templateData: TemplateData = {
artist: displayArtist?.replace(/\//g, placeholder),
album: track.album_name?.replace(/\//g, placeholder),
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
title: track.name?.replace(/\//g, placeholder),
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
track: trackPosition,
year: yearValue,
date: track.release_date,
@@ -161,6 +184,7 @@ export function useLyrics() {
album_name: track.album_name,
album_artist: displayAlbumArtist,
release_date: track.release_date,
isrc: resolvedTemplateISRC || undefined,
output_dir: outputDir,
filename_format: settings.filenameTemplate || "{title}",
track_number: settings.trackNumber,
+11
View File
@@ -36,6 +36,7 @@ export interface Settings {
useFirstArtistOnly: boolean;
useSingleGenre: boolean;
embedGenre: boolean;
redownloadWithSuffix: boolean;
separator: "comma" | "semicolon";
}
export const FOLDER_PRESETS: Record<FolderPreset, {
@@ -85,6 +86,7 @@ export const TEMPLATE_VARIABLES = [
{ key: "{disc}", description: "Disc number", example: "1" },
{ key: "{year}", description: "Release year", example: "2014" },
{ key: "{date}", description: "Release date (YYYY-MM-DD)", example: "2014-10-27" },
{ key: "{isrc}", description: "Track ISRC", example: "USUM71412345" },
];
function detectOS(): "Windows" | "linux/MacOS" {
const platform = window.navigator.platform.toLowerCase();
@@ -124,6 +126,7 @@ export const DEFAULT_SETTINGS: Settings = {
useFirstArtistOnly: false,
useSingleGenre: false,
embedGenre: true,
redownloadWithSuffix: false,
separator: "semicolon"
};
export const FONT_OPTIONS: {
@@ -243,6 +246,9 @@ function getSettingsFromLocalStorage(): Settings {
if (!('separator' in parsed)) {
parsed.separator = "semicolon";
}
if (!('redownloadWithSuffix' in parsed)) {
parsed.redownloadWithSuffix = false;
}
return { ...DEFAULT_SETTINGS, ...parsed };
}
}
@@ -346,6 +352,9 @@ export async function loadSettings(): Promise<Settings> {
if (!('separator' in parsed)) {
parsed.separator = "semicolon";
}
if (!('redownloadWithSuffix' in parsed)) {
parsed.redownloadWithSuffix = false;
}
cachedSettings = { ...DEFAULT_SETTINGS, ...parsed };
return cachedSettings!;
}
@@ -368,6 +377,7 @@ export interface TemplateData {
album?: string;
album_artist?: string;
title?: string;
isrc?: string;
track?: number;
disc?: number;
year?: string;
@@ -382,6 +392,7 @@ export function parseTemplate(template: string, data: TemplateData): string {
result = result.replace(/\{artist\}/g, data.artist || "Unknown Artist");
result = result.replace(/\{album\}/g, data.album || "Unknown Album");
result = result.replace(/\{album_artist\}/g, data.album_artist || data.artist || "Unknown Artist");
result = result.replace(/\{isrc\}/g, data.isrc || "");
result = result.replace(/\{track\}/g, data.track ? String(data.track).padStart(2, "0") : "00");
result = result.replace(/\{disc\}/g, data.disc ? String(data.disc) : "1");
result = result.replace(/\{year\}/g, data.year || "0000");
+4
View File
@@ -23,6 +23,7 @@ export interface TrackMetadata {
artist_id?: string;
artist_url?: string;
artists_data?: ArtistSimple[];
isrc?: string;
copyright?: string;
publisher?: string;
plays?: string;
@@ -134,6 +135,7 @@ export interface DownloadRequest {
spotify_disc_number?: number;
spotify_total_tracks?: number;
spotify_total_discs?: number;
isrc?: string;
copyright?: string;
publisher?: string;
spotify_url?: string;
@@ -190,6 +192,7 @@ export interface LyricsDownloadRequest {
album_name?: string;
album_artist?: string;
release_date?: string;
isrc?: string;
output_dir?: string;
filename_format?: string;
track_number?: boolean;
@@ -278,4 +281,5 @@ export interface AudioMetadata {
track_number: number;
disc_number: number;
year: string;
isrc?: string;
}