mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
add CLI parity flags and expand provider support
This brings the Go CLI closer to upstream behavior with global flag handling and clearer resolve failures, while adding Tidal video downloads plus initial Deezer and SoundCloud no-account flows for broader end-to-end coverage.
This commit is contained in:
@@ -18,7 +18,9 @@ import (
|
||||
"streamrip-go/internal/download"
|
||||
"streamrip-go/internal/naming"
|
||||
"streamrip-go/internal/provider"
|
||||
deezerprovider "streamrip-go/internal/provider/deezer"
|
||||
qobuzprovider "streamrip-go/internal/provider/qobuz"
|
||||
soundcloudprovider "streamrip-go/internal/provider/soundcloud"
|
||||
tidalprovider "streamrip-go/internal/provider/tidal"
|
||||
"streamrip-go/internal/store"
|
||||
)
|
||||
@@ -66,6 +68,10 @@ type trackTagger interface {
|
||||
TagFLAC(path string, meta tag.Metadata, coverPath string) error
|
||||
}
|
||||
|
||||
type videoDownloadableProvider interface {
|
||||
GetVideoDownloadable(ctx context.Context, videoID string) (*provider.Downloadable, error)
|
||||
}
|
||||
|
||||
func New(cfg *config.Config) (*Main, error) {
|
||||
var db store.Database
|
||||
if cfg.Session.Database.DownloadsEnabled || cfg.Session.Database.FailedDownloadsEnabled {
|
||||
@@ -79,8 +85,10 @@ func New(cfg *config.Config) (*Main, error) {
|
||||
}
|
||||
|
||||
providers := map[string]provider.Client{
|
||||
"qobuz": qobuzprovider.New(cfg),
|
||||
"tidal": tidalprovider.New(cfg),
|
||||
"qobuz": qobuzprovider.New(cfg),
|
||||
"tidal": tidalprovider.New(cfg),
|
||||
"deezer": deezerprovider.New(cfg),
|
||||
"soundcloud": soundcloudprovider.New(cfg),
|
||||
}
|
||||
|
||||
return &Main{
|
||||
@@ -156,8 +164,10 @@ func (m *Main) AddByID(ctx context.Context, source, mediaType, id string) error
|
||||
return m.ripCollection(ctx, p, source, "Artist", id, meta)
|
||||
case "label":
|
||||
return m.ripCollection(ctx, p, source, "Label", id, meta)
|
||||
case "video":
|
||||
return m.ripVideo(ctx, p, source, id, meta)
|
||||
default:
|
||||
return nil
|
||||
return fmt.Errorf("unsupported media type %q", mediaType)
|
||||
}
|
||||
}}, nil
|
||||
},
|
||||
@@ -205,6 +215,37 @@ func (m *Main) ripCollection(ctx context.Context, p provider.Client, source, kin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Main) ripVideo(ctx context.Context, p provider.Client, source, videoID string, meta map[string]any) error {
|
||||
alreadyDownloaded, err := m.Store.IsDownloaded(ctx, source, videoID)
|
||||
if err == nil && alreadyDownloaded && !m.IgnoreDB {
|
||||
m.logf("skip (already downloaded) id=%s\n", videoID)
|
||||
return nil
|
||||
}
|
||||
|
||||
vp, ok := p.(videoDownloadableProvider)
|
||||
if !ok {
|
||||
return fmt.Errorf("provider %q does not support video downloads", source)
|
||||
}
|
||||
|
||||
d, err := vp.GetVideoDownloadable(ctx, videoID)
|
||||
if err != nil {
|
||||
_ = m.Store.MarkFailed(ctx, source, "video", videoID)
|
||||
return fmt.Errorf("id=%s get_video_downloadable: %w", videoID, err)
|
||||
}
|
||||
|
||||
title := titleFromMetadata(meta, videoID)
|
||||
outPath := m.videoOutputPath(source, videoID, title, d.Extension)
|
||||
if err = m.DL.FileVideo(ctx, d.URL, outPath); err != nil {
|
||||
_ = m.Store.MarkFailed(ctx, source, "video", videoID)
|
||||
return fmt.Errorf("id=%s title=%q video download: %w", videoID, title, err)
|
||||
}
|
||||
|
||||
if err = m.Store.MarkDownloaded(ctx, source, videoID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildCollectionAlbum(id string, meta map[string]any) collectionAlbum {
|
||||
trackCount := intFromAny(meta["tracks_count"])
|
||||
if trackCount == 0 {
|
||||
@@ -357,16 +398,21 @@ func extractAlbumIDs(meta map[string]any) []string {
|
||||
}
|
||||
|
||||
func (m *Main) Resolve(ctx context.Context) error {
|
||||
pendingCount := len(m.Pending)
|
||||
resolved := make([]media.Media, 0, len(m.Pending))
|
||||
for _, item := range m.Pending {
|
||||
med, err := item.Resolve(ctx)
|
||||
if err != nil {
|
||||
m.logf("resolve failed: %v\n", err)
|
||||
continue
|
||||
}
|
||||
resolved = append(resolved, med)
|
||||
}
|
||||
m.Media = append(m.Media, resolved...)
|
||||
m.Pending = m.Pending[:0]
|
||||
if pendingCount > 0 && len(resolved) == 0 {
|
||||
return fmt.Errorf("resolve failed for all %d pending item(s)", pendingCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -830,6 +876,24 @@ func (m *Main) trackOutputPath(source, id, title, ext string, trackMeta map[stri
|
||||
return filepath.Join(base, fileName+"."+ext)
|
||||
}
|
||||
|
||||
func (m *Main) videoOutputPath(source, id, title, ext string) string {
|
||||
if strings.TrimSpace(ext) == "" {
|
||||
ext = "mp4"
|
||||
}
|
||||
base := m.Config.Session.Downloads.Folder
|
||||
if m.Config.Session.Downloads.SourceSubdirectories {
|
||||
base = filepath.Join(base, strings.Title(source))
|
||||
}
|
||||
fileName := naming.CleanName(title, naming.Config{
|
||||
RestrictCharacters: m.Config.Session.Filepaths.RestrictCharacters,
|
||||
TruncateTo: m.Config.Session.Filepaths.TruncateTo,
|
||||
})
|
||||
if fileName == "" {
|
||||
fileName = id
|
||||
}
|
||||
return filepath.Join(base, fileName+"."+ext)
|
||||
}
|
||||
|
||||
func titleFromMetadata(meta map[string]any, fallback string) string {
|
||||
if title, ok := meta["title"].(string); ok {
|
||||
title = strings.TrimSpace(title)
|
||||
|
||||
Reference in New Issue
Block a user