mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-10-16 13:57:25 +02:00
Completes the restructuring started when main() became run() int. main.go goes
from 1217 lines to 130: global option handling, config loading, signal setup,
and a dispatch switch that delegates to one function per command.
The command bodies move to cmd_rip.go (url, file, id), cmd_search.go (search,
lastfm), cmd_config.go (config, database) and cmd_smoke.go (the developer smoke
commands), each taking (ctx, cfg, gopts, args) and returning an exit code.
They also stop reading the process global. Previously run() did
os.Args = append([]string{os.Args[0], gopts.command}, gopts.commandArgs...)
and the arms then indexed back into os.Args[2:], os.Args[5:] and so on, so the
already-parsed options were round-tripped through os.Args. The arms now take
the parsed gopts.commandArgs directly, which is a uniform index shift of two
(os.Args[5:] -> args[3:], len(os.Args) < 5 -> len(args) < 3).
No behaviour change. Verified by running each command's argument validation and
help path against the built binary, plus the existing suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
310 lines
8.0 KiB
Go
310 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"streamrip-go/internal/app"
|
|
"streamrip-go/internal/config"
|
|
"streamrip-go/internal/provider"
|
|
)
|
|
|
|
// Developer smoke commands, listed by "rip dev-help". These exercise a single
|
|
// provider call and print the result; they are not part of the supported CLI.
|
|
|
|
func cmdSoundcloudSmoke(ctx context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
|
if len(args) < 1 {
|
|
fmt.Println("usage: rip soundcloud-smoke <soundcloud_url>")
|
|
return 2
|
|
}
|
|
meta, err := fetchSoundcloudOEmbed(ctx, cfg.Session.Downloads.VerifySSL, strings.TrimSpace(args[0]))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "soundcloud smoke error: %v\n", err)
|
|
return 1
|
|
}
|
|
fmt.Printf("soundcloud oembed ok: title=%q author=%q provider=%q\n", asString(meta["title"]), asString(meta["author_name"]), asString(meta["provider_name"]))
|
|
|
|
return 0
|
|
}
|
|
|
|
func cmdQobuzSmoke(ctx context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
|
if len(args) < 1 {
|
|
fmt.Println("usage: rip qobuz-smoke <track_id> [quality]")
|
|
return 2
|
|
}
|
|
opts, err := parseSmokeOptions(args[1:], 1, 4)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "option error: %v\n", err)
|
|
return 2
|
|
}
|
|
if opts.qualitySet {
|
|
cfg.Session.Qobuz.Quality = opts.quality
|
|
}
|
|
|
|
mainApp, err := app.New(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "app init error: %v\n", err)
|
|
return 1
|
|
}
|
|
defer func() { _ = mainApp.Close() }()
|
|
|
|
provider, err := mainApp.GetLoggedInProvider(ctx, "qobuz")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "qobuz login error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
trackID := args[0]
|
|
meta, err := provider.GetMetadata(ctx, trackID, "track")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "metadata error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
title, _ := meta["title"].(string)
|
|
d, err := provider.GetDownloadable(ctx, trackID, cfg.Session.Qobuz.Quality)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "downloadable error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
fmt.Printf("qobuz ok: title=%q quality=%d ext=%s\n", title, cfg.Session.Qobuz.Quality, d.Extension)
|
|
fmt.Printf("stream_url=%s\n", d.URL)
|
|
|
|
return 0
|
|
}
|
|
|
|
func cmdQobuzConvertRipSmoke(ctx context.Context, cfg *config.Config, gopts globalOptions, args []string) int {
|
|
if len(args) < 2 {
|
|
fmt.Println("usage: rip qobuz-convert-rip-smoke <track_id> <codec> [quality] [--force|--ignore-db]")
|
|
return 2
|
|
}
|
|
opts, err := parseSmokeOptions(args[2:], 1, 4)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "option error: %v\n", err)
|
|
return 2
|
|
}
|
|
if opts.qualitySet {
|
|
cfg.Session.Qobuz.Quality = opts.quality
|
|
}
|
|
cfg.Session.Conversion.Enabled = true
|
|
cfg.Session.Conversion.Codec = strings.ToUpper(strings.TrimSpace(args[1]))
|
|
|
|
mainApp, err := app.New(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "app init error: %v\n", err)
|
|
return 1
|
|
}
|
|
defer func() { _ = mainApp.Close() }()
|
|
mainApp.IgnoreDB = opts.ignoreDB || gopts.noDB
|
|
|
|
trackID := args[0]
|
|
if err = mainApp.AddByID(ctx, "qobuz", "track", trackID); err != nil {
|
|
fmt.Fprintf(os.Stderr, "add error: %v\n", err)
|
|
return 1
|
|
}
|
|
if err = mainApp.Resolve(ctx); err != nil {
|
|
fmt.Fprintf(os.Stderr, "resolve error: %v\n", err)
|
|
return 1
|
|
}
|
|
if err = mainApp.Rip(ctx); err != nil {
|
|
fmt.Fprintf(os.Stderr, "rip error: %v\n", err)
|
|
return 1
|
|
}
|
|
fmt.Printf("qobuz convert rip smoke complete (codec=%s)\n", cfg.Session.Conversion.Codec)
|
|
|
|
return 0
|
|
}
|
|
|
|
func cmdQobuzSearchSmoke(ctx context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
|
if len(args) < 1 {
|
|
fmt.Println("usage: rip qobuz-search-smoke <query>")
|
|
return 2
|
|
}
|
|
|
|
mainApp, err := app.New(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "app init error: %v\n", err)
|
|
return 1
|
|
}
|
|
defer func() { _ = mainApp.Close() }()
|
|
|
|
provider, err := mainApp.GetLoggedInProvider(ctx, "qobuz")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "qobuz login error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
query := strings.Join(args, " ")
|
|
pages, err := provider.Search(ctx, "album", query, 10)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "search error: %v\n", err)
|
|
return 1
|
|
}
|
|
for _, page := range pages {
|
|
albums, ok := page["albums"].(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
items, ok := albums["items"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, raw := range items {
|
|
item, ok := raw.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
id := asString(item["id"])
|
|
title := asString(item["title"])
|
|
version := asString(item["version"])
|
|
bitDepth := asString(item["maximum_bit_depth"])
|
|
sr := asString(item["maximum_sampling_rate"])
|
|
if version != "" {
|
|
title = title + " (" + version + ")"
|
|
}
|
|
fmt.Printf("album_id=%s | %s | %sB-%skHz\n", id, title, bitDepth, sr)
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func cmdTidalSearchSmoke(ctx context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
|
if len(args) < 1 {
|
|
fmt.Println("usage: rip tidal-search-smoke <query>")
|
|
return 2
|
|
}
|
|
|
|
mainApp, err := app.New(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "app init error: %v\n", err)
|
|
return 1
|
|
}
|
|
defer func() { _ = mainApp.Close() }()
|
|
|
|
provider, err := mainApp.GetLoggedInProvider(ctx, "tidal")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "tidal login error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
query := strings.Join(args, " ")
|
|
pages, err := provider.Search(ctx, "album", query, 10)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "search error: %v\n", err)
|
|
return 1
|
|
}
|
|
for _, page := range pages {
|
|
items, ok := page["items"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, raw := range items {
|
|
wrapper, ok := raw.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
item, ok := wrapper["item"].(map[string]any)
|
|
if !ok {
|
|
item = wrapper
|
|
}
|
|
fmt.Printf("album_id=%s | %s\n", asString(item["id"]), asString(item["title"]))
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func cmdTidalMetadataSmoke(ctx context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
|
if len(args) < 2 {
|
|
fmt.Println("usage: rip tidal-metadata-smoke <track|album|playlist|artist> <id>")
|
|
return 2
|
|
}
|
|
|
|
mainApp, err := app.New(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "app init error: %v\n", err)
|
|
return 1
|
|
}
|
|
defer func() { _ = mainApp.Close() }()
|
|
|
|
provider, err := mainApp.GetLoggedInProvider(ctx, "tidal")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "tidal login error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
mediaType := args[0]
|
|
itemID := args[1]
|
|
meta, err := provider.GetMetadata(ctx, itemID, mediaType)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "metadata error: %v\n", err)
|
|
return 1
|
|
}
|
|
title := asString(meta["title"])
|
|
if title == "" {
|
|
title = asString(meta["name"])
|
|
}
|
|
trackCount := 0
|
|
if tracksMap, ok := meta["tracks"].(map[string]any); ok {
|
|
if items, ok := tracksMap["items"].([]map[string]any); ok {
|
|
trackCount = len(items)
|
|
} else if anyItems, ok := tracksMap["items"].([]any); ok {
|
|
trackCount = len(anyItems)
|
|
}
|
|
}
|
|
fmt.Printf("tidal metadata ok: type=%s id=%s title=%q tracks=%d\n", mediaType, itemID, title, trackCount)
|
|
|
|
return 0
|
|
}
|
|
|
|
func cmdTidalVideoSmoke(ctx context.Context, cfg *config.Config, _ globalOptions, args []string) int {
|
|
if len(args) < 1 {
|
|
fmt.Println("usage: rip tidal-video-smoke <video_id>")
|
|
return 2
|
|
}
|
|
|
|
mainApp, err := app.New(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "app init error: %v\n", err)
|
|
return 1
|
|
}
|
|
defer func() { _ = mainApp.Close() }()
|
|
|
|
providerClient, err := mainApp.GetLoggedInProvider(ctx, "tidal")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "tidal login error: %v\n", err)
|
|
return 1
|
|
}
|
|
videoProvider, ok := providerClient.(interface {
|
|
GetVideoDownloadable(context.Context, string) (*provider.Downloadable, error)
|
|
})
|
|
if !ok {
|
|
fmt.Fprintln(os.Stderr, "tidal provider does not support video downloadable")
|
|
return 1
|
|
}
|
|
|
|
videoID := strings.TrimSpace(args[0])
|
|
meta, err := providerClient.GetMetadata(ctx, videoID, "video")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "video metadata error: %v\n", err)
|
|
return 1
|
|
}
|
|
d, err := videoProvider.GetVideoDownloadable(ctx, videoID)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "video downloadable error: %v\n", err)
|
|
return 1
|
|
}
|
|
title := asString(meta["title"])
|
|
if title == "" {
|
|
title = asString(meta["name"])
|
|
}
|
|
fmt.Printf("tidal video ok: id=%s title=%q ext=%s\n", videoID, title, d.Extension)
|
|
fmt.Printf("stream_url=%s\n", d.URL)
|
|
|
|
return 0
|
|
}
|