Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type DownloadConfig struct {
Slskd Slskd
ExcludeLocal bool
DownloadLimiter int `env:"DOWNLOAD_LIMITER" env-default:"1"` // rate limit download operations
OverwriteMetadata bool `env:"OVERWRITE_METADATA" env-default:"false"` // overwrite metadata when migrating downloads
OverwriteMetadata bool `env:"OVERWRITE_METADATA" env-default:"true"` // rewrite clean tags on every finished download
KeepPermissions bool `env:"KEEP_PERMISSIONS" env-default:"true"` // keep original file permissions when migrating download
RenameTrack bool `env:"RENAME_TRACK" env-default:"false"` // Rename track in {title}-{artist} format
UseSubDir bool `env:"USE_SUBDIRECTORY" env-default:"true"`
Expand Down
73 changes: 49 additions & 24 deletions src/downloader/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,20 +246,36 @@ func buildTrackPath(template string, track *models.Track) string {
return filepath.Clean(result)
}

func (c *DownloadClient) MoveDownload(srcDir, destDir, trackPath string, track *models.Track) error {
trackDir := filepath.Join(srcDir, trackPath)
srcFile := filepath.Join(trackDir, track.File)
func (c *DownloadClient) FinalizeDownload(monCfg MonitorConfig, trackPath string, track *models.Track) error {
srcFile := filepath.Join(monCfg.FromDir, trackPath, track.File)

if c.Cfg.RenameTrack { // Rename file to {title}-{artist} format
track.File = getFilename(track.CleanTitle, track.MainArtist) + filepath.Ext(track.File)
}
if c.Cfg.OverwriteMetadata {
metadata := util.BuildffmpegMetadata(*track)
if err := overwriteMetadata(metadata, srcFile); err != nil {
slog.Info(fmt.Sprintf("Writing clean metadata - %s", track.CleanTitle))
if err := overwriteMetadata(util.BuildffmpegMetadata(*track), track.CoverPath, srcFile); err != nil {
slog.Warn("problem overwriting metadata", "msg", err.Error())
}
}

if c.Cfg.RenameTrack {
newFile := getFilename(track.CleanTitle, track.MainArtist) + filepath.Ext(track.File)
if newFile != track.File {
if err := os.Rename(srcFile, filepath.Join(monCfg.FromDir, trackPath, newFile)); err != nil {
return fmt.Errorf("failed to rename track: %w", err)
}
track.File = newFile
}
}

if monCfg.MigrateDownload {
return c.MoveDownload(monCfg.FromDir, monCfg.ToDir, trackPath, track)
}
return nil
}

func (c *DownloadClient) MoveDownload(srcDir, destDir, trackPath string, track *models.Track) error {
trackDir := filepath.Join(srcDir, trackPath)
srcFile := filepath.Join(trackDir, track.File)

in, err := os.Open(srcFile)
if err != nil {
return fmt.Errorf("couldn't open source file: %s", err.Error())
Expand Down Expand Up @@ -338,26 +354,35 @@ func (c *DownloadClient) MoveDownload(srcDir, destDir, trackPath string, track *
return nil
}

func overwriteMetadata(metadata []string, srcFile string) error {
func overwriteMetadata(metadata []string, coverPath, srcFile string) error {
opts := ffmpeg.KwArgs{
"c": "copy",
"metadata": metadata,
"loglevel": "error",
}
streams := []*ffmpeg.Stream{
ffmpeg.Input(srcFile),
"c": "copy",
"map_metadata": "-1",
"metadata": metadata,
"loglevel": "error",
}
streams := []*ffmpeg.Stream{ffmpeg.Input(srcFile)}

if coverPath != "" {
if _, err := os.Stat(coverPath); err == nil {
streams = append(streams, ffmpeg.Input(coverPath))
opts["map"] = "-0:v?"
opts["disposition:v"] = "attached_pic"
if strings.EqualFold(filepath.Ext(srcFile), ".mp3") {
opts["id3v2_version"] = "3"
}
}
}

tmpFile := tempAudioFile(srcFile)
tmpFile := tempAudioFile(srcFile)

if err := util.WriteMetadata(streams, "", tmpFile, opts); err != nil {
return fmt.Errorf("failed to overwrite metadata: %w", err)
} else {
if err := os.Rename(tmpFile, srcFile); err != nil {
return fmt.Errorf("failed to rename tmp file: %w", err)
}
}
return nil
if err := util.WriteMetadata(streams, "", tmpFile, opts); err != nil {
return fmt.Errorf("failed to overwrite metadata: %w", err)
}
if err := os.Rename(tmpFile, srcFile); err != nil {
return fmt.Errorf("failed to rename tmp file: %w", err)
}
return nil
}

func tempAudioFile(path string) string {
Expand Down
21 changes: 13 additions & 8 deletions src/downloader/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type Monitor interface {
GetDownloadStatus([]*models.Track) (map[string]FileStatus, error)
GetConf() (MonitorConfig, error)
RetryDownload(*models.Track) (bool, error)
Cleanup(models.Track, string) error
}

Expand Down Expand Up @@ -88,12 +89,8 @@ func (c *DownloadClient) MonitorDownloads(tracks []*models.Track, m Monitor) err
slog.Info("[monitor] file downloaded successfully", "service", monCfg.Service, "file", track.File)
var path string
track.File, path = parsePath(track.File)
if monCfg.MigrateDownload {
if err = c.MoveDownload(monCfg.FromDir, monCfg.ToDir, path, track); err != nil {
slog.Error("error while moving file", "err", err.Error())
} else {
slog.Info("track moved successfully", "service", monCfg.Service)
}
if err = c.FinalizeDownload(monCfg, path, track); err != nil {
slog.Error("error finalizing download", "service", monCfg.Service, "err", err.Error())
}
delete(progressMap, key)
successDownloads += 1
Expand All @@ -109,11 +106,19 @@ func (c *DownloadClient) MonitorDownloads(tracks []*models.Track, m Monitor) err
continue

} else if monitoredTime > monCfg.MonitorDuration || fileStatus.State == "Errored" {
slog.Info("[monitor] no download progress for file, skipping", "service", monCfg.Service, "file", track.File, "state", fileStatus.State, "duration", monitoredTime,)
tracker.Skipped = true
if err = m.Cleanup(*track, fileStatus.ID); err != nil {
slog.Debug("cleanup failed", logging.RuntimeAttr(err.Error()))
}
if retried, _ := m.RetryDownload(track); retried {
slog.Info("[monitor] source failed, trying next", "service", monCfg.Service, "title", track.CleanTitle, "state", fileStatus.State)
// track.File changed, re-track under the new key
delete(progressMap, key)
newKey := fmt.Sprintf("%s|%s", track.ID, track.File)
progressMap[newKey] = &DownloadMonitor{LastUpdated: currentTime}
continue
}
slog.Info("[monitor] no more sources, skipping", "service", monCfg.Service, "file", track.File, "state", fileStatus.State, "duration", monitoredTime)
tracker.Skipped = true
continue
}
}
Expand Down
Loading
Loading