mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
218 lines
5.3 KiB
Go
218 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"streamrip-go/internal/config"
|
|
)
|
|
|
|
type smokeOptions struct {
|
|
qualitySet bool
|
|
quality int
|
|
ignoreDB bool
|
|
}
|
|
|
|
type globalOptions struct {
|
|
configPath string
|
|
folder string
|
|
noDB bool
|
|
qualitySet bool
|
|
quality int
|
|
codecSet bool
|
|
codec string
|
|
noProgress bool
|
|
noSSLVerify bool
|
|
verbose int
|
|
command string
|
|
commandArgs []string
|
|
}
|
|
|
|
func parseGlobalArgs(args []string) (globalOptions, error) {
|
|
opts := globalOptions{}
|
|
for i := 0; i < len(args); i++ {
|
|
arg := args[i]
|
|
if arg == "" {
|
|
continue
|
|
}
|
|
if !strings.HasPrefix(arg, "-") {
|
|
opts.command = arg
|
|
if i+1 < len(args) {
|
|
opts.commandArgs = append([]string(nil), args[i+1:]...)
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
switch {
|
|
case arg == "-ndb" || arg == "--no-db":
|
|
opts.noDB = true
|
|
case arg == "--no-progress":
|
|
opts.noProgress = true
|
|
case arg == "--no-ssl-verify":
|
|
opts.noSSLVerify = true
|
|
case arg == "-v" || arg == "--verbose":
|
|
if opts.verbose < 2 {
|
|
opts.verbose++
|
|
}
|
|
case arg == "-vv":
|
|
if opts.verbose < 2 {
|
|
opts.verbose = 2
|
|
}
|
|
case arg == "-f" || arg == "--folder":
|
|
if i+1 >= len(args) {
|
|
return globalOptions{}, fmt.Errorf("%s requires a value", arg)
|
|
}
|
|
opts.folder = strings.TrimSpace(args[i+1])
|
|
i++
|
|
case strings.HasPrefix(arg, "--folder="):
|
|
opts.folder = strings.TrimSpace(strings.TrimPrefix(arg, "--folder="))
|
|
case arg == "--config-path":
|
|
if i+1 >= len(args) {
|
|
return globalOptions{}, fmt.Errorf("--config-path requires a value")
|
|
}
|
|
opts.configPath = strings.TrimSpace(args[i+1])
|
|
i++
|
|
case strings.HasPrefix(arg, "--config-path="):
|
|
opts.configPath = strings.TrimSpace(strings.TrimPrefix(arg, "--config-path="))
|
|
case arg == "-q" || arg == "--quality":
|
|
if i+1 >= len(args) {
|
|
return globalOptions{}, fmt.Errorf("%s requires a value", arg)
|
|
}
|
|
q, err := strconv.Atoi(args[i+1])
|
|
if err != nil || q < 0 || q > 4 {
|
|
return globalOptions{}, fmt.Errorf("invalid quality %q (expected 0-4)", args[i+1])
|
|
}
|
|
opts.qualitySet = true
|
|
opts.quality = q
|
|
i++
|
|
case strings.HasPrefix(arg, "--quality="):
|
|
qRaw := strings.TrimSpace(strings.TrimPrefix(arg, "--quality="))
|
|
q, err := strconv.Atoi(qRaw)
|
|
if err != nil || q < 0 || q > 4 {
|
|
return globalOptions{}, fmt.Errorf("invalid quality %q (expected 0-4)", qRaw)
|
|
}
|
|
opts.qualitySet = true
|
|
opts.quality = q
|
|
case arg == "-c" || arg == "--codec":
|
|
if i+1 >= len(args) {
|
|
return globalOptions{}, fmt.Errorf("%s requires a value", arg)
|
|
}
|
|
codec, err := normalizeCodec(args[i+1])
|
|
if err != nil {
|
|
return globalOptions{}, err
|
|
}
|
|
opts.codecSet = true
|
|
opts.codec = codec
|
|
i++
|
|
case strings.HasPrefix(arg, "--codec="):
|
|
codecRaw := strings.TrimSpace(strings.TrimPrefix(arg, "--codec="))
|
|
codec, err := normalizeCodec(codecRaw)
|
|
if err != nil {
|
|
return globalOptions{}, err
|
|
}
|
|
opts.codecSet = true
|
|
opts.codec = codec
|
|
default:
|
|
return globalOptions{}, fmt.Errorf("unknown global option %q", arg)
|
|
}
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
func normalizeCodec(raw string) (string, error) {
|
|
codec := strings.ToUpper(strings.TrimSpace(raw))
|
|
switch codec {
|
|
case "ALAC", "FLAC", "MP3", "AAC", "VORBIS":
|
|
return codec, nil
|
|
case "OGG":
|
|
return "VORBIS", nil
|
|
default:
|
|
return "", fmt.Errorf("unsupported codec %q (expected ALAC, FLAC, OGG, MP3, AAC)", raw)
|
|
}
|
|
}
|
|
|
|
func applyGlobalConfigOverrides(cfg *config.Config, opts globalOptions) {
|
|
if opts.folder != "" {
|
|
cfg.Session.Downloads.Folder = opts.folder
|
|
}
|
|
if opts.noDB {
|
|
cfg.Session.Database.DownloadsEnabled = false
|
|
}
|
|
if opts.qualitySet {
|
|
cfg.Session.Qobuz.Quality = opts.quality
|
|
cfg.Session.Tidal.Quality = opts.quality
|
|
cfg.Session.Deezer.Quality = opts.quality
|
|
cfg.Session.Soundcloud.Quality = opts.quality
|
|
}
|
|
if opts.codecSet {
|
|
cfg.Session.Conversion.Enabled = true
|
|
cfg.Session.Conversion.Codec = opts.codec
|
|
}
|
|
if opts.noProgress {
|
|
cfg.Session.CLI.ProgressBars = false
|
|
}
|
|
if opts.noSSLVerify {
|
|
cfg.Session.Downloads.VerifySSL = false
|
|
}
|
|
}
|
|
|
|
func errorWithActionableHint(err error, opts globalOptions) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
msg := err.Error()
|
|
if opts.noSSLVerify {
|
|
return msg
|
|
}
|
|
lower := strings.ToLower(msg)
|
|
if strings.Contains(lower, "x509") || strings.Contains(lower, "certificate") || strings.Contains(lower, "tls") || strings.Contains(lower, "ssl") {
|
|
return msg + " (hint: try again with --no-ssl-verify)"
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func parseSmokeOptions(args []string, minQuality int, maxQuality int) (smokeOptions, error) {
|
|
opts := smokeOptions{}
|
|
for _, arg := range args {
|
|
switch arg {
|
|
case "--force", "--ignore-db":
|
|
opts.ignoreDB = true
|
|
default:
|
|
q, err := parseQuality(arg, minQuality, maxQuality)
|
|
if err != nil {
|
|
return smokeOptions{}, fmt.Errorf("unknown option %q", arg)
|
|
}
|
|
opts.quality = q
|
|
opts.qualitySet = true
|
|
}
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
func parseQuality(raw string, min int, max int) (int, error) {
|
|
q, err := strconv.Atoi(raw)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if q < min || q > max {
|
|
return 0, fmt.Errorf("quality must be %d-%d, got %d", min, max, q)
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
func asString(v any) string {
|
|
switch t := v.(type) {
|
|
case string:
|
|
return t
|
|
case int:
|
|
return strconv.Itoa(t)
|
|
case int64:
|
|
return strconv.FormatInt(t, 10)
|
|
case float64:
|
|
return strconv.FormatFloat(t, 'f', -1, 64)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|