.redownlaod with suffix, isrc variable
This commit is contained in:
@@ -159,6 +159,7 @@ type DownloadRequest struct {
|
|||||||
SpotifyDiscNumber int `json:"spotify_disc_number,omitempty"`
|
SpotifyDiscNumber int `json:"spotify_disc_number,omitempty"`
|
||||||
SpotifyTotalTracks int `json:"spotify_total_tracks,omitempty"`
|
SpotifyTotalTracks int `json:"spotify_total_tracks,omitempty"`
|
||||||
SpotifyTotalDiscs int `json:"spotify_total_discs,omitempty"`
|
SpotifyTotalDiscs int `json:"spotify_total_discs,omitempty"`
|
||||||
|
ISRC string `json:"isrc,omitempty"`
|
||||||
Copyright string `json:"copyright,omitempty"`
|
Copyright string `json:"copyright,omitempty"`
|
||||||
Publisher string `json:"publisher,omitempty"`
|
Publisher string `json:"publisher,omitempty"`
|
||||||
Composer string `json:"composer,omitempty"`
|
Composer string `json:"composer,omitempty"`
|
||||||
@@ -363,6 +364,9 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) {
|
|||||||
if req.FilenameFormat == "" {
|
if req.FilenameFormat == "" {
|
||||||
req.FilenameFormat = "title-artist"
|
req.FilenameFormat = "title-artist"
|
||||||
}
|
}
|
||||||
|
if req.ISRC == "" && strings.Contains(req.FilenameFormat, "{isrc}") && req.SpotifyID != "" {
|
||||||
|
req.ISRC = backend.ResolveTrackISRC(req.SpotifyID)
|
||||||
|
}
|
||||||
|
|
||||||
itemID := req.ItemID
|
itemID := req.ItemID
|
||||||
if itemID == "" {
|
if itemID == "" {
|
||||||
@@ -449,19 +453,21 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.TrackName != "" && req.ArtistName != "" {
|
if req.TrackName != "" && req.ArtistName != "" {
|
||||||
expectedFilename := backend.BuildExpectedFilename(req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.FilenameFormat, req.PlaylistName, req.PlaylistOwner, req.TrackNumber, req.Position, req.SpotifyDiscNumber, req.UseAlbumTrackNumber)
|
expectedFilename := backend.BuildExpectedFilename(req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.FilenameFormat, req.PlaylistName, req.PlaylistOwner, req.TrackNumber, req.Position, req.SpotifyDiscNumber, req.UseAlbumTrackNumber, req.ISRC)
|
||||||
expectedPath := filepath.Join(req.OutputDir, expectedFilename)
|
expectedPath := filepath.Join(req.OutputDir, expectedFilename)
|
||||||
|
|
||||||
if fileInfo, err := os.Stat(expectedPath); err == nil && fileInfo.Size() > 100*1024 {
|
if !backend.GetRedownloadWithSuffixSetting() {
|
||||||
|
if fileInfo, err := os.Stat(expectedPath); err == nil && fileInfo.Size() > 100*1024 {
|
||||||
|
|
||||||
backend.SkipDownloadItem(itemID, expectedPath)
|
backend.SkipDownloadItem(itemID, expectedPath)
|
||||||
return DownloadResponse{
|
return DownloadResponse{
|
||||||
Success: true,
|
Success: true,
|
||||||
Message: "File already exists",
|
Message: "File already exists",
|
||||||
File: expectedPath,
|
File: expectedPath,
|
||||||
AlreadyExists: true,
|
AlreadyExists: true,
|
||||||
ItemID: itemID,
|
ItemID: itemID,
|
||||||
}, nil
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,32 +512,35 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) {
|
|||||||
|
|
||||||
downloader := backend.NewAmazonDownloader()
|
downloader := backend.NewAmazonDownloader()
|
||||||
if req.ServiceURL != "" {
|
if req.ServiceURL != "" {
|
||||||
filename, err = downloader.DownloadByURL(req.ServiceURL, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.PlaylistName, req.PlaylistOwner, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.CoverURL, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.EmbedMaxQualityCover, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, spotifyURL, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
filename, err = downloader.DownloadByURL(req.ServiceURL, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.PlaylistName, req.PlaylistOwner, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.CoverURL, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.EmbedMaxQualityCover, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, req.ISRC, spotifyURL, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
||||||
} else {
|
} else {
|
||||||
filename, err = downloader.DownloadBySpotifyID(req.SpotifyID, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.PlaylistName, req.PlaylistOwner, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.CoverURL, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.EmbedMaxQualityCover, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, spotifyURL, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
filename, err = downloader.DownloadBySpotifyID(req.SpotifyID, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.PlaylistName, req.PlaylistOwner, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.CoverURL, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.EmbedMaxQualityCover, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, req.ISRC, spotifyURL, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "tidal":
|
case "tidal":
|
||||||
if req.ApiURL == "" || req.ApiURL == "auto" {
|
if req.ApiURL == "" || req.ApiURL == "auto" {
|
||||||
downloader := backend.NewTidalDownloader("")
|
downloader := backend.NewTidalDownloader("")
|
||||||
if req.ServiceURL != "" {
|
if req.ServiceURL != "" {
|
||||||
filename, err = downloader.DownloadByURLWithFallback(req.ServiceURL, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
filename, err = downloader.DownloadByURLWithFallback(req.ServiceURL, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, req.ISRC, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
||||||
} else {
|
} else {
|
||||||
filename, err = downloader.Download(req.SpotifyID, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
filename, err = downloader.Download(req.SpotifyID, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, req.ISRC, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
downloader := backend.NewTidalDownloader(req.ApiURL)
|
downloader := backend.NewTidalDownloader(req.ApiURL)
|
||||||
if req.ServiceURL != "" {
|
if req.ServiceURL != "" {
|
||||||
filename, err = downloader.DownloadByURL(req.ServiceURL, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
filename, err = downloader.DownloadByURL(req.ServiceURL, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, req.ISRC, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
||||||
} else {
|
} else {
|
||||||
filename, err = downloader.Download(req.SpotifyID, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
filename, err = downloader.Download(req.SpotifyID, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, req.UseAlbumTrackNumber, req.CoverURL, req.EmbedMaxQualityCover, req.SpotifyTrackNumber, req.SpotifyDiscNumber, req.SpotifyTotalTracks, req.SpotifyTotalDiscs, req.Copyright, req.Publisher, req.Composer, metadataSeparator, req.ISRC, spotifyURL, req.AllowFallback, req.UseFirstArtistOnly, req.UseSingleGenre, req.EmbedGenre)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "qobuz":
|
case "qobuz":
|
||||||
|
|
||||||
fmt.Println("Waiting for ISRC (Qobuz dependency)...")
|
isrc := strings.TrimSpace(req.ISRC)
|
||||||
isrc := <-isrcChan
|
if isrc == "" {
|
||||||
|
fmt.Println("Waiting for ISRC (Qobuz dependency)...")
|
||||||
|
isrc = <-isrcChan
|
||||||
|
}
|
||||||
downloader := backend.NewQobuzDownloader()
|
downloader := backend.NewQobuzDownloader()
|
||||||
quality := req.AudioFormat
|
quality := req.AudioFormat
|
||||||
if quality == "" {
|
if quality == "" {
|
||||||
@@ -957,6 +966,7 @@ type LyricsDownloadRequest struct {
|
|||||||
AlbumName string `json:"album_name"`
|
AlbumName string `json:"album_name"`
|
||||||
AlbumArtist string `json:"album_artist"`
|
AlbumArtist string `json:"album_artist"`
|
||||||
ReleaseDate string `json:"release_date"`
|
ReleaseDate string `json:"release_date"`
|
||||||
|
ISRC string `json:"isrc,omitempty"`
|
||||||
OutputDir string `json:"output_dir"`
|
OutputDir string `json:"output_dir"`
|
||||||
FilenameFormat string `json:"filename_format"`
|
FilenameFormat string `json:"filename_format"`
|
||||||
TrackNumber bool `json:"track_number"`
|
TrackNumber bool `json:"track_number"`
|
||||||
@@ -981,6 +991,7 @@ func (a *App) DownloadLyrics(req LyricsDownloadRequest) (backend.LyricsDownloadR
|
|||||||
AlbumName: req.AlbumName,
|
AlbumName: req.AlbumName,
|
||||||
AlbumArtist: req.AlbumArtist,
|
AlbumArtist: req.AlbumArtist,
|
||||||
ReleaseDate: req.ReleaseDate,
|
ReleaseDate: req.ReleaseDate,
|
||||||
|
ISRC: req.ISRC,
|
||||||
OutputDir: req.OutputDir,
|
OutputDir: req.OutputDir,
|
||||||
FilenameFormat: req.FilenameFormat,
|
FilenameFormat: req.FilenameFormat,
|
||||||
TrackNumber: req.TrackNumber,
|
TrackNumber: req.TrackNumber,
|
||||||
@@ -1404,6 +1415,7 @@ type CheckFileExistenceRequest struct {
|
|||||||
AlbumName string `json:"album_name,omitempty"`
|
AlbumName string `json:"album_name,omitempty"`
|
||||||
AlbumArtist string `json:"album_artist,omitempty"`
|
AlbumArtist string `json:"album_artist,omitempty"`
|
||||||
ReleaseDate string `json:"release_date,omitempty"`
|
ReleaseDate string `json:"release_date,omitempty"`
|
||||||
|
ISRC string `json:"isrc,omitempty"`
|
||||||
TrackNumber int `json:"track_number,omitempty"`
|
TrackNumber int `json:"track_number,omitempty"`
|
||||||
DiscNumber int `json:"disc_number,omitempty"`
|
DiscNumber int `json:"disc_number,omitempty"`
|
||||||
Position int `json:"position,omitempty"`
|
Position int `json:"position,omitempty"`
|
||||||
@@ -1433,6 +1445,7 @@ func (a *App) CheckFilesExistence(outputDir string, rootDir string, tracks []Che
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultFilenameFormat := "title-artist"
|
defaultFilenameFormat := "title-artist"
|
||||||
|
redownloadWithSuffix := backend.GetRedownloadWithSuffixSetting()
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
index int
|
index int
|
||||||
@@ -1483,6 +1496,10 @@ func (a *App) CheckFilesExistence(outputDir string, rootDir string, tracks []Che
|
|||||||
if filenameFormat == "" {
|
if filenameFormat == "" {
|
||||||
filenameFormat = defaultFilenameFormat
|
filenameFormat = defaultFilenameFormat
|
||||||
}
|
}
|
||||||
|
isrc := strings.TrimSpace(t.ISRC)
|
||||||
|
if isrc == "" && strings.Contains(filenameFormat, "{isrc}") && t.SpotifyID != "" {
|
||||||
|
isrc = backend.ResolveTrackISRC(t.SpotifyID)
|
||||||
|
}
|
||||||
|
|
||||||
trackNumber := t.Position
|
trackNumber := t.Position
|
||||||
if t.UseAlbumTrackNumber && t.TrackNumber > 0 {
|
if t.UseAlbumTrackNumber && t.TrackNumber > 0 {
|
||||||
@@ -1507,6 +1524,7 @@ func (a *App) CheckFilesExistence(outputDir string, rootDir string, tracks []Che
|
|||||||
trackNumber,
|
trackNumber,
|
||||||
t.DiscNumber,
|
t.DiscNumber,
|
||||||
t.UseAlbumTrackNumber,
|
t.UseAlbumTrackNumber,
|
||||||
|
isrc,
|
||||||
)
|
)
|
||||||
|
|
||||||
expectedFilename := strings.TrimSuffix(expectedFilenameBase, ".flac") + fileExt
|
expectedFilename := strings.TrimSuffix(expectedFilenameBase, ".flac") + fileExt
|
||||||
@@ -1517,13 +1535,17 @@ func (a *App) CheckFilesExistence(outputDir string, rootDir string, tracks []Che
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectedPath := filepath.Join(targetDir, expectedFilename)
|
expectedPath := filepath.Join(targetDir, expectedFilename)
|
||||||
|
if redownloadWithSuffix {
|
||||||
if fileInfo, err := os.Stat(expectedPath); err == nil && fileInfo.Size() > 100*1024 {
|
expectedPath, _ = backend.ResolveOutputPathForDownload(expectedPath, true)
|
||||||
res.Exists = true
|
res.FilePath = filepath.Base(expectedPath)
|
||||||
res.FilePath = expectedPath
|
|
||||||
} else {
|
} else {
|
||||||
|
if fileInfo, err := os.Stat(expectedPath); err == nil && fileInfo.Size() > 100*1024 {
|
||||||
|
res.Exists = true
|
||||||
|
res.FilePath = expectedPath
|
||||||
|
} else {
|
||||||
|
|
||||||
res.FilePath = expectedFilename
|
res.FilePath = expectedFilename
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultsChan <- result{index: idx, result: res}
|
resultsChan <- result{index: idx, result: res}
|
||||||
@@ -1573,6 +1595,10 @@ func (a *App) SkipDownloadItem(itemID, filePath string) {
|
|||||||
backend.SkipDownloadItem(itemID, filePath)
|
backend.SkipDownloadItem(itemID, filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTrackISRC(spotifyTrackID string) string {
|
||||||
|
return backend.ResolveTrackISRC(spotifyTrackID)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) GetPreviewURL(trackID string) (string, error) {
|
func (a *App) GetPreviewURL(trackID string) (string, error) {
|
||||||
return backend.GetPreviewURL(trackID)
|
return backend.GetPreviewURL(trackID)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-9
@@ -204,7 +204,7 @@ func (a *AmazonDownloader) DownloadFromService(amazonURL, outputDir, quality str
|
|||||||
return a.DownloadFromAfkarXYZ(amazonURL, outputDir, quality)
|
return a.DownloadFromAfkarXYZ(amazonURL, outputDir, quality)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, spotifyURL string, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, isrcOverride, spotifyURL string, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
||||||
|
|
||||||
if outputDir != "." {
|
if outputDir != "." {
|
||||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||||
@@ -219,12 +219,14 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
|||||||
filenameArtist = GetFirstArtist(spotifyArtistName)
|
filenameArtist = GetFirstArtist(spotifyArtistName)
|
||||||
filenameAlbumArtist = GetFirstArtist(spotifyAlbumArtist)
|
filenameAlbumArtist = GetFirstArtist(spotifyAlbumArtist)
|
||||||
}
|
}
|
||||||
expectedFilename := BuildExpectedFilename(spotifyTrackName, filenameArtist, spotifyAlbumName, filenameAlbumArtist, spotifyReleaseDate, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyDiscNumber, false)
|
expectedFilename := BuildExpectedFilename(spotifyTrackName, filenameArtist, spotifyAlbumName, filenameAlbumArtist, spotifyReleaseDate, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyDiscNumber, false, isrcOverride)
|
||||||
expectedPath := filepath.Join(outputDir, expectedFilename)
|
expectedPath := filepath.Join(outputDir, expectedFilename)
|
||||||
|
|
||||||
if fileInfo, err := os.Stat(expectedPath); err == nil && fileInfo.Size() > 0 {
|
if !GetRedownloadWithSuffixSetting() {
|
||||||
fmt.Printf("File already exists: %s (%.2f MB)\n", expectedPath, float64(fileInfo.Size())/(1024*1024))
|
if fileInfo, err := os.Stat(expectedPath); err == nil && fileInfo.Size() > 0 {
|
||||||
return "EXISTS:" + expectedPath, nil
|
fmt.Printf("File already exists: %s (%.2f MB)\n", expectedPath, float64(fileInfo.Size())/(1024*1024))
|
||||||
|
return "EXISTS:" + expectedPath, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,11 +273,13 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var isrc string
|
isrc := strings.TrimSpace(isrcOverride)
|
||||||
var mbMeta Metadata
|
var mbMeta Metadata
|
||||||
if spotifyURL != "" {
|
if spotifyURL != "" {
|
||||||
result := <-metaChan
|
result := <-metaChan
|
||||||
isrc = result.ISRC
|
if isrc == "" {
|
||||||
|
isrc = result.ISRC
|
||||||
|
}
|
||||||
mbMeta = result.Metadata
|
mbMeta = result.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +313,7 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
|||||||
newFilename = strings.ReplaceAll(newFilename, "{album_artist}", safeAlbumArtist)
|
newFilename = strings.ReplaceAll(newFilename, "{album_artist}", safeAlbumArtist)
|
||||||
newFilename = strings.ReplaceAll(newFilename, "{year}", year)
|
newFilename = strings.ReplaceAll(newFilename, "{year}", year)
|
||||||
newFilename = strings.ReplaceAll(newFilename, "{date}", SanitizeFilename(spotifyReleaseDate))
|
newFilename = strings.ReplaceAll(newFilename, "{date}", SanitizeFilename(spotifyReleaseDate))
|
||||||
|
newFilename = strings.ReplaceAll(newFilename, "{isrc}", SanitizeOptionalFilename(isrc))
|
||||||
|
|
||||||
if spotifyDiscNumber > 0 {
|
if spotifyDiscNumber > 0 {
|
||||||
newFilename = strings.ReplaceAll(newFilename, "{disc}", fmt.Sprintf("%d", spotifyDiscNumber))
|
newFilename = strings.ReplaceAll(newFilename, "{disc}", fmt.Sprintf("%d", spotifyDiscNumber))
|
||||||
@@ -346,6 +351,9 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
|||||||
}
|
}
|
||||||
newFilename = newFilename + ext
|
newFilename = newFilename + ext
|
||||||
newFilePath := filepath.Join(outputDir, newFilename)
|
newFilePath := filepath.Join(outputDir, newFilename)
|
||||||
|
if GetRedownloadWithSuffixSetting() {
|
||||||
|
newFilePath, _ = ResolveOutputPathForDownload(newFilePath, true)
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.Rename(filePath, newFilePath); err != nil {
|
if err := os.Rename(filePath, newFilePath); err != nil {
|
||||||
fmt.Printf("Warning: Failed to rename file: %v\n", err)
|
fmt.Printf("Warning: Failed to rename file: %v\n", err)
|
||||||
@@ -420,7 +428,7 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
|||||||
return filePath, nil
|
return filePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmazonDownloader) DownloadBySpotifyID(spotifyTrackID, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, spotifyURL string,
|
func (a *AmazonDownloader) DownloadBySpotifyID(spotifyTrackID, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, isrcOverride, spotifyURL string,
|
||||||
useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool,
|
useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
@@ -429,5 +437,5 @@ func (a *AmazonDownloader) DownloadBySpotifyID(spotifyTrackID, outputDir, qualit
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, embedMaxQualityCover, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, spotifyURL, useFirstArtistOnly, useSingleGenre, embedGenre)
|
return a.DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, embedMaxQualityCover, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, isrcOverride, spotifyURL, useFirstArtistOnly, useSingleGenre, embedGenre)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,16 @@ func GetSpotFetchAPISettings() (bool, string) {
|
|||||||
return true, apiURL
|
return true, apiURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetRedownloadWithSuffixSetting() bool {
|
||||||
|
settings, err := LoadConfigSettings()
|
||||||
|
if err != nil || settings == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled, _ := settings["redownloadWithSuffix"].(bool)
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
|
||||||
func GetLinkResolverSetting() string {
|
func GetLinkResolverSetting() string {
|
||||||
settings, err := LoadConfigSettings()
|
settings, err := LoadConfigSettings()
|
||||||
if err != nil || settings == nil {
|
if err != nil || settings == nil {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type AudioMetadata struct {
|
|||||||
TrackNumber int `json:"track_number"`
|
TrackNumber int `json:"track_number"`
|
||||||
DiscNumber int `json:"disc_number"`
|
DiscNumber int `json:"disc_number"`
|
||||||
Year string `json:"year"`
|
Year string `json:"year"`
|
||||||
|
ISRC string `json:"isrc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RenamePreview struct {
|
type RenamePreview struct {
|
||||||
@@ -175,6 +176,8 @@ func readFlacMetadata(filePath string) (*AudioMetadata, error) {
|
|||||||
}
|
}
|
||||||
case "DATE", "YEAR":
|
case "DATE", "YEAR":
|
||||||
metadata.Year = value
|
metadata.Year = value
|
||||||
|
case "ISRC", "TSRC":
|
||||||
|
metadata.ISRC = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,6 +224,12 @@ func readMp3Metadata(filePath string) (*AudioMetadata, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if frames := tag.GetFrames("TSRC"); len(frames) > 0 {
|
||||||
|
if textFrame, ok := frames[0].(id3v2.TextFrame); ok {
|
||||||
|
metadata.ISRC = textFrame.Text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return metadata, nil
|
return metadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +310,8 @@ func readMetadataWithFFprobe(filePath string) (*AudioMetadata, error) {
|
|||||||
if metadata.Year == "" || len(value) > len(metadata.Year) {
|
if metadata.Year == "" || len(value) > len(metadata.Year) {
|
||||||
metadata.Year = value
|
metadata.Year = value
|
||||||
}
|
}
|
||||||
|
case "isrc", "tsrc":
|
||||||
|
metadata.ISRC = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +344,7 @@ func GenerateFilename(metadata *AudioMetadata, format string, ext string) string
|
|||||||
result = strings.ReplaceAll(result, "{album_artist}", sanitizeFilenameForRename(metadata.AlbumArtist))
|
result = strings.ReplaceAll(result, "{album_artist}", sanitizeFilenameForRename(metadata.AlbumArtist))
|
||||||
result = strings.ReplaceAll(result, "{year}", sanitizeFilenameForRename(year))
|
result = strings.ReplaceAll(result, "{year}", sanitizeFilenameForRename(year))
|
||||||
result = strings.ReplaceAll(result, "{date}", sanitizeFilenameForRename(metadata.Year))
|
result = strings.ReplaceAll(result, "{date}", sanitizeFilenameForRename(metadata.Year))
|
||||||
|
result = strings.ReplaceAll(result, "{isrc}", sanitizeFilenameForRename(metadata.ISRC))
|
||||||
|
|
||||||
if metadata.TrackNumber > 0 {
|
if metadata.TrackNumber > 0 {
|
||||||
result = strings.ReplaceAll(result, "{track}", fmt.Sprintf("%02d", metadata.TrackNumber))
|
result = strings.ReplaceAll(result, "{track}", fmt.Sprintf("%02d", metadata.TrackNumber))
|
||||||
|
|||||||
+52
-3
@@ -2,6 +2,7 @@ package backend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -9,12 +10,12 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BuildExpectedFilename(trackName, artistName, albumName, albumArtist, releaseDate, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position, discNumber int, useAlbumTrackNumber bool) string {
|
func buildFormattedFilenameBase(trackName, artistName, albumName, albumArtist, releaseDate, filenameFormat, playlistName, playlistOwner, isrc string, includeTrackNumber bool, position, discNumber int, useAlbumTrackNumber bool) string {
|
||||||
|
|
||||||
safeTitle := SanitizeFilename(trackName)
|
safeTitle := SanitizeFilename(trackName)
|
||||||
safeArtist := SanitizeFilename(artistName)
|
safeArtist := SanitizeFilename(artistName)
|
||||||
safeAlbum := SanitizeFilename(albumName)
|
safeAlbum := SanitizeFilename(albumName)
|
||||||
safeAlbumArtist := SanitizeFilename(albumArtist)
|
safeAlbumArtist := SanitizeFilename(albumArtist)
|
||||||
|
safeISRC := SanitizeOptionalFilename(isrc)
|
||||||
|
|
||||||
safePlaylist := SanitizeFilename(playlistName)
|
safePlaylist := SanitizeFilename(playlistName)
|
||||||
safeCreator := SanitizeFilename(playlistOwner)
|
safeCreator := SanitizeFilename(playlistOwner)
|
||||||
@@ -36,6 +37,7 @@ func BuildExpectedFilename(trackName, artistName, albumName, albumArtist, releas
|
|||||||
filename = strings.ReplaceAll(filename, "{date}", SanitizeFilename(releaseDate))
|
filename = strings.ReplaceAll(filename, "{date}", SanitizeFilename(releaseDate))
|
||||||
filename = strings.ReplaceAll(filename, "{playlist}", safePlaylist)
|
filename = strings.ReplaceAll(filename, "{playlist}", safePlaylist)
|
||||||
filename = strings.ReplaceAll(filename, "{creator}", safeCreator)
|
filename = strings.ReplaceAll(filename, "{creator}", safeCreator)
|
||||||
|
filename = strings.ReplaceAll(filename, "{isrc}", safeISRC)
|
||||||
|
|
||||||
if discNumber > 0 {
|
if discNumber > 0 {
|
||||||
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
||||||
@@ -67,7 +69,47 @@ func BuildExpectedFilename(trackName, artistName, albumName, albumArtist, releas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filename + ".flac"
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildExpectedFilename(trackName, artistName, albumName, albumArtist, releaseDate, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position, discNumber int, useAlbumTrackNumber bool, extra ...string) string {
|
||||||
|
isrc := ""
|
||||||
|
if len(extra) > 0 {
|
||||||
|
isrc = extra[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildFormattedFilenameBase(trackName, artistName, albumName, albumArtist, releaseDate, filenameFormat, playlistName, playlistOwner, isrc, includeTrackNumber, position, discNumber, useAlbumTrackNumber) + ".flac"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveOutputPathForDownload(path string, redownloadWithSuffix bool) (string, bool) {
|
||||||
|
if !redownloadWithSuffix {
|
||||||
|
if info, err := os.Stat(path); err == nil && info.Size() > 0 {
|
||||||
|
return path, true
|
||||||
|
}
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if info, err := os.Stat(path); err != nil || info.Size() == 0 {
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := filepath.Ext(path)
|
||||||
|
base := strings.TrimSuffix(path, ext)
|
||||||
|
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
candidate := fmt.Sprintf("%s_%02d%s", base, i, ext)
|
||||||
|
if info, err := os.Stat(candidate); err != nil || info.Size() == 0 {
|
||||||
|
return candidate, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustFileSize(path string) int64 {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return info.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SanitizeFilename(name string) string {
|
func SanitizeFilename(name string) string {
|
||||||
@@ -188,3 +230,10 @@ func sanitizeFolderName(name string) string { return SanitizeFilename(name) }
|
|||||||
func sanitizeFilename(name string) string {
|
func sanitizeFilename(name string) string {
|
||||||
return SanitizeFilename(name)
|
return SanitizeFilename(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SanitizeOptionalFilename(name string) string {
|
||||||
|
if strings.TrimSpace(name) == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return SanitizeFilename(name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func ResolveTrackISRC(spotifyTrackID string) string {
|
||||||
|
spotifyTrackID = strings.TrimSpace(spotifyTrackID)
|
||||||
|
if spotifyTrackID == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if cachedISRC, err := GetCachedISRC(spotifyTrackID); err == nil && cachedISRC != "" {
|
||||||
|
return strings.ToUpper(strings.TrimSpace(cachedISRC))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := NewSongLinkClient()
|
||||||
|
isrc, err := client.GetISRCDirect(spotifyTrackID)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToUpper(strings.TrimSpace(isrc))
|
||||||
|
}
|
||||||
+11
-3
@@ -44,6 +44,7 @@ type LyricsDownloadRequest struct {
|
|||||||
AlbumName string `json:"album_name"`
|
AlbumName string `json:"album_name"`
|
||||||
AlbumArtist string `json:"album_artist"`
|
AlbumArtist string `json:"album_artist"`
|
||||||
ReleaseDate string `json:"release_date"`
|
ReleaseDate string `json:"release_date"`
|
||||||
|
ISRC string `json:"isrc"`
|
||||||
OutputDir string `json:"output_dir"`
|
OutputDir string `json:"output_dir"`
|
||||||
FilenameFormat string `json:"filename_format"`
|
FilenameFormat string `json:"filename_format"`
|
||||||
TrackNumber bool `json:"track_number"`
|
TrackNumber bool `json:"track_number"`
|
||||||
@@ -363,11 +364,12 @@ func msToLRCTimestamp(msStr string) string {
|
|||||||
return fmt.Sprintf("[%02d:%02d.%02d]", minutes, seconds, centiseconds)
|
return fmt.Sprintf("[%02d:%02d.%02d]", minutes, seconds, centiseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLyricsFilename(trackName, artistName, albumName, albumArtist, releaseDate, filenameFormat string, includeTrackNumber bool, position, discNumber int) string {
|
func buildLyricsFilename(trackName, artistName, albumName, albumArtist, releaseDate, filenameFormat, isrc string, includeTrackNumber bool, position, discNumber int) string {
|
||||||
safeTitle := sanitizeFilename(trackName)
|
safeTitle := sanitizeFilename(trackName)
|
||||||
safeArtist := sanitizeFilename(artistName)
|
safeArtist := sanitizeFilename(artistName)
|
||||||
safeAlbum := sanitizeFilename(albumName)
|
safeAlbum := sanitizeFilename(albumName)
|
||||||
safeAlbumArtist := sanitizeFilename(albumArtist)
|
safeAlbumArtist := sanitizeFilename(albumArtist)
|
||||||
|
safeISRC := SanitizeOptionalFilename(isrc)
|
||||||
|
|
||||||
year := ""
|
year := ""
|
||||||
if len(releaseDate) >= 4 {
|
if len(releaseDate) >= 4 {
|
||||||
@@ -384,6 +386,7 @@ func buildLyricsFilename(trackName, artistName, albumName, albumArtist, releaseD
|
|||||||
filename = strings.ReplaceAll(filename, "{album_artist}", safeAlbumArtist)
|
filename = strings.ReplaceAll(filename, "{album_artist}", safeAlbumArtist)
|
||||||
filename = strings.ReplaceAll(filename, "{year}", year)
|
filename = strings.ReplaceAll(filename, "{year}", year)
|
||||||
filename = strings.ReplaceAll(filename, "{date}", sanitizeFilename(releaseDate))
|
filename = strings.ReplaceAll(filename, "{date}", sanitizeFilename(releaseDate))
|
||||||
|
filename = strings.ReplaceAll(filename, "{isrc}", safeISRC)
|
||||||
|
|
||||||
if discNumber > 0 {
|
if discNumber > 0 {
|
||||||
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
||||||
@@ -485,10 +488,15 @@ func (c *LyricsClient) DownloadLyrics(req LyricsDownloadRequest) (*LyricsDownloa
|
|||||||
if filenameFormat == "" {
|
if filenameFormat == "" {
|
||||||
filenameFormat = "title-artist"
|
filenameFormat = "title-artist"
|
||||||
}
|
}
|
||||||
filename := buildLyricsFilename(req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, filenameFormat, req.TrackNumber, req.Position, req.DiscNumber)
|
resolvedISRC := strings.TrimSpace(req.ISRC)
|
||||||
|
if resolvedISRC == "" && strings.Contains(filenameFormat, "{isrc}") {
|
||||||
|
resolvedISRC = ResolveTrackISRC(req.SpotifyID)
|
||||||
|
}
|
||||||
|
filename := buildLyricsFilename(req.TrackName, req.ArtistName, req.AlbumName, req.AlbumArtist, req.ReleaseDate, filenameFormat, resolvedISRC, req.TrackNumber, req.Position, req.DiscNumber)
|
||||||
filePath := filepath.Join(outputDir, filename)
|
filePath := filepath.Join(outputDir, filename)
|
||||||
|
|
||||||
if fileInfo, err := os.Stat(filePath); err == nil && fileInfo.Size() > 0 {
|
filePath, alreadyExists := ResolveOutputPathForDownload(filePath, GetRedownloadWithSuffixSetting())
|
||||||
|
if alreadyExists {
|
||||||
return &LyricsDownloadResponse{
|
return &LyricsDownloadResponse{
|
||||||
Success: true,
|
Success: true,
|
||||||
Message: "Lyrics file already exists",
|
Message: "Lyrics file already exists",
|
||||||
|
|||||||
+10
-5
@@ -326,8 +326,12 @@ func (q *QobuzDownloader) DownloadCoverArt(coverURL, filepath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildQobuzFilename(title, artist, album, albumArtist, releaseDate string, trackNumber, discNumber int, format string, includeTrackNumber bool, position int, useAlbumTrackNumber bool) string {
|
func buildQobuzFilename(title, artist, album, albumArtist, releaseDate string, trackNumber, discNumber int, format string, includeTrackNumber bool, position int, useAlbumTrackNumber bool, extra ...string) string {
|
||||||
var filename string
|
var filename string
|
||||||
|
isrc := ""
|
||||||
|
if len(extra) > 0 {
|
||||||
|
isrc = SanitizeOptionalFilename(extra[0])
|
||||||
|
}
|
||||||
|
|
||||||
numberToUse := position
|
numberToUse := position
|
||||||
if useAlbumTrackNumber && trackNumber > 0 {
|
if useAlbumTrackNumber && trackNumber > 0 {
|
||||||
@@ -347,6 +351,7 @@ func buildQobuzFilename(title, artist, album, albumArtist, releaseDate string, t
|
|||||||
filename = strings.ReplaceAll(filename, "{album_artist}", albumArtist)
|
filename = strings.ReplaceAll(filename, "{album_artist}", albumArtist)
|
||||||
filename = strings.ReplaceAll(filename, "{year}", year)
|
filename = strings.ReplaceAll(filename, "{year}", year)
|
||||||
filename = strings.ReplaceAll(filename, "{date}", SanitizeFilename(releaseDate))
|
filename = strings.ReplaceAll(filename, "{date}", SanitizeFilename(releaseDate))
|
||||||
|
filename = strings.ReplaceAll(filename, "{isrc}", isrc)
|
||||||
|
|
||||||
if discNumber > 0 {
|
if discNumber > 0 {
|
||||||
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
||||||
@@ -467,11 +472,11 @@ func (q *QobuzDownloader) DownloadTrackWithISRC(isrc, outputDir, quality, filena
|
|||||||
safeTitle := sanitizeFilename(trackTitle)
|
safeTitle := sanitizeFilename(trackTitle)
|
||||||
safeAlbum := sanitizeFilename(albumTitle)
|
safeAlbum := sanitizeFilename(albumTitle)
|
||||||
|
|
||||||
filename := buildQobuzFilename(safeTitle, safeArtist, safeAlbum, safeAlbumArtist, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber)
|
filename := buildQobuzFilename(safeTitle, safeArtist, safeAlbum, safeAlbumArtist, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber, isrc)
|
||||||
filepath := filepath.Join(outputDir, filename)
|
filepath := filepath.Join(outputDir, filename)
|
||||||
|
filepath, alreadyExists := ResolveOutputPathForDownload(filepath, GetRedownloadWithSuffixSetting())
|
||||||
if fileInfo, err := os.Stat(filepath); err == nil && fileInfo.Size() > 0 {
|
if alreadyExists {
|
||||||
fmt.Printf("File already exists: %s (%.2f MB)\n", filepath, float64(fileInfo.Size())/(1024*1024))
|
fmt.Printf("File already exists: %s (%.2f MB)\n", filepath, float64(mustFileSize(filepath))/(1024*1024))
|
||||||
return "EXISTS:" + filepath, nil
|
return "EXISTS:" + filepath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-15
@@ -416,7 +416,7 @@ func (t *TidalDownloader) DownloadFromManifest(manifestB64, outputPath string) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, spotifyURL string, allowFallback bool, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, isrcOverride, spotifyURL string, allowFallback bool, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
||||||
if outputDir != "." {
|
if outputDir != "." {
|
||||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||||
return "", fmt.Errorf("directory error: %w", err)
|
return "", fmt.Errorf("directory error: %w", err)
|
||||||
@@ -449,11 +449,12 @@ func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFo
|
|||||||
trackTitleForFile := sanitizeFilename(trackTitle)
|
trackTitleForFile := sanitizeFilename(trackTitle)
|
||||||
albumTitleForFile := sanitizeFilename(albumTitle)
|
albumTitleForFile := sanitizeFilename(albumTitle)
|
||||||
|
|
||||||
filename := buildTidalFilename(trackTitleForFile, artistNameForFile, albumTitleForFile, albumArtistForFile, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber)
|
filename := buildTidalFilename(trackTitleForFile, artistNameForFile, albumTitleForFile, albumArtistForFile, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber, isrcOverride)
|
||||||
outputFilename := filepath.Join(outputDir, filename)
|
outputFilename := filepath.Join(outputDir, filename)
|
||||||
|
|
||||||
if fileInfo, err := os.Stat(outputFilename); err == nil && fileInfo.Size() > 0 {
|
outputFilename, alreadyExists := ResolveOutputPathForDownload(outputFilename, GetRedownloadWithSuffixSetting())
|
||||||
fmt.Printf("File already exists: %s (%.2f MB)\n", outputFilename, float64(fileInfo.Size())/(1024*1024))
|
if alreadyExists {
|
||||||
|
fmt.Printf("File already exists: %s (%.2f MB)\n", outputFilename, float64(mustFileSize(outputFilename))/(1024*1024))
|
||||||
return "EXISTS:" + outputFilename, nil
|
return "EXISTS:" + outputFilename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,11 +512,13 @@ func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFo
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var isrc string
|
isrc := strings.TrimSpace(isrcOverride)
|
||||||
var mbMeta Metadata
|
var mbMeta Metadata
|
||||||
if spotifyURL != "" {
|
if spotifyURL != "" {
|
||||||
result := <-metaChan
|
result := <-metaChan
|
||||||
isrc = result.ISRC
|
if isrc == "" {
|
||||||
|
isrc = result.ISRC
|
||||||
|
}
|
||||||
mbMeta = result.Metadata
|
mbMeta = result.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +575,7 @@ func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFo
|
|||||||
return outputFilename, nil
|
return outputFilename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, spotifyURL string, allowFallback bool, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, isrcOverride, spotifyURL string, allowFallback bool, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
||||||
apis, err := t.GetAvailableAPIs()
|
apis, err := t.GetAvailableAPIs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("no APIs available for fallback: %w", err)
|
return "", fmt.Errorf("no APIs available for fallback: %w", err)
|
||||||
@@ -610,11 +613,12 @@ func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality
|
|||||||
trackTitleForFile := sanitizeFilename(trackTitle)
|
trackTitleForFile := sanitizeFilename(trackTitle)
|
||||||
albumTitleForFile := sanitizeFilename(albumTitle)
|
albumTitleForFile := sanitizeFilename(albumTitle)
|
||||||
|
|
||||||
filename := buildTidalFilename(trackTitleForFile, artistNameForFile, albumTitleForFile, albumArtistForFile, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber)
|
filename := buildTidalFilename(trackTitleForFile, artistNameForFile, albumTitleForFile, albumArtistForFile, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber, isrcOverride)
|
||||||
outputFilename := filepath.Join(outputDir, filename)
|
outputFilename := filepath.Join(outputDir, filename)
|
||||||
|
|
||||||
if fileInfo, err := os.Stat(outputFilename); err == nil && fileInfo.Size() > 0 {
|
outputFilename, alreadyExists := ResolveOutputPathForDownload(outputFilename, GetRedownloadWithSuffixSetting())
|
||||||
fmt.Printf("File already exists: %s (%.2f MB)\n", outputFilename, float64(fileInfo.Size())/(1024*1024))
|
if alreadyExists {
|
||||||
|
fmt.Printf("File already exists: %s (%.2f MB)\n", outputFilename, float64(mustFileSize(outputFilename))/(1024*1024))
|
||||||
return "EXISTS:" + outputFilename, nil
|
return "EXISTS:" + outputFilename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,11 +677,13 @@ func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var isrc string
|
isrc := strings.TrimSpace(isrcOverride)
|
||||||
var mbMeta Metadata
|
var mbMeta Metadata
|
||||||
if spotifyURL != "" {
|
if spotifyURL != "" {
|
||||||
result := <-metaChan
|
result := <-metaChan
|
||||||
isrc = result.ISRC
|
if isrc == "" {
|
||||||
|
isrc = result.ISRC
|
||||||
|
}
|
||||||
mbMeta = result.Metadata
|
mbMeta = result.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,14 +740,14 @@ func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality
|
|||||||
return outputFilename, nil
|
return outputFilename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TidalDownloader) Download(spotifyTrackID, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, spotifyURL string, allowFallback bool, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
func (t *TidalDownloader) Download(spotifyTrackID, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, isrcOverride, spotifyURL string, allowFallback bool, useFirstArtistOnly bool, useSingleGenre bool, embedGenre bool) (string, error) {
|
||||||
|
|
||||||
tidalURL, err := t.GetTidalURLFromSpotify(spotifyTrackID)
|
tidalURL, err := t.GetTidalURLFromSpotify(spotifyTrackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("songlink couldn't find Tidal URL: %w", err)
|
return "", fmt.Errorf("songlink couldn't find Tidal URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, useAlbumTrackNumber, spotifyCoverURL, embedMaxQualityCover, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, spotifyURL, allowFallback, useFirstArtistOnly, useSingleGenre, embedGenre)
|
return t.DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, useAlbumTrackNumber, spotifyCoverURL, embedMaxQualityCover, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyComposer, metadataSeparator, isrcOverride, spotifyURL, allowFallback, useFirstArtistOnly, useSingleGenre, embedGenre)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SegmentTemplate struct {
|
type SegmentTemplate struct {
|
||||||
@@ -981,8 +987,12 @@ func getDownloadURLRotated(apis []string, trackID int64, quality string) (string
|
|||||||
return "", "", fmt.Errorf("all %d APIs failed. Last error: %v", len(apis), lastError)
|
return "", "", fmt.Errorf("all %d APIs failed. Last error: %v", len(apis), lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTidalFilename(title, artist, album, albumArtist, releaseDate string, trackNumber, discNumber int, format string, includeTrackNumber bool, position int, useAlbumTrackNumber bool) string {
|
func buildTidalFilename(title, artist, album, albumArtist, releaseDate string, trackNumber, discNumber int, format string, includeTrackNumber bool, position int, useAlbumTrackNumber bool, extra ...string) string {
|
||||||
var filename string
|
var filename string
|
||||||
|
isrc := ""
|
||||||
|
if len(extra) > 0 {
|
||||||
|
isrc = SanitizeOptionalFilename(extra[0])
|
||||||
|
}
|
||||||
|
|
||||||
numberToUse := position
|
numberToUse := position
|
||||||
if useAlbumTrackNumber && trackNumber > 0 {
|
if useAlbumTrackNumber && trackNumber > 0 {
|
||||||
@@ -1002,6 +1012,7 @@ func buildTidalFilename(title, artist, album, albumArtist, releaseDate string, t
|
|||||||
filename = strings.ReplaceAll(filename, "{album_artist}", albumArtist)
|
filename = strings.ReplaceAll(filename, "{album_artist}", albumArtist)
|
||||||
filename = strings.ReplaceAll(filename, "{year}", year)
|
filename = strings.ReplaceAll(filename, "{year}", year)
|
||||||
filename = strings.ReplaceAll(filename, "{date}", SanitizeFilename(releaseDate))
|
filename = strings.ReplaceAll(filename, "{date}", SanitizeFilename(releaseDate))
|
||||||
|
filename = strings.ReplaceAll(filename, "{isrc}", isrc)
|
||||||
|
|
||||||
if discNumber > 0 {
|
if discNumber > 0 {
|
||||||
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
filename = strings.ReplaceAll(filename, "{disc}", fmt.Sprintf("%d", discNumber))
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface FileMetadata {
|
|||||||
track_number: number;
|
track_number: number;
|
||||||
disc_number: number;
|
disc_number: number;
|
||||||
year: string;
|
year: string;
|
||||||
|
isrc?: string;
|
||||||
}
|
}
|
||||||
type TabType = "track" | "lyric" | "cover";
|
type TabType = "track" | "lyric" | "cover";
|
||||||
const FORMAT_PRESETS: Record<string, {
|
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"/>
|
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help"/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">
|
<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>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -571,7 +572,7 @@ export function FileManagerPage() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<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>
|
</p>
|
||||||
</div>)}
|
</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">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">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">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>)}
|
</div>) : (<div className="text-center py-4 text-muted-foreground">No metadata available</div>)}
|
||||||
<DialogFooter><Button onClick={() => setShowMetadata(false)}>Close</Button></DialogFooter>
|
<DialogFooter><Button onClick={() => setShowMetadata(false)}>Close</Button></DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -568,7 +568,8 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
|
|||||||
.replace(/\{track\}/g, "01")
|
.replace(/\{track\}/g, "01")
|
||||||
.replace(/\{disc\}/g, "1")
|
.replace(/\{disc\}/g, "1")
|
||||||
.replace(/\{year\}/g, "2018")
|
.replace(/\{year\}/g, "2018")
|
||||||
.replace(/\{date\}/g, "2018-02-09")}
|
.replace(/\{date\}/g, "2018-02-09")
|
||||||
|
.replace(/\{isrc\}/g, "USUM71801234")}
|
||||||
/
|
/
|
||||||
</span>
|
</span>
|
||||||
</p>)}
|
</p>)}
|
||||||
@@ -614,6 +615,16 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
|
|||||||
</Label>
|
</Label>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
@@ -686,7 +697,8 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
|
|||||||
.replace(/\{track\}/g, "01")
|
.replace(/\{track\}/g, "01")
|
||||||
.replace(/\{disc\}/g, "1")
|
.replace(/\{disc\}/g, "1")
|
||||||
.replace(/\{year\}/g, "2018")
|
.replace(/\{year\}/g, "2018")
|
||||||
.replace(/\{date\}/g, "2018-02-09")}
|
.replace(/\{date\}/g, "2018-02-09")
|
||||||
|
.replace(/\{isrc\}/g, "USUM71801234")}
|
||||||
.flac
|
.flac
|
||||||
</span>
|
</span>
|
||||||
</p>)}
|
</p>)}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface CheckFileExistenceRequest {
|
|||||||
album_name?: string;
|
album_name?: string;
|
||||||
album_artist?: string;
|
album_artist?: string;
|
||||||
release_date?: string;
|
release_date?: string;
|
||||||
|
isrc?: string;
|
||||||
track_number?: number;
|
track_number?: number;
|
||||||
disc_number?: number;
|
disc_number?: number;
|
||||||
position?: 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 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 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 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) {
|
export function useDownload(region: string) {
|
||||||
const [downloadProgress, setDownloadProgress] = useState<number>(0);
|
const [downloadProgress, setDownloadProgress] = useState<number>(0);
|
||||||
const [isDownloading, setIsDownloading] = useState(false);
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
@@ -81,11 +100,13 @@ export function useDownload(region: string) {
|
|||||||
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist
|
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist
|
||||||
? getFirstArtist(albumArtist)
|
? getFirstArtist(albumArtist)
|
||||||
: albumArtist;
|
: albumArtist;
|
||||||
|
const resolvedTemplateISRC = await resolveTemplateISRC(settings, spotifyId || id);
|
||||||
const templateData: TemplateData = {
|
const templateData: TemplateData = {
|
||||||
artist: displayArtist?.replace(/\//g, placeholder),
|
artist: displayArtist?.replace(/\//g, placeholder),
|
||||||
album: albumName?.replace(/\//g, placeholder),
|
album: albumName?.replace(/\//g, placeholder),
|
||||||
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
||||||
title: trackName?.replace(/\//g, placeholder),
|
title: trackName?.replace(/\//g, placeholder),
|
||||||
|
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
|
||||||
track: trackNumberForTemplate,
|
track: trackNumberForTemplate,
|
||||||
year: yearValue,
|
year: yearValue,
|
||||||
date: releaseDate,
|
date: releaseDate,
|
||||||
@@ -117,6 +138,7 @@ export function useDownload(region: string) {
|
|||||||
album_name: albumName,
|
album_name: albumName,
|
||||||
album_artist: displayAlbumArtist,
|
album_artist: displayAlbumArtist,
|
||||||
release_date: finalReleaseDate || releaseDate,
|
release_date: finalReleaseDate || releaseDate,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
track_number: finalTrackNumber || spotifyTrackNumber || 0,
|
track_number: finalTrackNumber || spotifyTrackNumber || 0,
|
||||||
disc_number: spotifyDiscNumber || 0,
|
disc_number: spotifyDiscNumber || 0,
|
||||||
position: trackNumberForTemplate,
|
position: trackNumberForTemplate,
|
||||||
@@ -193,6 +215,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_first_artist_only: settings.useFirstArtistOnly,
|
use_first_artist_only: settings.useFirstArtistOnly,
|
||||||
@@ -240,6 +263,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_single_genre: settings.useSingleGenre,
|
use_single_genre: settings.useSingleGenre,
|
||||||
@@ -286,6 +310,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_single_genre: settings.useSingleGenre,
|
use_single_genre: settings.useSingleGenre,
|
||||||
@@ -350,6 +375,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_single_genre: settings.useSingleGenre,
|
use_single_genre: settings.useSingleGenre,
|
||||||
@@ -395,11 +421,13 @@ export function useDownload(region: string) {
|
|||||||
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist
|
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist
|
||||||
? getFirstArtist(albumArtist)
|
? getFirstArtist(albumArtist)
|
||||||
: albumArtist;
|
: albumArtist;
|
||||||
|
const resolvedTemplateISRC = await resolveTemplateISRC(settings, spotifyId);
|
||||||
const templateData: TemplateData = {
|
const templateData: TemplateData = {
|
||||||
artist: displayArtist?.replace(/\//g, placeholder),
|
artist: displayArtist?.replace(/\//g, placeholder),
|
||||||
album: albumName?.replace(/\//g, placeholder),
|
album: albumName?.replace(/\//g, placeholder),
|
||||||
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
||||||
title: trackName?.replace(/\//g, placeholder),
|
title: trackName?.replace(/\//g, placeholder),
|
||||||
|
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
|
||||||
track: trackNumberForTemplate,
|
track: trackNumberForTemplate,
|
||||||
year: yearValue,
|
year: yearValue,
|
||||||
date: releaseDate,
|
date: releaseDate,
|
||||||
@@ -468,6 +496,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_first_artist_only: settings.useFirstArtistOnly,
|
use_first_artist_only: settings.useFirstArtistOnly,
|
||||||
@@ -515,6 +544,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_first_artist_only: settings.useFirstArtistOnly,
|
use_first_artist_only: settings.useFirstArtistOnly,
|
||||||
@@ -563,6 +593,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_first_artist_only: settings.useFirstArtistOnly,
|
use_first_artist_only: settings.useFirstArtistOnly,
|
||||||
@@ -624,6 +655,7 @@ export function useDownload(region: string) {
|
|||||||
spotify_disc_number: spotifyDiscNumber,
|
spotify_disc_number: spotifyDiscNumber,
|
||||||
spotify_total_tracks: spotifyTotalTracks,
|
spotify_total_tracks: spotifyTotalTracks,
|
||||||
spotify_total_discs: spotifyTotalDiscs,
|
spotify_total_discs: spotifyTotalDiscs,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
copyright: copyright,
|
copyright: copyright,
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
use_first_artist_only: settings.useFirstArtistOnly,
|
use_first_artist_only: settings.useFirstArtistOnly,
|
||||||
|
|||||||
@@ -5,6 +5,24 @@ import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
|||||||
import { joinPath, sanitizePath, getFirstArtist } from "@/lib/utils";
|
import { joinPath, sanitizePath, getFirstArtist } from "@/lib/utils";
|
||||||
import { logger } from "@/lib/logger";
|
import { logger } from "@/lib/logger";
|
||||||
import type { TrackMetadata } from "@/types/api";
|
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() {
|
export function useLyrics() {
|
||||||
const [downloadingLyricsTrack, setDownloadingLyricsTrack] = useState<string | null>(null);
|
const [downloadingLyricsTrack, setDownloadingLyricsTrack] = useState<string | null>(null);
|
||||||
const [downloadedLyrics, setDownloadedLyrics] = useState<Set<string>>(new Set());
|
const [downloadedLyrics, setDownloadedLyrics] = useState<Set<string>>(new Set());
|
||||||
@@ -28,11 +46,13 @@ export function useLyrics() {
|
|||||||
const yearValue = releaseDate?.substring(0, 4);
|
const yearValue = releaseDate?.substring(0, 4);
|
||||||
const displayArtist = settings.useFirstArtistOnly && artistName ? getFirstArtist(artistName) : artistName;
|
const displayArtist = settings.useFirstArtistOnly && artistName ? getFirstArtist(artistName) : artistName;
|
||||||
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist ? getFirstArtist(albumArtist) : albumArtist;
|
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist ? getFirstArtist(albumArtist) : albumArtist;
|
||||||
|
const resolvedTemplateISRC = await resolveTemplateISRC(settings, spotifyId);
|
||||||
const templateData: TemplateData = {
|
const templateData: TemplateData = {
|
||||||
artist: displayArtist?.replace(/\//g, placeholder),
|
artist: displayArtist?.replace(/\//g, placeholder),
|
||||||
album: albumName?.replace(/\//g, placeholder),
|
album: albumName?.replace(/\//g, placeholder),
|
||||||
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
||||||
title: trackName?.replace(/\//g, placeholder),
|
title: trackName?.replace(/\//g, placeholder),
|
||||||
|
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
|
||||||
track: position,
|
track: position,
|
||||||
year: yearValue,
|
year: yearValue,
|
||||||
date: releaseDate,
|
date: releaseDate,
|
||||||
@@ -61,6 +81,7 @@ export function useLyrics() {
|
|||||||
album_name: albumName,
|
album_name: albumName,
|
||||||
album_artist: displayAlbumArtist,
|
album_artist: displayAlbumArtist,
|
||||||
release_date: releaseDate,
|
release_date: releaseDate,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
output_dir: outputDir,
|
output_dir: outputDir,
|
||||||
filename_format: settings.filenameTemplate || "{title}",
|
filename_format: settings.filenameTemplate || "{title}",
|
||||||
track_number: settings.trackNumber,
|
track_number: settings.trackNumber,
|
||||||
@@ -129,11 +150,13 @@ export function useLyrics() {
|
|||||||
const yearValue = track.release_date?.substring(0, 4);
|
const yearValue = track.release_date?.substring(0, 4);
|
||||||
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
|
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 displayAlbumArtist = settings.useFirstArtistOnly && track.album_artist ? getFirstArtist(track.album_artist) : track.album_artist;
|
||||||
|
const resolvedTemplateISRC = await resolveTemplateISRC(settings, id);
|
||||||
const templateData: TemplateData = {
|
const templateData: TemplateData = {
|
||||||
artist: displayArtist?.replace(/\//g, placeholder),
|
artist: displayArtist?.replace(/\//g, placeholder),
|
||||||
album: track.album_name?.replace(/\//g, placeholder),
|
album: track.album_name?.replace(/\//g, placeholder),
|
||||||
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
||||||
title: track.name?.replace(/\//g, placeholder),
|
title: track.name?.replace(/\//g, placeholder),
|
||||||
|
isrc: resolvedTemplateISRC?.replace(/\//g, placeholder),
|
||||||
track: trackPosition,
|
track: trackPosition,
|
||||||
year: yearValue,
|
year: yearValue,
|
||||||
date: track.release_date,
|
date: track.release_date,
|
||||||
@@ -161,6 +184,7 @@ export function useLyrics() {
|
|||||||
album_name: track.album_name,
|
album_name: track.album_name,
|
||||||
album_artist: displayAlbumArtist,
|
album_artist: displayAlbumArtist,
|
||||||
release_date: track.release_date,
|
release_date: track.release_date,
|
||||||
|
isrc: resolvedTemplateISRC || undefined,
|
||||||
output_dir: outputDir,
|
output_dir: outputDir,
|
||||||
filename_format: settings.filenameTemplate || "{title}",
|
filename_format: settings.filenameTemplate || "{title}",
|
||||||
track_number: settings.trackNumber,
|
track_number: settings.trackNumber,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export interface Settings {
|
|||||||
useFirstArtistOnly: boolean;
|
useFirstArtistOnly: boolean;
|
||||||
useSingleGenre: boolean;
|
useSingleGenre: boolean;
|
||||||
embedGenre: boolean;
|
embedGenre: boolean;
|
||||||
|
redownloadWithSuffix: boolean;
|
||||||
separator: "comma" | "semicolon";
|
separator: "comma" | "semicolon";
|
||||||
}
|
}
|
||||||
export const FOLDER_PRESETS: Record<FolderPreset, {
|
export const FOLDER_PRESETS: Record<FolderPreset, {
|
||||||
@@ -85,6 +86,7 @@ export const TEMPLATE_VARIABLES = [
|
|||||||
{ key: "{disc}", description: "Disc number", example: "1" },
|
{ key: "{disc}", description: "Disc number", example: "1" },
|
||||||
{ key: "{year}", description: "Release year", example: "2014" },
|
{ key: "{year}", description: "Release year", example: "2014" },
|
||||||
{ key: "{date}", description: "Release date (YYYY-MM-DD)", example: "2014-10-27" },
|
{ key: "{date}", description: "Release date (YYYY-MM-DD)", example: "2014-10-27" },
|
||||||
|
{ key: "{isrc}", description: "Track ISRC", example: "USUM71412345" },
|
||||||
];
|
];
|
||||||
function detectOS(): "Windows" | "linux/MacOS" {
|
function detectOS(): "Windows" | "linux/MacOS" {
|
||||||
const platform = window.navigator.platform.toLowerCase();
|
const platform = window.navigator.platform.toLowerCase();
|
||||||
@@ -124,6 +126,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||||||
useFirstArtistOnly: false,
|
useFirstArtistOnly: false,
|
||||||
useSingleGenre: false,
|
useSingleGenre: false,
|
||||||
embedGenre: true,
|
embedGenre: true,
|
||||||
|
redownloadWithSuffix: false,
|
||||||
separator: "semicolon"
|
separator: "semicolon"
|
||||||
};
|
};
|
||||||
export const FONT_OPTIONS: {
|
export const FONT_OPTIONS: {
|
||||||
@@ -243,6 +246,9 @@ function getSettingsFromLocalStorage(): Settings {
|
|||||||
if (!('separator' in parsed)) {
|
if (!('separator' in parsed)) {
|
||||||
parsed.separator = "semicolon";
|
parsed.separator = "semicolon";
|
||||||
}
|
}
|
||||||
|
if (!('redownloadWithSuffix' in parsed)) {
|
||||||
|
parsed.redownloadWithSuffix = false;
|
||||||
|
}
|
||||||
return { ...DEFAULT_SETTINGS, ...parsed };
|
return { ...DEFAULT_SETTINGS, ...parsed };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,6 +352,9 @@ export async function loadSettings(): Promise<Settings> {
|
|||||||
if (!('separator' in parsed)) {
|
if (!('separator' in parsed)) {
|
||||||
parsed.separator = "semicolon";
|
parsed.separator = "semicolon";
|
||||||
}
|
}
|
||||||
|
if (!('redownloadWithSuffix' in parsed)) {
|
||||||
|
parsed.redownloadWithSuffix = false;
|
||||||
|
}
|
||||||
cachedSettings = { ...DEFAULT_SETTINGS, ...parsed };
|
cachedSettings = { ...DEFAULT_SETTINGS, ...parsed };
|
||||||
return cachedSettings!;
|
return cachedSettings!;
|
||||||
}
|
}
|
||||||
@@ -368,6 +377,7 @@ export interface TemplateData {
|
|||||||
album?: string;
|
album?: string;
|
||||||
album_artist?: string;
|
album_artist?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
isrc?: string;
|
||||||
track?: number;
|
track?: number;
|
||||||
disc?: number;
|
disc?: number;
|
||||||
year?: string;
|
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(/\{artist\}/g, data.artist || "Unknown Artist");
|
||||||
result = result.replace(/\{album\}/g, data.album || "Unknown Album");
|
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(/\{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(/\{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(/\{disc\}/g, data.disc ? String(data.disc) : "1");
|
||||||
result = result.replace(/\{year\}/g, data.year || "0000");
|
result = result.replace(/\{year\}/g, data.year || "0000");
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface TrackMetadata {
|
|||||||
artist_id?: string;
|
artist_id?: string;
|
||||||
artist_url?: string;
|
artist_url?: string;
|
||||||
artists_data?: ArtistSimple[];
|
artists_data?: ArtistSimple[];
|
||||||
|
isrc?: string;
|
||||||
copyright?: string;
|
copyright?: string;
|
||||||
publisher?: string;
|
publisher?: string;
|
||||||
plays?: string;
|
plays?: string;
|
||||||
@@ -134,6 +135,7 @@ export interface DownloadRequest {
|
|||||||
spotify_disc_number?: number;
|
spotify_disc_number?: number;
|
||||||
spotify_total_tracks?: number;
|
spotify_total_tracks?: number;
|
||||||
spotify_total_discs?: number;
|
spotify_total_discs?: number;
|
||||||
|
isrc?: string;
|
||||||
copyright?: string;
|
copyright?: string;
|
||||||
publisher?: string;
|
publisher?: string;
|
||||||
spotify_url?: string;
|
spotify_url?: string;
|
||||||
@@ -190,6 +192,7 @@ export interface LyricsDownloadRequest {
|
|||||||
album_name?: string;
|
album_name?: string;
|
||||||
album_artist?: string;
|
album_artist?: string;
|
||||||
release_date?: string;
|
release_date?: string;
|
||||||
|
isrc?: string;
|
||||||
output_dir?: string;
|
output_dir?: string;
|
||||||
filename_format?: string;
|
filename_format?: string;
|
||||||
track_number?: boolean;
|
track_number?: boolean;
|
||||||
@@ -278,4 +281,5 @@ export interface AudioMetadata {
|
|||||||
track_number: number;
|
track_number: number;
|
||||||
disc_number: number;
|
disc_number: number;
|
||||||
year: string;
|
year: string;
|
||||||
|
isrc?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user