commit
726a21fb53
@ -1,6 +1,7 @@
|
|||||||
media-user-token: "your-media-user-token"
|
media-user-token: "your-media-user-token"
|
||||||
embed-lrc: true
|
embed-lrc: true
|
||||||
save-lrc-file: false
|
save-lrc-file: false
|
||||||
|
save-artist-cover: false
|
||||||
save-animated-artwork: false # If enabled, requires ffmpeg
|
save-animated-artwork: false # If enabled, requires ffmpeg
|
||||||
emby-animated-artwork: false # If enabled, requires ffmpeg
|
emby-animated-artwork: false # If enabled, requires ffmpeg
|
||||||
embed-cover: true
|
embed-cover: true
|
||||||
|
14
main.go
14
main.go
@ -45,6 +45,7 @@ type Config struct {
|
|||||||
EmbyAnimatedArtwork bool `yaml:"emby-animated-artwork"`
|
EmbyAnimatedArtwork bool `yaml:"emby-animated-artwork"`
|
||||||
EmbedLrc bool `yaml:"embed-lrc"`
|
EmbedLrc bool `yaml:"embed-lrc"`
|
||||||
EmbedCover bool `yaml:"embed-cover"`
|
EmbedCover bool `yaml:"embed-cover"`
|
||||||
|
SaveArtistCover bool `yaml:"save-artist-cover"`
|
||||||
CoverSize string `yaml:"cover-size"`
|
CoverSize string `yaml:"cover-size"`
|
||||||
CoverFormat string `yaml:"cover-format"`
|
CoverFormat string `yaml:"cover-format"`
|
||||||
AlacSaveFolder string `yaml:"alac-save-folder"`
|
AlacSaveFolder string `yaml:"alac-save-folder"`
|
||||||
@ -1154,7 +1155,7 @@ func getMeta(albumId string, token string, storefront string) (*AutoGenerated, e
|
|||||||
query.Set("omit[resource]", "autos")
|
query.Set("omit[resource]", "autos")
|
||||||
query.Set("include", "tracks,artists,record-labels")
|
query.Set("include", "tracks,artists,record-labels")
|
||||||
query.Set("include[songs]", "artists")
|
query.Set("include[songs]", "artists")
|
||||||
query.Set("fields[artists]", "name")
|
query.Set("fields[artists]", "name,artwork")
|
||||||
query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
|
query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
|
||||||
query.Set("fields[record-labels]", "name")
|
query.Set("fields[record-labels]", "name")
|
||||||
query.Set("extend", "editorialVideo")
|
query.Set("extend", "editorialVideo")
|
||||||
@ -1390,6 +1391,14 @@ func rip(albumId string, token string, storefront string, userToken string) erro
|
|||||||
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
|
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
|
||||||
os.MkdirAll(sanAlbumFolder, os.ModePerm)
|
os.MkdirAll(sanAlbumFolder, os.ModePerm)
|
||||||
fmt.Println(albumFolder)
|
fmt.Println(albumFolder)
|
||||||
|
//get artist cover
|
||||||
|
if config.SaveArtistCover && !(strings.Contains(albumId, "pl.")) {
|
||||||
|
err = writeCover(singerFolder, "folder", meta.Data[0].Relationships.Artists.Data[0].Attributes.Artwork.Url)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to write artist cover.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//get album cover
|
||||||
err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL)
|
err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to write cover.")
|
fmt.Println("Failed to write cover.")
|
||||||
@ -2402,6 +2411,9 @@ type AutoGenerated struct {
|
|||||||
Href string `json:"href"`
|
Href string `json:"href"`
|
||||||
Attributes struct {
|
Attributes struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Artwork struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
} `json:"artwork"`
|
||||||
} `json:"attributes"`
|
} `json:"attributes"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
} `json:"artists"`
|
} `json:"artists"`
|
||||||
|
@ -41,8 +41,11 @@ var (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
MediaUserToken string `yaml:"media-user-token"`
|
MediaUserToken string `yaml:"media-user-token"`
|
||||||
SaveLrcFile bool `yaml:"save-lrc-file"`
|
SaveLrcFile bool `yaml:"save-lrc-file"`
|
||||||
|
SaveAnimatedArtwork bool `yaml:"save-animated-artwork"`
|
||||||
|
EmbyAnimatedArtwork bool `yaml:"emby-animated-artwork"`
|
||||||
EmbedLrc bool `yaml:"embed-lrc"`
|
EmbedLrc bool `yaml:"embed-lrc"`
|
||||||
EmbedCover bool `yaml:"embed-cover"`
|
EmbedCover bool `yaml:"embed-cover"`
|
||||||
|
SaveArtistCover bool `yaml:"save-artist-cover"`
|
||||||
CoverSize string `yaml:"cover-size"`
|
CoverSize string `yaml:"cover-size"`
|
||||||
CoverFormat string `yaml:"cover-format"`
|
CoverFormat string `yaml:"cover-format"`
|
||||||
AlacSaveFolder string `yaml:"alac-save-folder"`
|
AlacSaveFolder string `yaml:"alac-save-folder"`
|
||||||
@ -1098,9 +1101,10 @@ func getMeta(albumId string, token string, storefront string) (*AutoGenerated, e
|
|||||||
query.Set("omit[resource]", "autos")
|
query.Set("omit[resource]", "autos")
|
||||||
query.Set("include", "tracks,artists,record-labels")
|
query.Set("include", "tracks,artists,record-labels")
|
||||||
query.Set("include[songs]", "artists")
|
query.Set("include[songs]", "artists")
|
||||||
query.Set("fields[artists]", "name")
|
query.Set("fields[artists]", "name,artwork")
|
||||||
query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
|
query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
|
||||||
query.Set("fields[record-labels]", "name")
|
query.Set("fields[record-labels]", "name")
|
||||||
|
query.Set("extend", "editorialVideo")
|
||||||
// query.Set("l", "en-gb")
|
// query.Set("l", "en-gb")
|
||||||
req.URL.RawQuery = query.Encode()
|
req.URL.RawQuery = query.Encode()
|
||||||
do, err := http.DefaultClient.Do(req)
|
do, err := http.DefaultClient.Do(req)
|
||||||
@ -1315,10 +1319,48 @@ func rip(albumId string, token string, storefront string, userToken string) erro
|
|||||||
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
|
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
|
||||||
os.MkdirAll(sanAlbumFolder, os.ModePerm)
|
os.MkdirAll(sanAlbumFolder, os.ModePerm)
|
||||||
fmt.Println(albumFolder)
|
fmt.Println(albumFolder)
|
||||||
|
//get artist cover
|
||||||
|
if config.SaveArtistCover && !(strings.Contains(albumId, "pl.")) {
|
||||||
|
err = writeCover(singerFolder, "folder", meta.Data[0].Relationships.Artists.Data[0].Attributes.Artwork.Url)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to write artist cover.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//get album cover
|
||||||
err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL)
|
err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to write cover.")
|
fmt.Println("Failed to write cover.")
|
||||||
}
|
}
|
||||||
|
//get animated artwork
|
||||||
|
if config.SaveAnimatedArtwork && meta.Data[0].Attributes.EditorialVideo.MotionDetailSquare.Video != "" {
|
||||||
|
fmt.Println("Found Animation Artwork.")
|
||||||
|
motionvideoUrl, err := extractVideo(meta.Data[0].Attributes.EditorialVideo.MotionDetailSquare.Video)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("no motion video.\n", err)
|
||||||
|
}
|
||||||
|
exists, err := fileExists(filepath.Join(sanAlbumFolder, "animated_artwork.mp4"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to check if animated artwork exists.")
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
fmt.Println("Animated artwork already exists locally.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Animation Artwork Downloading...")
|
||||||
|
cmd := exec.Command("ffmpeg", "-loglevel", "quiet", "-y", "-i", motionvideoUrl, "-c", "copy", filepath.Join(sanAlbumFolder, "animated_artwork.mp4"))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Printf("animated artwork dl err: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Animation Artwork Downloaded")
|
||||||
|
}
|
||||||
|
if config.EmbyAnimatedArtwork {
|
||||||
|
cmd2 := exec.Command("ffmpeg", "-i", filepath.Join(sanAlbumFolder, "animated_artwork.mp4"), "-vf", "scale=440:-1", "-r", "24", "-f", "gif", filepath.Join(sanAlbumFolder, "folder.jpg"))
|
||||||
|
if err := cmd2.Run(); err != nil {
|
||||||
|
fmt.Printf("animated artwork to gif err: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
trackTotal := len(meta.Data[0].Relationships.Tracks.Data)
|
trackTotal := len(meta.Data[0].Relationships.Tracks.Data)
|
||||||
for trackNum, track := range meta.Data[0].Relationships.Tracks.Data {
|
for trackNum, track := range meta.Data[0].Relationships.Tracks.Data {
|
||||||
trackNum++
|
trackNum++
|
||||||
@ -1651,6 +1693,46 @@ func extractMedia(b string) (string, []string, error) {
|
|||||||
return streamUrl.String(), keys, nil
|
return streamUrl.String(), keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractVideo(c string) (string, error) {
|
||||||
|
MediaUrl, err := url.Parse(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
resp, err := http.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
videoString := string(body)
|
||||||
|
from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true)
|
||||||
|
if err != nil || listType != m3u8.MASTER {
|
||||||
|
return "", errors.New("m3u8 not of media type")
|
||||||
|
}
|
||||||
|
video := from.(*m3u8.MasterPlaylist)
|
||||||
|
var streamUrl *url.URL
|
||||||
|
sort.Slice(video.Variants, func(i, j int) bool {
|
||||||
|
return video.Variants[i].AverageBandwidth > video.Variants[j].AverageBandwidth
|
||||||
|
})
|
||||||
|
if len(video.Variants) > 0 {
|
||||||
|
highestBandwidthVariant := video.Variants[0]
|
||||||
|
streamUrl, err = MediaUrl.Parse(highestBandwidthVariant.URI)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if streamUrl == nil {
|
||||||
|
return "", errors.New("no video codec found")
|
||||||
|
}
|
||||||
|
return streamUrl.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func extractSong(url string) (*SongInfo, error) {
|
func extractSong(url string) (*SongInfo, error) {
|
||||||
fmt.Println("Downloading...")
|
fmt.Println("Downloading...")
|
||||||
track, err := http.Get(url)
|
track, err := http.Get(url)
|
||||||
@ -2101,6 +2183,14 @@ type AutoGenerated struct {
|
|||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
} `json:"playParams"`
|
} `json:"playParams"`
|
||||||
IsCompilation bool `json:"isCompilation"`
|
IsCompilation bool `json:"isCompilation"`
|
||||||
|
EditorialVideo struct {
|
||||||
|
MotionDetailSquare struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionDetailSquare"`
|
||||||
|
MotionSquareVideo1x1 struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionSquareVideo1x1"`
|
||||||
|
} `json:"editorialVideo"`
|
||||||
} `json:"attributes"`
|
} `json:"attributes"`
|
||||||
Relationships struct {
|
Relationships struct {
|
||||||
RecordLabels struct {
|
RecordLabels struct {
|
||||||
@ -2115,6 +2205,9 @@ type AutoGenerated struct {
|
|||||||
Href string `json:"href"`
|
Href string `json:"href"`
|
||||||
Attributes struct {
|
Attributes struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Artwork struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
} `json:"artwork"`
|
||||||
} `json:"attributes"`
|
} `json:"attributes"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
} `json:"artists"`
|
} `json:"artists"`
|
||||||
|
@ -41,8 +41,11 @@ var (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
MediaUserToken string `yaml:"media-user-token"`
|
MediaUserToken string `yaml:"media-user-token"`
|
||||||
SaveLrcFile bool `yaml:"save-lrc-file"`
|
SaveLrcFile bool `yaml:"save-lrc-file"`
|
||||||
|
SaveAnimatedArtwork bool `yaml:"save-animated-artwork"`
|
||||||
|
EmbyAnimatedArtwork bool `yaml:"emby-animated-artwork"`
|
||||||
EmbedLrc bool `yaml:"embed-lrc"`
|
EmbedLrc bool `yaml:"embed-lrc"`
|
||||||
EmbedCover bool `yaml:"embed-cover"`
|
EmbedCover bool `yaml:"embed-cover"`
|
||||||
|
SaveArtistCover bool `yaml:"save-artist-cover"`
|
||||||
CoverSize string `yaml:"cover-size"`
|
CoverSize string `yaml:"cover-size"`
|
||||||
CoverFormat string `yaml:"cover-format"`
|
CoverFormat string `yaml:"cover-format"`
|
||||||
AlacSaveFolder string `yaml:"alac-save-folder"`
|
AlacSaveFolder string `yaml:"alac-save-folder"`
|
||||||
@ -1033,9 +1036,10 @@ func getMeta(albumId string, token string, storefront string) (*AutoGenerated, e
|
|||||||
query.Set("omit[resource]", "autos")
|
query.Set("omit[resource]", "autos")
|
||||||
query.Set("include", "tracks,artists,record-labels")
|
query.Set("include", "tracks,artists,record-labels")
|
||||||
query.Set("include[songs]", "artists")
|
query.Set("include[songs]", "artists")
|
||||||
query.Set("fields[artists]", "name")
|
query.Set("fields[artists]", "name,artwork")
|
||||||
query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
|
query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
|
||||||
query.Set("fields[record-labels]", "name")
|
query.Set("fields[record-labels]", "name")
|
||||||
|
query.Set("extend", "editorialVideo")
|
||||||
// query.Set("l", "en-gb")
|
// query.Set("l", "en-gb")
|
||||||
req.URL.RawQuery = query.Encode()
|
req.URL.RawQuery = query.Encode()
|
||||||
do, err := http.DefaultClient.Do(req)
|
do, err := http.DefaultClient.Do(req)
|
||||||
@ -1273,10 +1277,48 @@ func rip(albumId string, token string, storefront string, userToken string) erro
|
|||||||
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
|
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
|
||||||
os.MkdirAll(sanAlbumFolder, os.ModePerm)
|
os.MkdirAll(sanAlbumFolder, os.ModePerm)
|
||||||
fmt.Println(albumFolder)
|
fmt.Println(albumFolder)
|
||||||
|
//get artist cover
|
||||||
|
if config.SaveArtistCover && !(strings.Contains(albumId, "pl.")) {
|
||||||
|
err = writeCover(singerFolder, "folder", meta.Data[0].Relationships.Artists.Data[0].Attributes.Artwork.Url)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to write artist cover.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//get album cover
|
||||||
err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL)
|
err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to write cover.")
|
fmt.Println("Failed to write cover.")
|
||||||
}
|
}
|
||||||
|
//get animated artwork
|
||||||
|
if config.SaveAnimatedArtwork && meta.Data[0].Attributes.EditorialVideo.MotionDetailSquare.Video != "" {
|
||||||
|
fmt.Println("Found Animation Artwork.")
|
||||||
|
motionvideoUrl, err := extractVideo(meta.Data[0].Attributes.EditorialVideo.MotionDetailSquare.Video)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("no motion video.\n", err)
|
||||||
|
}
|
||||||
|
exists, err := fileExists(filepath.Join(sanAlbumFolder, "animated_artwork.mp4"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to check if animated artwork exists.")
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
fmt.Println("Animated artwork already exists locally.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Animation Artwork Downloading...")
|
||||||
|
cmd := exec.Command("ffmpeg", "-loglevel", "quiet", "-y", "-i", motionvideoUrl, "-c", "copy", filepath.Join(sanAlbumFolder, "animated_artwork.mp4"))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Printf("animated artwork dl err: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Animation Artwork Downloaded")
|
||||||
|
}
|
||||||
|
if config.EmbyAnimatedArtwork {
|
||||||
|
cmd2 := exec.Command("ffmpeg", "-i", filepath.Join(sanAlbumFolder, "animated_artwork.mp4"), "-vf", "scale=440:-1", "-r", "24", "-f", "gif", filepath.Join(sanAlbumFolder, "folder.jpg"))
|
||||||
|
if err := cmd2.Run(); err != nil {
|
||||||
|
fmt.Printf("animated artwork to gif err: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
trackTotal := len(meta.Data[0].Relationships.Tracks.Data)
|
trackTotal := len(meta.Data[0].Relationships.Tracks.Data)
|
||||||
arr := make([]int, trackTotal)
|
arr := make([]int, trackTotal)
|
||||||
for i := 0; i < trackTotal; i++ {
|
for i := 0; i < trackTotal; i++ {
|
||||||
@ -1687,6 +1729,45 @@ func extractMedia(b string) (string, []string, error) {
|
|||||||
}
|
}
|
||||||
return streamUrl.String(), keys, nil
|
return streamUrl.String(), keys, nil
|
||||||
}
|
}
|
||||||
|
func extractVideo(c string) (string, error) {
|
||||||
|
MediaUrl, err := url.Parse(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
resp, err := http.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
videoString := string(body)
|
||||||
|
from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true)
|
||||||
|
if err != nil || listType != m3u8.MASTER {
|
||||||
|
return "", errors.New("m3u8 not of media type")
|
||||||
|
}
|
||||||
|
video := from.(*m3u8.MasterPlaylist)
|
||||||
|
var streamUrl *url.URL
|
||||||
|
sort.Slice(video.Variants, func(i, j int) bool {
|
||||||
|
return video.Variants[i].AverageBandwidth > video.Variants[j].AverageBandwidth
|
||||||
|
})
|
||||||
|
if len(video.Variants) > 0 {
|
||||||
|
highestBandwidthVariant := video.Variants[0]
|
||||||
|
streamUrl, err = MediaUrl.Parse(highestBandwidthVariant.URI)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if streamUrl == nil {
|
||||||
|
return "", errors.New("no video codec found")
|
||||||
|
}
|
||||||
|
return streamUrl.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func extractSong(url string) (*SongInfo, error) {
|
func extractSong(url string) (*SongInfo, error) {
|
||||||
fmt.Println("Downloading...")
|
fmt.Println("Downloading...")
|
||||||
@ -2137,6 +2218,14 @@ type AutoGenerated struct {
|
|||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
} `json:"playParams"`
|
} `json:"playParams"`
|
||||||
IsCompilation bool `json:"isCompilation"`
|
IsCompilation bool `json:"isCompilation"`
|
||||||
|
EditorialVideo struct {
|
||||||
|
MotionDetailSquare struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionDetailSquare"`
|
||||||
|
MotionSquareVideo1x1 struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionSquareVideo1x1"`
|
||||||
|
} `json:"editorialVideo"`
|
||||||
} `json:"attributes"`
|
} `json:"attributes"`
|
||||||
Relationships struct {
|
Relationships struct {
|
||||||
RecordLabels struct {
|
RecordLabels struct {
|
||||||
@ -2151,6 +2240,9 @@ type AutoGenerated struct {
|
|||||||
Href string `json:"href"`
|
Href string `json:"href"`
|
||||||
Attributes struct {
|
Attributes struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Artwork struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
} `json:"artwork"`
|
||||||
} `json:"attributes"`
|
} `json:"attributes"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
} `json:"artists"`
|
} `json:"artists"`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user