mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-26 05:51:18 +02:00
refactor(backend): split extension_providers.go into themed files
Pure mechanical move within package gobackend — no signature or behavior
change. extension_providers.go (3,798 lines) now holds only the
extensionManager provider-listing/orchestration methods; DTOs, goja value
conversion, the provider wrapper methods, download fallback, and priority
state moved to extension_{provider_types,goja_convert,provider_wrapper,
fallback,priority}.go.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,678 @@
|
||||
package gobackend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func gojaValueIsEmpty(value goja.Value) bool {
|
||||
return value == nil || goja.IsUndefined(value) || goja.IsNull(value)
|
||||
}
|
||||
|
||||
func gojaObjectString(obj *goja.Object, keys ...string) string {
|
||||
for _, key := range keys {
|
||||
value := obj.Get(key)
|
||||
if gojaValueIsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
if str, ok := value.Export().(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func gojaObjectValue(obj *goja.Object, keys ...string) goja.Value {
|
||||
for _, key := range keys {
|
||||
value := obj.Get(key)
|
||||
if !gojaValueIsEmpty(value) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func gojaObjectInt(obj *goja.Object, keys ...string) int {
|
||||
for _, key := range keys {
|
||||
value := obj.Get(key)
|
||||
if gojaValueIsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
return int(value.ToInteger())
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func gojaObjectInt64(obj *goja.Object, keys ...string) int64 {
|
||||
for _, key := range keys {
|
||||
value := obj.Get(key)
|
||||
if gojaValueIsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
return value.ToInteger()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func gojaObjectFloat(obj *goja.Object, keys ...string) float64 {
|
||||
for _, key := range keys {
|
||||
value := obj.Get(key)
|
||||
if gojaValueIsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
return value.ToFloat()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func gojaObjectBool(obj *goja.Object, keys ...string) bool {
|
||||
for _, key := range keys {
|
||||
value := obj.Get(key)
|
||||
if gojaValueIsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
return value.ToBoolean()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func gojaObjectInterfaceMap(obj *goja.Object, keys ...string) map[string]interface{} {
|
||||
value := gojaObjectValue(obj, keys...)
|
||||
if gojaValueIsEmpty(value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
exported, ok := value.Export().(map[string]interface{})
|
||||
if !ok || len(exported) == 0 {
|
||||
return nil
|
||||
}
|
||||
return exported
|
||||
}
|
||||
|
||||
func gojaObjectStringMap(vm *goja.Runtime, obj *goja.Object, keys ...string) map[string]string {
|
||||
value := gojaObjectValue(obj, keys...)
|
||||
if gojaValueIsEmpty(value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
valueObj := value.ToObject(vm)
|
||||
objectKeys := valueObj.Keys()
|
||||
if len(objectKeys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(map[string]string, len(objectKeys))
|
||||
for _, childKey := range objectKeys {
|
||||
childValue := valueObj.Get(childKey)
|
||||
if gojaValueIsEmpty(childValue) {
|
||||
continue
|
||||
}
|
||||
result[childKey] = childValue.String()
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func gojaObjectStringSlice(obj *goja.Object, keys ...string) []string {
|
||||
value := gojaObjectValue(obj, keys...)
|
||||
if gojaValueIsEmpty(value) {
|
||||
return nil
|
||||
}
|
||||
exported, ok := value.Export().([]interface{})
|
||||
if !ok || len(exported) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]string, 0, len(exported))
|
||||
for _, item := range exported {
|
||||
str, ok := item.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
str = strings.TrimSpace(str)
|
||||
if str != "" {
|
||||
result = append(result, str)
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func gojaArrayLength(value goja.Value, vm *goja.Runtime) (int, error) {
|
||||
if gojaValueIsEmpty(value) {
|
||||
return 0, nil
|
||||
}
|
||||
lengthValue := value.ToObject(vm).Get("length")
|
||||
if gojaValueIsEmpty(lengthValue) {
|
||||
return 0, fmt.Errorf("value is not an array")
|
||||
}
|
||||
length := lengthValue.ToInteger()
|
||||
if length <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return int(length), nil
|
||||
}
|
||||
|
||||
func parseExtensionTrackValue(vm *goja.Runtime, value goja.Value) ExtTrackMetadata {
|
||||
obj := value.ToObject(vm)
|
||||
return ExtTrackMetadata{
|
||||
ID: gojaObjectString(obj, "id"),
|
||||
Name: gojaObjectString(obj, "name"),
|
||||
Artists: gojaObjectString(obj, "artists"),
|
||||
AlbumName: gojaObjectString(obj, "album_name", "albumName"),
|
||||
AlbumArtist: gojaObjectString(obj, "album_artist", "albumArtist"),
|
||||
AlbumID: gojaObjectString(obj, "album_id", "albumId"),
|
||||
AlbumURL: gojaObjectString(obj, "album_url", "albumUrl"),
|
||||
ArtistID: gojaObjectString(obj, "artist_id", "artistId"),
|
||||
ArtistURL: gojaObjectString(obj, "artist_url", "artistUrl"),
|
||||
ExternalURL: gojaObjectString(obj, "external_urls", "externalUrls", "external_url", "externalUrl", "url"),
|
||||
DurationMS: gojaObjectInt(obj, "duration_ms", "durationMs"),
|
||||
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl"),
|
||||
PreviewURL: gojaObjectString(obj, "preview_url", "previewUrl"),
|
||||
Images: gojaObjectString(obj, "images"),
|
||||
ReleaseDate: gojaObjectString(obj, "release_date", "releaseDate"),
|
||||
TrackNumber: gojaObjectInt(obj, "track_number", "trackNumber"),
|
||||
TotalTracks: gojaObjectInt(obj, "total_tracks", "totalTracks"),
|
||||
DiscNumber: gojaObjectInt(obj, "disc_number", "discNumber"),
|
||||
TotalDiscs: gojaObjectInt(obj, "total_discs", "totalDiscs"),
|
||||
ISRC: gojaObjectString(obj, "isrc"),
|
||||
ProviderID: gojaObjectString(obj, "provider_id", "providerId"),
|
||||
ItemType: gojaObjectString(obj, "item_type", "itemType"),
|
||||
AlbumType: gojaObjectString(obj, "album_type", "albumType"),
|
||||
Explicit: gojaObjectBool(obj, "explicit", "is_explicit", "isExplicit"),
|
||||
TidalID: gojaObjectString(obj, "tidal_id", "tidalId"),
|
||||
QobuzID: gojaObjectString(obj, "qobuz_id", "qobuzId"),
|
||||
DeezerID: gojaObjectString(obj, "deezer_id", "deezerId"),
|
||||
SpotifyID: gojaObjectString(obj, "spotify_id", "spotifyId"),
|
||||
ExternalLinks: gojaObjectStringMap(vm, obj, "external_links", "externalLinks"),
|
||||
Label: gojaObjectString(obj, "label"),
|
||||
Copyright: gojaObjectString(obj, "copyright"),
|
||||
Genre: gojaObjectString(obj, "genre"),
|
||||
Composer: gojaObjectString(obj, "composer"),
|
||||
AudioQuality: gojaObjectString(obj, "audio_quality", "audioQuality"),
|
||||
AudioModes: gojaObjectString(obj, "audio_modes", "audioModes"),
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtensionTrackArray(vm *goja.Runtime, value goja.Value) ([]ExtTrackMetadata, error) {
|
||||
length, err := gojaArrayLength(value, vm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length == 0 {
|
||||
return []ExtTrackMetadata{}, nil
|
||||
}
|
||||
|
||||
arrayObj := value.ToObject(vm)
|
||||
tracks := make([]ExtTrackMetadata, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
trackValue := arrayObj.Get(strconv.Itoa(i))
|
||||
if gojaValueIsEmpty(trackValue) {
|
||||
continue
|
||||
}
|
||||
tracks = append(tracks, parseExtensionTrackValue(vm, trackValue))
|
||||
}
|
||||
return tracks, nil
|
||||
}
|
||||
|
||||
func parseExtensionAlbumValue(vm *goja.Runtime, value goja.Value) (ExtAlbumMetadata, error) {
|
||||
if gojaValueIsEmpty(value) {
|
||||
return ExtAlbumMetadata{}, nil
|
||||
}
|
||||
|
||||
obj := value.ToObject(vm)
|
||||
tracks := []ExtTrackMetadata{}
|
||||
if tracksValue := gojaObjectValue(obj, "tracks"); !gojaValueIsEmpty(tracksValue) {
|
||||
parsedTracks, err := parseExtensionTrackArray(vm, tracksValue)
|
||||
if err != nil {
|
||||
return ExtAlbumMetadata{}, err
|
||||
}
|
||||
tracks = parsedTracks
|
||||
}
|
||||
|
||||
return ExtAlbumMetadata{
|
||||
ID: gojaObjectString(obj, "id"),
|
||||
Name: gojaObjectString(obj, "name"),
|
||||
Artists: gojaObjectString(obj, "artists"),
|
||||
ArtistID: gojaObjectString(obj, "artist_id", "artistId"),
|
||||
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl", "images"),
|
||||
HeaderImage: gojaObjectString(obj, "header_image", "headerImage"),
|
||||
HeaderVideo: gojaObjectString(obj, "header_video", "headerVideo"),
|
||||
ReleaseDate: gojaObjectString(obj, "release_date", "releaseDate"),
|
||||
TotalTracks: gojaObjectInt(obj, "total_tracks", "totalTracks"),
|
||||
AlbumType: gojaObjectString(obj, "album_type", "albumType"),
|
||||
AudioTraits: gojaObjectStringSlice(obj, "audio_traits", "audioTraits"),
|
||||
Tracks: tracks,
|
||||
ProviderID: gojaObjectString(obj, "provider_id", "providerId"),
|
||||
}.withTrackFallbacks(), nil
|
||||
}
|
||||
|
||||
// withTrackFallbacks fills the album-level artist and release date from the
|
||||
// album's tracks when the extension did not provide them at the album level.
|
||||
// This is a generic mechanism so any extension benefits, without per-extension
|
||||
// special-casing in the app.
|
||||
func (a ExtAlbumMetadata) withTrackFallbacks() ExtAlbumMetadata {
|
||||
if strings.TrimSpace(a.Artists) == "" {
|
||||
a.Artists = albumArtistFromTracks(a.Tracks)
|
||||
}
|
||||
if strings.TrimSpace(a.ReleaseDate) == "" {
|
||||
a.ReleaseDate = albumReleaseDateFromTracks(a.Tracks)
|
||||
}
|
||||
if len(a.AudioTraits) == 0 {
|
||||
a.AudioTraits = albumAudioTraitsFromTracks(a.Tracks)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// albumArtistFromTracks prefers an explicit per-track album artist, then falls
|
||||
// back to the most common track artist across the album.
|
||||
func albumArtistFromTracks(tracks []ExtTrackMetadata) string {
|
||||
for _, t := range tracks {
|
||||
if s := strings.TrimSpace(t.AlbumArtist); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
counts := map[string]int{}
|
||||
order := []string{}
|
||||
for _, t := range tracks {
|
||||
artist := strings.TrimSpace(t.Artists)
|
||||
if artist == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := counts[artist]; !ok {
|
||||
order = append(order, artist)
|
||||
}
|
||||
counts[artist]++
|
||||
}
|
||||
best := ""
|
||||
bestCount := 0
|
||||
for _, artist := range order {
|
||||
if counts[artist] > bestCount {
|
||||
best = artist
|
||||
bestCount = counts[artist]
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// albumReleaseDateFromTracks returns the first non-empty track release date.
|
||||
func albumReleaseDateFromTracks(tracks []ExtTrackMetadata) string {
|
||||
for _, t := range tracks {
|
||||
if s := strings.TrimSpace(t.ReleaseDate); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// albumAudioTraitsFromTracks derives album-level audio badges (Dolby Atmos,
|
||||
// Hi-Res Lossless, Lossless) from the per-track audio quality/mode fields that
|
||||
// extensions like Tidal and Qobuz already provide. Tokens match what the album
|
||||
// header understands ("dolby_atmos", "hi_res_lossless", "lossless").
|
||||
func albumAudioTraitsFromTracks(tracks []ExtTrackMetadata) []string {
|
||||
atmos := false
|
||||
hiRes := false
|
||||
lossless := false
|
||||
|
||||
for _, t := range tracks {
|
||||
modes := strings.ToUpper(t.AudioModes)
|
||||
quality := strings.ToUpper(t.AudioQuality)
|
||||
if strings.Contains(modes, "ATMOS") || strings.Contains(quality, "ATMOS") {
|
||||
atmos = true
|
||||
}
|
||||
if strings.Contains(quality, "HI_RES") ||
|
||||
strings.Contains(quality, "HIRES") ||
|
||||
strings.Contains(quality, "MASTER") ||
|
||||
strings.Contains(quality, "MQA") {
|
||||
hiRes = true
|
||||
}
|
||||
if strings.Contains(quality, "LOSSLESS") ||
|
||||
strings.Contains(quality, "FLAC") {
|
||||
lossless = true
|
||||
}
|
||||
if bd, sr := parseBitDepthSampleRate(quality); bd > 0 {
|
||||
if bd > 16 || sr > 48 {
|
||||
hiRes = true
|
||||
} else {
|
||||
lossless = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traits := []string{}
|
||||
if atmos {
|
||||
traits = append(traits, "dolby_atmos")
|
||||
}
|
||||
if hiRes {
|
||||
traits = append(traits, "hi_res_lossless")
|
||||
} else if lossless {
|
||||
traits = append(traits, "lossless")
|
||||
}
|
||||
return traits
|
||||
}
|
||||
|
||||
// parseBitDepthSampleRate extracts a bit depth and sample rate (in kHz) from
|
||||
// labels such as "24bit/96kHz", "16bit/44.1kHz" or "24bit".
|
||||
func parseBitDepthSampleRate(quality string) (int, float64) {
|
||||
lower := strings.ToLower(quality)
|
||||
bitDepth := 0
|
||||
sampleRate := 0.0
|
||||
|
||||
if idx := strings.Index(lower, "bit"); idx > 0 {
|
||||
j := idx
|
||||
for j > 0 && lower[j-1] >= '0' && lower[j-1] <= '9' {
|
||||
j--
|
||||
}
|
||||
if n, err := strconv.Atoi(lower[j:idx]); err == nil {
|
||||
bitDepth = n
|
||||
}
|
||||
}
|
||||
if idx := strings.Index(lower, "khz"); idx > 0 {
|
||||
j := idx
|
||||
for j > 0 && ((lower[j-1] >= '0' && lower[j-1] <= '9') || lower[j-1] == '.') {
|
||||
j--
|
||||
}
|
||||
if f, err := strconv.ParseFloat(lower[j:idx], 64); err == nil {
|
||||
sampleRate = f
|
||||
}
|
||||
}
|
||||
return bitDepth, sampleRate
|
||||
}
|
||||
|
||||
func parseExtensionAlbumArray(vm *goja.Runtime, value goja.Value) ([]ExtAlbumMetadata, error) {
|
||||
length, err := gojaArrayLength(value, vm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length == 0 {
|
||||
return []ExtAlbumMetadata{}, nil
|
||||
}
|
||||
|
||||
arrayObj := value.ToObject(vm)
|
||||
albums := make([]ExtAlbumMetadata, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
albumValue := arrayObj.Get(strconv.Itoa(i))
|
||||
if gojaValueIsEmpty(albumValue) {
|
||||
continue
|
||||
}
|
||||
album, err := parseExtensionAlbumValue(vm, albumValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
albums = append(albums, album)
|
||||
}
|
||||
return albums, nil
|
||||
}
|
||||
|
||||
func parseExtensionArtistValue(vm *goja.Runtime, value goja.Value) (ExtArtistMetadata, error) {
|
||||
if gojaValueIsEmpty(value) {
|
||||
return ExtArtistMetadata{}, nil
|
||||
}
|
||||
|
||||
obj := value.ToObject(vm)
|
||||
albums := []ExtAlbumMetadata{}
|
||||
if albumsValue := gojaObjectValue(obj, "albums"); !gojaValueIsEmpty(albumsValue) {
|
||||
parsedAlbums, err := parseExtensionAlbumArray(vm, albumsValue)
|
||||
if err != nil {
|
||||
return ExtArtistMetadata{}, err
|
||||
}
|
||||
albums = parsedAlbums
|
||||
}
|
||||
|
||||
releases := []ExtAlbumMetadata{}
|
||||
if releasesValue := gojaObjectValue(obj, "releases"); !gojaValueIsEmpty(releasesValue) {
|
||||
parsedReleases, err := parseExtensionAlbumArray(vm, releasesValue)
|
||||
if err != nil {
|
||||
return ExtArtistMetadata{}, err
|
||||
}
|
||||
releases = parsedReleases
|
||||
}
|
||||
|
||||
topTracks := []ExtTrackMetadata{}
|
||||
if topTracksValue := gojaObjectValue(obj, "top_tracks", "topTracks"); !gojaValueIsEmpty(topTracksValue) {
|
||||
parsedTopTracks, err := parseExtensionTrackArray(vm, topTracksValue)
|
||||
if err != nil {
|
||||
return ExtArtistMetadata{}, err
|
||||
}
|
||||
topTracks = parsedTopTracks
|
||||
}
|
||||
|
||||
return ExtArtistMetadata{
|
||||
ID: gojaObjectString(obj, "id"),
|
||||
Name: gojaObjectString(obj, "name"),
|
||||
ImageURL: gojaObjectString(obj, "image_url", "imageUrl"),
|
||||
HeaderImage: gojaObjectString(obj, "header_image", "headerImage"),
|
||||
HeaderVideo: gojaObjectString(obj, "header_video", "headerVideo"),
|
||||
Listeners: gojaObjectInt(obj, "listeners"),
|
||||
Albums: albums,
|
||||
Releases: releases,
|
||||
TopTracks: topTracks,
|
||||
ProviderID: gojaObjectString(obj, "provider_id", "providerId"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseExtensionAvailabilityValue(vm *goja.Runtime, value goja.Value) ExtAvailabilityResult {
|
||||
obj := value.ToObject(vm)
|
||||
return ExtAvailabilityResult{
|
||||
Available: gojaObjectBool(obj, "available"),
|
||||
Reason: gojaObjectString(obj, "reason"),
|
||||
TrackID: gojaObjectString(obj, "track_id", "trackId"),
|
||||
SkipFallback: gojaObjectBool(obj, "skip_fallback", "skipFallback"),
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtensionDownloadURLValue(vm *goja.Runtime, value goja.Value) ExtDownloadURLResult {
|
||||
obj := value.ToObject(vm)
|
||||
return ExtDownloadURLResult{
|
||||
URL: gojaObjectString(obj, "url"),
|
||||
Format: gojaObjectString(obj, "format"),
|
||||
BitDepth: gojaObjectInt(obj, "bit_depth", "bitDepth"),
|
||||
SampleRate: gojaObjectInt(obj, "sample_rate", "sampleRate"),
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtensionDownloadDecryptionValue(vm *goja.Runtime, value goja.Value) *DownloadDecryptionInfo {
|
||||
if gojaValueIsEmpty(value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
obj := value.ToObject(vm)
|
||||
info := &DownloadDecryptionInfo{
|
||||
Strategy: gojaObjectString(obj, "strategy"),
|
||||
Key: gojaObjectString(obj, "key"),
|
||||
IV: gojaObjectString(obj, "iv"),
|
||||
InputFormat: gojaObjectString(obj, "input_format", "inputFormat"),
|
||||
OutputExtension: gojaObjectString(obj, "output_extension", "outputExtension"),
|
||||
Options: gojaObjectInterfaceMap(obj, "options"),
|
||||
}
|
||||
if info.Strategy == "" && info.Key == "" && info.IV == "" && info.InputFormat == "" && info.OutputExtension == "" && len(info.Options) == 0 {
|
||||
return nil
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func parseExtensionDownloadResultValue(vm *goja.Runtime, value goja.Value) ExtDownloadResult {
|
||||
obj := value.ToObject(vm)
|
||||
return ExtDownloadResult{
|
||||
Success: gojaObjectBool(obj, "success"),
|
||||
FilePath: gojaObjectString(obj, "file_path", "filePath", "path"),
|
||||
AlreadyExists: gojaObjectBool(obj, "already_exists", "alreadyExists"),
|
||||
BitDepth: gojaObjectInt(obj, "bit_depth", "bitDepth"),
|
||||
SampleRate: gojaObjectInt(obj, "sample_rate", "sampleRate"),
|
||||
AudioCodec: gojaObjectString(obj, "audio_codec", "audioCodec", "codec"),
|
||||
ErrorMessage: gojaObjectString(obj, "error_message", "errorMessage", "error"),
|
||||
ErrorType: gojaObjectString(obj, "error_type", "errorType"),
|
||||
RetryAfterSeconds: gojaObjectInt(obj, "retry_after_seconds", "retryAfterSeconds"),
|
||||
Title: gojaObjectString(obj, "title"),
|
||||
Artist: gojaObjectString(obj, "artist"),
|
||||
Album: gojaObjectString(obj, "album"),
|
||||
AlbumArtist: gojaObjectString(obj, "album_artist", "albumArtist"),
|
||||
TrackNumber: gojaObjectInt(obj, "track_number", "trackNumber"),
|
||||
DiscNumber: gojaObjectInt(obj, "disc_number", "discNumber"),
|
||||
TotalTracks: gojaObjectInt(obj, "total_tracks", "totalTracks"),
|
||||
TotalDiscs: gojaObjectInt(obj, "total_discs", "totalDiscs"),
|
||||
ReleaseDate: gojaObjectString(obj, "release_date", "releaseDate"),
|
||||
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl"),
|
||||
ISRC: gojaObjectString(obj, "isrc"),
|
||||
Genre: gojaObjectString(obj, "genre"),
|
||||
Label: gojaObjectString(obj, "label"),
|
||||
Copyright: gojaObjectString(obj, "copyright"),
|
||||
Composer: gojaObjectString(obj, "composer"),
|
||||
LyricsLRC: gojaObjectString(obj, "lyrics_lrc", "lyricsLrc"),
|
||||
DecryptionKey: gojaObjectString(obj, "decryption_key", "decryptionKey"),
|
||||
Decryption: parseExtensionDownloadDecryptionValue(vm, gojaObjectValue(obj, "decryption")),
|
||||
ActualExtension: gojaObjectString(obj, "actual_extension", "actualExtension"),
|
||||
OutputExtension: gojaObjectString(obj, "output_extension", "outputExtension"),
|
||||
ActualContainer: gojaObjectString(obj, "actual_container", "actualContainer", "container"),
|
||||
RequiresContainerConversion: gojaObjectBool(
|
||||
obj,
|
||||
"requires_container_conversion",
|
||||
"requiresContainerConversion",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtensionURLHandleValue(vm *goja.Runtime, value goja.Value) (ExtURLHandleResult, error) {
|
||||
obj := value.ToObject(vm)
|
||||
handleResult := ExtURLHandleResult{
|
||||
Type: gojaObjectString(obj, "type"),
|
||||
Name: gojaObjectString(obj, "name"),
|
||||
CoverURL: gojaObjectString(obj, "cover_url", "coverUrl"),
|
||||
HeaderImage: gojaObjectString(obj, "header_image", "headerImage"),
|
||||
HeaderVideo: gojaObjectString(obj, "header_video", "headerVideo"),
|
||||
}
|
||||
|
||||
if trackValue := gojaObjectValue(obj, "track"); !gojaValueIsEmpty(trackValue) {
|
||||
track := parseExtensionTrackValue(vm, trackValue)
|
||||
handleResult.Track = &track
|
||||
}
|
||||
if tracksValue := gojaObjectValue(obj, "tracks"); !gojaValueIsEmpty(tracksValue) {
|
||||
tracks, err := parseExtensionTrackArray(vm, tracksValue)
|
||||
if err != nil {
|
||||
return ExtURLHandleResult{}, err
|
||||
}
|
||||
handleResult.Tracks = tracks
|
||||
}
|
||||
if albumValue := gojaObjectValue(obj, "album"); !gojaValueIsEmpty(albumValue) {
|
||||
album, err := parseExtensionAlbumValue(vm, albumValue)
|
||||
if err != nil {
|
||||
return ExtURLHandleResult{}, err
|
||||
}
|
||||
handleResult.Album = &album
|
||||
}
|
||||
if artistValue := gojaObjectValue(obj, "artist"); !gojaValueIsEmpty(artistValue) {
|
||||
artist, err := parseExtensionArtistValue(vm, artistValue)
|
||||
if err != nil {
|
||||
return ExtURLHandleResult{}, err
|
||||
}
|
||||
handleResult.Artist = &artist
|
||||
}
|
||||
|
||||
return handleResult, nil
|
||||
}
|
||||
|
||||
func parseExtensionMatchTrackValue(vm *goja.Runtime, value goja.Value) MatchTrackResult {
|
||||
obj := value.ToObject(vm)
|
||||
return MatchTrackResult{
|
||||
Matched: gojaObjectBool(obj, "matched"),
|
||||
TrackID: gojaObjectString(obj, "track_id", "trackId"),
|
||||
Confidence: gojaObjectFloat(obj, "confidence"),
|
||||
Reason: gojaObjectString(obj, "reason"),
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtensionPostProcessValue(vm *goja.Runtime, value goja.Value) PostProcessResult {
|
||||
obj := value.ToObject(vm)
|
||||
return PostProcessResult{
|
||||
Success: gojaObjectBool(obj, "success"),
|
||||
NewFilePath: gojaObjectString(obj, "new_file_path", "newFilePath"),
|
||||
NewFileURI: gojaObjectString(obj, "new_file_uri", "newFileUri"),
|
||||
Error: gojaObjectString(obj, "error"),
|
||||
BitDepth: gojaObjectInt(obj, "bit_depth", "bitDepth"),
|
||||
SampleRate: gojaObjectInt(obj, "sample_rate", "sampleRate"),
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtensionLyricsLineArray(vm *goja.Runtime, value goja.Value) ([]ExtLyricsLine, error) {
|
||||
length, err := gojaArrayLength(value, vm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length == 0 {
|
||||
return []ExtLyricsLine{}, nil
|
||||
}
|
||||
|
||||
arrayObj := value.ToObject(vm)
|
||||
lines := make([]ExtLyricsLine, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
lineValue := arrayObj.Get(strconv.Itoa(i))
|
||||
if gojaValueIsEmpty(lineValue) {
|
||||
continue
|
||||
}
|
||||
lineObj := lineValue.ToObject(vm)
|
||||
lines = append(lines, ExtLyricsLine{
|
||||
StartTimeMs: gojaObjectInt64(lineObj, "startTimeMs", "start_time_ms"),
|
||||
Words: gojaObjectString(lineObj, "words"),
|
||||
EndTimeMs: gojaObjectInt64(lineObj, "endTimeMs", "end_time_ms"),
|
||||
})
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func parseExtensionLyricsValue(vm *goja.Runtime, value goja.Value) (ExtLyricsResult, error) {
|
||||
obj := value.ToObject(vm)
|
||||
lines := []ExtLyricsLine{}
|
||||
if linesValue := gojaObjectValue(obj, "lines"); !gojaValueIsEmpty(linesValue) {
|
||||
parsedLines, err := parseExtensionLyricsLineArray(vm, linesValue)
|
||||
if err != nil {
|
||||
return ExtLyricsResult{}, err
|
||||
}
|
||||
lines = parsedLines
|
||||
}
|
||||
|
||||
return ExtLyricsResult{
|
||||
Lines: lines,
|
||||
SyncType: gojaObjectString(obj, "syncType", "sync_type"),
|
||||
Instrumental: gojaObjectBool(obj, "instrumental"),
|
||||
PlainLyrics: gojaObjectString(obj, "plainLyrics", "plain_lyrics"),
|
||||
Provider: gojaObjectString(obj, "provider"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseExtensionSearchResult(vm *goja.Runtime, value goja.Value) (ExtSearchResult, error) {
|
||||
if gojaValueIsEmpty(value) {
|
||||
return ExtSearchResult{}, nil
|
||||
}
|
||||
|
||||
resultObj := value.ToObject(vm)
|
||||
tracksValue := resultObj.Get("tracks")
|
||||
if gojaValueIsEmpty(tracksValue) {
|
||||
tracks, err := parseExtensionTrackArray(vm, value)
|
||||
if err != nil {
|
||||
return ExtSearchResult{}, err
|
||||
}
|
||||
return ExtSearchResult{
|
||||
Tracks: tracks,
|
||||
Total: len(tracks),
|
||||
}, nil
|
||||
}
|
||||
|
||||
tracks, err := parseExtensionTrackArray(vm, tracksValue)
|
||||
if err != nil {
|
||||
return ExtSearchResult{}, err
|
||||
}
|
||||
total := gojaObjectInt(resultObj, "total")
|
||||
if total == 0 {
|
||||
total = len(tracks)
|
||||
}
|
||||
return ExtSearchResult{
|
||||
Tracks: tracks,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package gobackend
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var providerPriority []string
|
||||
var providerPriorityMu sync.RWMutex
|
||||
|
||||
var extensionFallbackProviderIDs []string
|
||||
var extensionFallbackProviderIDsMu sync.RWMutex
|
||||
|
||||
var metadataProviderPriority []string
|
||||
var metadataProviderPriorityMu sync.RWMutex
|
||||
|
||||
func SetProviderPriority(providerIDs []string) {
|
||||
providerPriorityMu.Lock()
|
||||
defer providerPriorityMu.Unlock()
|
||||
providerPriority = sanitizeDownloadProviderPriority(providerIDs)
|
||||
GoLog("[Extension] Download provider priority set: %v\n", providerPriority)
|
||||
}
|
||||
|
||||
func GetProviderPriority() []string {
|
||||
providerPriorityMu.RLock()
|
||||
defer providerPriorityMu.RUnlock()
|
||||
|
||||
if len(providerPriority) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
result := make([]string, len(providerPriority))
|
||||
copy(result, providerPriority)
|
||||
return result
|
||||
}
|
||||
|
||||
func sanitizeDownloadProviderPriority(providerIDs []string) []string {
|
||||
sanitized := make([]string, 0, len(providerIDs))
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
for _, providerID := range providerIDs {
|
||||
providerID = strings.TrimSpace(providerID)
|
||||
if providerID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if isRetiredBuiltInDownloadProvider(providerID) {
|
||||
continue
|
||||
}
|
||||
|
||||
seenKey := strings.ToLower(providerID)
|
||||
if _, exists := seen[seenKey]; exists {
|
||||
continue
|
||||
}
|
||||
seen[seenKey] = struct{}{}
|
||||
sanitized = append(sanitized, providerID)
|
||||
}
|
||||
|
||||
return sanitized
|
||||
}
|
||||
|
||||
func isRetiredBuiltInDownloadProvider(providerID string) bool {
|
||||
normalized := strings.ToLower(strings.TrimSpace(providerID))
|
||||
if normalized == "" {
|
||||
return false
|
||||
}
|
||||
switch normalized {
|
||||
case "deezer", "qobuz", "tidal":
|
||||
return !hasEnabledExtensionProvider(normalized, func(manifest *ExtensionManifest) bool {
|
||||
return manifest.IsDownloadProvider()
|
||||
})
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isRetiredBuiltInMetadataProvider(providerID string) bool {
|
||||
normalized := strings.ToLower(strings.TrimSpace(providerID))
|
||||
if normalized == "" {
|
||||
return false
|
||||
}
|
||||
switch normalized {
|
||||
case "deezer", "spotify", "qobuz", "tidal":
|
||||
return !hasEnabledExtensionProvider(normalized, func(manifest *ExtensionManifest) bool {
|
||||
return manifest.IsMetadataProvider()
|
||||
})
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hasEnabledExtensionProvider(providerID string, matches func(*ExtensionManifest) bool) bool {
|
||||
if providerID == "" || matches == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
manager := getExtensionManager()
|
||||
manager.mu.RLock()
|
||||
defer manager.mu.RUnlock()
|
||||
|
||||
for id, ext := range manager.extensions {
|
||||
if !strings.EqualFold(strings.TrimSpace(id), providerID) {
|
||||
continue
|
||||
}
|
||||
if ext == nil || !ext.Enabled || ext.Error != "" || ext.Manifest == nil {
|
||||
return false
|
||||
}
|
||||
return matches(ext.Manifest)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func SetExtensionFallbackProviderIDs(providerIDs []string) {
|
||||
extensionFallbackProviderIDsMu.Lock()
|
||||
defer extensionFallbackProviderIDsMu.Unlock()
|
||||
|
||||
if providerIDs == nil {
|
||||
extensionFallbackProviderIDs = nil
|
||||
GoLog("[Extension] Extension fallback providers reset to default (all enabled download extensions)\n")
|
||||
return
|
||||
}
|
||||
|
||||
sanitized := make([]string, 0, len(providerIDs))
|
||||
seen := map[string]struct{}{}
|
||||
for _, providerID := range providerIDs {
|
||||
providerID = strings.TrimSpace(providerID)
|
||||
if providerID == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[providerID]; exists {
|
||||
continue
|
||||
}
|
||||
seen[providerID] = struct{}{}
|
||||
sanitized = append(sanitized, providerID)
|
||||
}
|
||||
|
||||
extensionFallbackProviderIDs = sanitized
|
||||
GoLog("[Extension] Extension fallback providers set: %v\n", sanitized)
|
||||
}
|
||||
|
||||
func GetExtensionFallbackProviderIDs() []string {
|
||||
extensionFallbackProviderIDsMu.RLock()
|
||||
defer extensionFallbackProviderIDsMu.RUnlock()
|
||||
|
||||
if extensionFallbackProviderIDs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]string, len(extensionFallbackProviderIDs))
|
||||
copy(result, extensionFallbackProviderIDs)
|
||||
return result
|
||||
}
|
||||
|
||||
func isExtensionFallbackAllowed(providerID string) bool {
|
||||
allowed := GetExtensionFallbackProviderIDs()
|
||||
if allowed == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, allowedProviderID := range allowed {
|
||||
if allowedProviderID == providerID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SetMetadataProviderPriority(providerIDs []string) {
|
||||
metadataProviderPriorityMu.Lock()
|
||||
defer metadataProviderPriorityMu.Unlock()
|
||||
|
||||
sanitized := make([]string, 0, len(providerIDs))
|
||||
seen := map[string]struct{}{}
|
||||
for _, providerID := range providerIDs {
|
||||
providerID = strings.TrimSpace(providerID)
|
||||
if providerID == "" || isRetiredBuiltInMetadataProvider(providerID) {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[providerID]; exists {
|
||||
continue
|
||||
}
|
||||
seen[providerID] = struct{}{}
|
||||
sanitized = append(sanitized, providerID)
|
||||
}
|
||||
metadataProviderPriority = sanitized
|
||||
GoLog("[Extension] Metadata provider priority set: %v\n", sanitized)
|
||||
}
|
||||
|
||||
func GetMetadataProviderPriority() []string {
|
||||
metadataProviderPriorityMu.RLock()
|
||||
defer metadataProviderPriorityMu.RUnlock()
|
||||
|
||||
if len(metadataProviderPriority) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
result := make([]string, len(metadataProviderPriority))
|
||||
copy(result, metadataProviderPriority)
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package gobackend
|
||||
|
||||
import "strings"
|
||||
|
||||
type ExtTrackMetadata struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Artists string `json:"artists"`
|
||||
AlbumName string `json:"album_name"`
|
||||
AlbumArtist string `json:"album_artist,omitempty"`
|
||||
AlbumID string `json:"album_id,omitempty"`
|
||||
AlbumURL string `json:"album_url,omitempty"`
|
||||
ArtistID string `json:"artist_id,omitempty"`
|
||||
ArtistURL string `json:"artist_url,omitempty"`
|
||||
ExternalURL string `json:"external_urls,omitempty"`
|
||||
DurationMS int `json:"duration_ms"`
|
||||
CoverURL string `json:"cover_url,omitempty"`
|
||||
PreviewURL string `json:"preview_url,omitempty"`
|
||||
Images string `json:"images,omitempty"`
|
||||
ReleaseDate string `json:"release_date,omitempty"`
|
||||
TrackNumber int `json:"track_number,omitempty"`
|
||||
TotalTracks int `json:"total_tracks,omitempty"`
|
||||
DiscNumber int `json:"disc_number,omitempty"`
|
||||
TotalDiscs int `json:"total_discs,omitempty"`
|
||||
ISRC string `json:"isrc,omitempty"`
|
||||
ProviderID string `json:"provider_id"`
|
||||
ItemType string `json:"item_type,omitempty"`
|
||||
AlbumType string `json:"album_type,omitempty"`
|
||||
Explicit bool `json:"explicit,omitempty"`
|
||||
|
||||
TidalID string `json:"tidal_id,omitempty"`
|
||||
QobuzID string `json:"qobuz_id,omitempty"`
|
||||
DeezerID string `json:"deezer_id,omitempty"`
|
||||
SpotifyID string `json:"spotify_id,omitempty"`
|
||||
ExternalLinks map[string]string `json:"external_links,omitempty"`
|
||||
|
||||
Label string `json:"label,omitempty"`
|
||||
Copyright string `json:"copyright,omitempty"`
|
||||
Genre string `json:"genre,omitempty"`
|
||||
Composer string `json:"composer,omitempty"`
|
||||
|
||||
AudioQuality string `json:"audio_quality,omitempty"`
|
||||
AudioModes string `json:"audio_modes,omitempty"`
|
||||
}
|
||||
|
||||
func (t *ExtTrackMetadata) ResolvedCoverURL() string {
|
||||
if t.CoverURL != "" {
|
||||
return t.CoverURL
|
||||
}
|
||||
return t.Images
|
||||
}
|
||||
|
||||
type ExtAlbumMetadata struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Artists string `json:"artists"`
|
||||
ArtistID string `json:"artist_id,omitempty"`
|
||||
CoverURL string `json:"cover_url,omitempty"`
|
||||
HeaderImage string `json:"header_image,omitempty"`
|
||||
HeaderVideo string `json:"header_video,omitempty"`
|
||||
ReleaseDate string `json:"release_date,omitempty"`
|
||||
TotalTracks int `json:"total_tracks"`
|
||||
AlbumType string `json:"album_type,omitempty"`
|
||||
AudioTraits []string `json:"audio_traits,omitempty"`
|
||||
Tracks []ExtTrackMetadata `json:"tracks"`
|
||||
ProviderID string `json:"provider_id"`
|
||||
}
|
||||
|
||||
type ExtArtistMetadata struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
HeaderImage string `json:"header_image,omitempty"`
|
||||
HeaderVideo string `json:"header_video,omitempty"`
|
||||
Listeners int `json:"listeners,omitempty"`
|
||||
Albums []ExtAlbumMetadata `json:"albums,omitempty"`
|
||||
Releases []ExtAlbumMetadata `json:"releases,omitempty"`
|
||||
TopTracks []ExtTrackMetadata `json:"top_tracks,omitempty"`
|
||||
ProviderID string `json:"provider_id"`
|
||||
}
|
||||
|
||||
type ExtSearchResult struct {
|
||||
Tracks []ExtTrackMetadata `json:"tracks"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type ExtAvailabilityResult struct {
|
||||
Available bool `json:"available"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
TrackID string `json:"track_id,omitempty"`
|
||||
SkipFallback bool `json:"skip_fallback,omitempty"`
|
||||
}
|
||||
|
||||
type ExtDownloadURLResult struct {
|
||||
URL string `json:"url"`
|
||||
Format string `json:"format"`
|
||||
BitDepth int `json:"bit_depth,omitempty"`
|
||||
SampleRate int `json:"sample_rate,omitempty"`
|
||||
}
|
||||
|
||||
type DownloadDecryptionInfo struct {
|
||||
Strategy string `json:"strategy,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
IV string `json:"iv,omitempty"`
|
||||
InputFormat string `json:"input_format,omitempty"`
|
||||
OutputExtension string `json:"output_extension,omitempty"`
|
||||
Options map[string]interface{} `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
type ExtDownloadResult struct {
|
||||
Success bool `json:"success"`
|
||||
FilePath string `json:"file_path,omitempty"`
|
||||
AlreadyExists bool `json:"already_exists,omitempty"`
|
||||
BitDepth int `json:"bit_depth,omitempty"`
|
||||
SampleRate int `json:"sample_rate,omitempty"`
|
||||
AudioCodec string `json:"audio_codec,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
ErrorType string `json:"error_type,omitempty"`
|
||||
RetryAfterSeconds int `json:"retry_after_seconds,omitempty"`
|
||||
|
||||
Title string `json:"title,omitempty"`
|
||||
Artist string `json:"artist,omitempty"`
|
||||
Album string `json:"album,omitempty"`
|
||||
AlbumArtist string `json:"album_artist,omitempty"`
|
||||
TrackNumber int `json:"track_number,omitempty"`
|
||||
DiscNumber int `json:"disc_number,omitempty"`
|
||||
TotalTracks int `json:"total_tracks,omitempty"`
|
||||
TotalDiscs int `json:"total_discs,omitempty"`
|
||||
ReleaseDate string `json:"release_date,omitempty"`
|
||||
CoverURL string `json:"cover_url,omitempty"`
|
||||
ISRC string `json:"isrc,omitempty"`
|
||||
Genre string `json:"genre,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Copyright string `json:"copyright,omitempty"`
|
||||
Composer string `json:"composer,omitempty"`
|
||||
LyricsLRC string `json:"lyrics_lrc,omitempty"`
|
||||
DecryptionKey string `json:"decryption_key,omitempty"`
|
||||
Decryption *DownloadDecryptionInfo `json:"decryption,omitempty"`
|
||||
ActualExtension string `json:"actual_extension,omitempty"`
|
||||
OutputExtension string `json:"output_extension,omitempty"`
|
||||
ActualContainer string `json:"actual_container,omitempty"`
|
||||
RequiresContainerConversion bool `json:"requires_container_conversion,omitempty"`
|
||||
}
|
||||
|
||||
const genericFFmpegMOVDecryptionStrategy = "ffmpeg.mov_key"
|
||||
|
||||
func cloneDownloadDecryptionInfo(info *DownloadDecryptionInfo) *DownloadDecryptionInfo {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloned := &DownloadDecryptionInfo{
|
||||
Strategy: strings.TrimSpace(info.Strategy),
|
||||
Key: strings.TrimSpace(info.Key),
|
||||
IV: strings.TrimSpace(info.IV),
|
||||
InputFormat: strings.TrimSpace(info.InputFormat),
|
||||
OutputExtension: strings.TrimSpace(info.OutputExtension),
|
||||
}
|
||||
if len(info.Options) > 0 {
|
||||
cloned.Options = make(map[string]interface{}, len(info.Options))
|
||||
for key, value := range info.Options {
|
||||
cloned.Options[key] = value
|
||||
}
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func normalizeDownloadDecryptionStrategy(strategy string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(strategy)) {
|
||||
case "", "ffmpeg.mov_key", "ffmpeg_mov_key", "mov_decryption_key", "mp4_decryption_key", "ffmpeg.mp4_decryption_key":
|
||||
return genericFFmpegMOVDecryptionStrategy
|
||||
default:
|
||||
return strings.TrimSpace(strategy)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeDownloadDecryptionInfo(info *DownloadDecryptionInfo, legacyKey string) *DownloadDecryptionInfo {
|
||||
normalized := cloneDownloadDecryptionInfo(info)
|
||||
trimmedLegacyKey := strings.TrimSpace(legacyKey)
|
||||
|
||||
if normalized == nil {
|
||||
if trimmedLegacyKey == "" {
|
||||
return nil
|
||||
}
|
||||
return &DownloadDecryptionInfo{
|
||||
Strategy: genericFFmpegMOVDecryptionStrategy,
|
||||
Key: trimmedLegacyKey,
|
||||
InputFormat: "mov",
|
||||
}
|
||||
}
|
||||
|
||||
normalized.Strategy = normalizeDownloadDecryptionStrategy(normalized.Strategy)
|
||||
if normalized.Key == "" && trimmedLegacyKey != "" {
|
||||
normalized.Key = trimmedLegacyKey
|
||||
}
|
||||
if normalized.Strategy == "" && normalized.Key != "" {
|
||||
normalized.Strategy = genericFFmpegMOVDecryptionStrategy
|
||||
}
|
||||
if normalized.Strategy == genericFFmpegMOVDecryptionStrategy && normalized.InputFormat == "" {
|
||||
normalized.InputFormat = "mov"
|
||||
}
|
||||
if normalized.Strategy == genericFFmpegMOVDecryptionStrategy && normalized.Key == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizedDownloadDecryptionKey(info *DownloadDecryptionInfo, legacyKey string) string {
|
||||
if normalized := normalizeDownloadDecryptionInfo(info, legacyKey); normalized != nil {
|
||||
if normalized.Strategy == genericFFmpegMOVDecryptionStrategy {
|
||||
return normalized.Key
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(legacyKey)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user