mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
implement native Deezer download/decrypt pipeline
Replace Deezer yt-dlp usage with native ARL session + media.get_url resolution, add BF_CBC_STRIPE decryption in downloader, and wire cipher-aware Deezer downloads through the main rip pipeline. Includes validation hardening and metadata/source-id improvements used by tagging flows.
This commit is contained in:
@@ -106,6 +106,7 @@ func (c *Client) searchTracks(ctx context.Context, query string, limit int) ([]m
|
||||
if artist == "" {
|
||||
artist = strings.TrimSpace(stringFromAny(m["channel"]))
|
||||
}
|
||||
artistID := strings.TrimSpace(firstNonEmpty(stringFromAny(m["uploader_id"]), stringFromAny(m["channel_id"])))
|
||||
item := map[string]any{
|
||||
"id": id,
|
||||
"title": stringFromAny(m["title"]),
|
||||
@@ -113,6 +114,9 @@ func (c *Client) searchTracks(ctx context.Context, query string, limit int) ([]m
|
||||
"name": artist,
|
||||
},
|
||||
}
|
||||
if artistID != "" {
|
||||
item["artist"] = map[string]any{"name": artist, "id": artistID}
|
||||
}
|
||||
if trackID := strings.TrimSpace(stringFromAny(m["id"])); trackID != "" {
|
||||
item["source_track_id"] = trackID
|
||||
}
|
||||
@@ -164,6 +168,7 @@ func (c *Client) searchPlaylists(ctx context.Context, query string, limit int) (
|
||||
title = strings.Trim(strings.ReplaceAll(path, "/", " "), " ")
|
||||
}
|
||||
artist := strings.TrimSpace(firstNonEmpty(stringFromAny(info["uploader"]), stringFromAny(info["channel"])))
|
||||
artistID := strings.TrimSpace(firstNonEmpty(stringFromAny(info["uploader_id"]), stringFromAny(info["channel_id"])))
|
||||
trackCount := 0
|
||||
if entries := asAnySlice(info["entries"]); len(entries) > 0 {
|
||||
trackCount = len(entries)
|
||||
@@ -175,11 +180,14 @@ func (c *Client) searchPlaylists(ctx context.Context, query string, limit int) (
|
||||
"tracks_count": trackCount,
|
||||
"artist": map[string]any{"name": artist},
|
||||
}
|
||||
if artistID != "" {
|
||||
item["artist"] = map[string]any{"name": artist, "id": artistID}
|
||||
}
|
||||
if pid := strings.TrimSpace(stringFromAny(info["id"])); pid != "" {
|
||||
item["source_playlist_id"] = pid
|
||||
}
|
||||
if thumb := strings.TrimSpace(stringFromAny(info["thumbnail"])); thumb != "" {
|
||||
item["image"] = map[string]any{"small": thumb, "large": thumb, "extralarge": thumb, "original": thumb}
|
||||
item["image"] = soundcloudImageMap(thumb)
|
||||
}
|
||||
items = append(items, item)
|
||||
if len(items) >= limit {
|
||||
@@ -227,7 +235,11 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s
|
||||
track["title"] = title
|
||||
}
|
||||
if artist := strings.TrimSpace(firstNonEmpty(stringFromAny(entry["uploader"]), stringFromAny(entry["channel"]))); artist != "" {
|
||||
track["artist"] = map[string]any{"name": artist}
|
||||
artistMap := map[string]any{"name": artist}
|
||||
if artistID := strings.TrimSpace(firstNonEmpty(stringFromAny(entry["uploader_id"]), stringFromAny(entry["channel_id"]))); artistID != "" {
|
||||
artistMap["id"] = artistID
|
||||
}
|
||||
track["artist"] = artistMap
|
||||
}
|
||||
track["track_number"] = i + 1
|
||||
tracks = append(tracks, track)
|
||||
@@ -249,7 +261,7 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s
|
||||
meta["artist"] = map[string]any{"name": artist}
|
||||
}
|
||||
if thumb := strings.TrimSpace(stringFromAny(root["thumbnail"])); thumb != "" {
|
||||
meta["image"] = map[string]any{"small": thumb, "large": thumb, "extralarge": thumb, "original": thumb}
|
||||
meta["image"] = soundcloudImageMap(thumb)
|
||||
}
|
||||
if entries := asAnySlice(root["entries"]); len(entries) > 0 {
|
||||
meta["tracks_count"] = len(entries)
|
||||
@@ -326,17 +338,33 @@ func (c *Client) playlistInfo(ctx context.Context, item string) (map[string]any,
|
||||
|
||||
func trackMetadataFromInfo(id string, info map[string]any) map[string]any {
|
||||
canonicalID := firstNonEmpty(canonicalSoundcloudURL(info), id)
|
||||
publisher := nestedMap(info, "publisher_metadata")
|
||||
title := strings.TrimSpace(stringFromAny(info["title"]))
|
||||
if title == "" {
|
||||
title = canonicalID
|
||||
}
|
||||
albumTitle := strings.TrimSpace(stringFromAny(publisher["album_title"]))
|
||||
if albumTitle == "" {
|
||||
albumTitle = strings.TrimSpace(stringFromAny(info["album"]))
|
||||
}
|
||||
if albumTitle == "" {
|
||||
albumTitle = title
|
||||
}
|
||||
artistName := strings.TrimSpace(stringFromAny(info["artist"]))
|
||||
if artistName == "" {
|
||||
artistName = strings.TrimSpace(stringFromAny(publisher["artist"]))
|
||||
}
|
||||
if artistName == "" {
|
||||
artistName = strings.TrimSpace(stringFromAny(info["uploader"]))
|
||||
}
|
||||
if artistName == "" {
|
||||
artistName = strings.TrimSpace(stringFromAny(info["channel"]))
|
||||
}
|
||||
artistID := strings.TrimSpace(firstNonEmpty(
|
||||
stringFromAny(info["uploader_id"]),
|
||||
stringFromAny(info["channel_id"]),
|
||||
stringFromAny(nestedMap(info, "user")["id"]),
|
||||
))
|
||||
|
||||
trackNum := intFromAny(info["track_number"])
|
||||
if trackNum <= 0 {
|
||||
@@ -347,18 +375,20 @@ func trackMetadataFromInfo(id string, info map[string]any) map[string]any {
|
||||
"id": canonicalID,
|
||||
"title": title,
|
||||
"track_number": trackNum,
|
||||
"artist": map[string]any{"name": artistName},
|
||||
"performer": map[string]any{"name": artistName},
|
||||
"artist": map[string]any{"name": artistName, "id": artistID},
|
||||
"performer": map[string]any{"name": artistName, "id": artistID},
|
||||
"album": map[string]any{
|
||||
"id": strings.TrimSpace(stringFromAny(info["album"])),
|
||||
"title": strings.TrimSpace(stringFromAny(info["album"])),
|
||||
"artist": map[string]any{"name": artistName},
|
||||
"id": firstNonEmpty(strings.TrimSpace(stringFromAny(info["album"])), canonicalID),
|
||||
"title": albumTitle,
|
||||
"artist": map[string]any{"name": artistName, "id": artistID},
|
||||
},
|
||||
"description": strings.TrimSpace(stringFromAny(info["description"])),
|
||||
"genre": strings.TrimSpace(stringFromAny(info["genre"])),
|
||||
"isrc": strings.TrimSpace(stringFromAny(info["isrc"])),
|
||||
"label": strings.TrimSpace(stringFromAny(info["label"])),
|
||||
"label": strings.TrimSpace(firstNonEmpty(stringFromAny(info["label"]), stringFromAny(info["label_name"]))),
|
||||
"copyright": strings.TrimSpace(stringFromAny(publisher["p_line"])),
|
||||
"release_date": strings.TrimSpace(firstNonEmpty(
|
||||
stringFromAny(info["created_at"]),
|
||||
stringFromAny(info["release_date"]),
|
||||
stringFromAny(info["upload_date"]),
|
||||
)),
|
||||
@@ -367,7 +397,7 @@ func trackMetadataFromInfo(id string, info map[string]any) map[string]any {
|
||||
meta["source_track_id"] = trackID
|
||||
}
|
||||
|
||||
if age := intFromAny(info["age_limit"]); age >= 18 {
|
||||
if boolFromAny(publisher["explicit"]) || intFromAny(info["age_limit"]) >= 18 {
|
||||
meta["explicit"] = true
|
||||
}
|
||||
|
||||
@@ -376,19 +406,14 @@ func trackMetadataFromInfo(id string, info map[string]any) map[string]any {
|
||||
}
|
||||
|
||||
if thumb := strings.TrimSpace(stringFromAny(info["thumbnail"])); thumb != "" {
|
||||
meta["image"] = map[string]any{
|
||||
"small": thumb,
|
||||
"large": thumb,
|
||||
"extralarge": thumb,
|
||||
"original": thumb,
|
||||
}
|
||||
meta["image"] = soundcloudImageMap(thumb)
|
||||
}
|
||||
|
||||
if album := strings.TrimSpace(stringFromAny(info["album"])); album == "" {
|
||||
if strings.TrimSpace(stringFromAny(info["album"])) == "" && strings.TrimSpace(stringFromAny(publisher["album_title"])) == "" {
|
||||
meta["album"] = map[string]any{
|
||||
"id": id,
|
||||
"id": canonicalID,
|
||||
"title": title,
|
||||
"artist": map[string]any{"name": artistName},
|
||||
"artist": map[string]any{"name": artistName, "id": artistID},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,6 +517,49 @@ func firstNonEmpty(items ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func nestedMap(m map[string]any, key string) map[string]any {
|
||||
v, ok := m[key].(map[string]any)
|
||||
if !ok {
|
||||
return map[string]any{}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func boolFromAny(v any) bool {
|
||||
switch t := v.(type) {
|
||||
case bool:
|
||||
return t
|
||||
case string:
|
||||
l := strings.ToLower(strings.TrimSpace(t))
|
||||
return l == "1" || l == "true" || l == "yes"
|
||||
case int:
|
||||
return t != 0
|
||||
case int64:
|
||||
return t != 0
|
||||
case float64:
|
||||
return t != 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func soundcloudImageMap(raw string) map[string]any {
|
||||
base := strings.TrimSpace(raw)
|
||||
if base == "" {
|
||||
return map[string]any{}
|
||||
}
|
||||
large := strings.Replace(base, "-large.", "-t500x500.", 1)
|
||||
if large == base {
|
||||
large = strings.Replace(base, "large", "t500x500", 1)
|
||||
}
|
||||
return map[string]any{
|
||||
"small": base,
|
||||
"large": large,
|
||||
"extralarge": large,
|
||||
"original": large,
|
||||
}
|
||||
}
|
||||
|
||||
func runCommand(ctx context.Context, name string, args ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
b, err := cmd.CombinedOutput()
|
||||
|
||||
Reference in New Issue
Block a user