mirror of
https://github.com/zhaarey/apple-music-downloader.git
synced 2025-10-23 15:11:05 +00:00
test: station dl (need media-user-token)
This commit is contained in:
169
main.go
169
main.go
@@ -134,6 +134,17 @@ func checkUrlPlaylist(url string) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkUrlStation(url string) (string, string) {
|
||||||
|
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/station|\/station\/.+))\/(?:id)?(ra\.[\w-]+)(?:$|\?)`)
|
||||||
|
matches := pat.FindAllStringSubmatch(url, -1)
|
||||||
|
|
||||||
|
if matches == nil {
|
||||||
|
return "", ""
|
||||||
|
} else {
|
||||||
|
return matches[0][1], matches[0][2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkUrlArtist(url string) (string, string) {
|
func checkUrlArtist(url string) (string, string) {
|
||||||
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/artist|\/artist\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/artist|\/artist\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
||||||
matches := pat.FindAllStringSubmatch(url, -1)
|
matches := pat.FindAllStringSubmatch(url, -1)
|
||||||
@@ -562,7 +573,7 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
|||||||
//fmt.Sprintf("lyrics=%s", lrc),
|
//fmt.Sprintf("lyrics=%s", lrc),
|
||||||
}
|
}
|
||||||
if Config.EmbedCover {
|
if Config.EmbedCover {
|
||||||
if strings.Contains(track.PreID, "pl.") && Config.DlAlbumcoverForPlaylist {
|
if (strings.Contains(track.PreID, "pl.") || strings.Contains(track.PreID, "ra.")) && Config.DlAlbumcoverForPlaylist {
|
||||||
track.CoverPath, err = writeCover(track.SaveDir, track.ID, track.Resp.Attributes.Artwork.URL)
|
track.CoverPath, err = writeCover(track.SaveDir, track.ID, track.Resp.Attributes.Artwork.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to write cover.")
|
fmt.Println("Failed to write cover.")
|
||||||
@@ -577,7 +588,7 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
|||||||
counter.Error++
|
counter.Error++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.Contains(track.PreID, "pl.") && Config.DlAlbumcoverForPlaylist {
|
if (strings.Contains(track.PreID, "pl.") || strings.Contains(track.PreID, "ra.")) && Config.DlAlbumcoverForPlaylist {
|
||||||
if err := os.Remove(track.CoverPath); err != nil {
|
if err := os.Remove(track.CoverPath); err != nil {
|
||||||
fmt.Printf("Error deleting file: %s\n", track.CoverPath)
|
fmt.Printf("Error deleting file: %s\n", track.CoverPath)
|
||||||
counter.Error++
|
counter.Error++
|
||||||
@@ -598,6 +609,136 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
|||||||
counter.Success++
|
counter.Success++
|
||||||
okDict[track.PreID] = append(okDict[track.PreID], track.TaskNum)
|
okDict[track.PreID] = append(okDict[track.PreID], track.TaskNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ripStation(albumId string, token string, storefront string, mediaUserToken string) error {
|
||||||
|
station := task.NewStation(storefront, albumId)
|
||||||
|
err := station.GetResp(mediaUserToken, token, Config.Language)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
meta := station.Resp
|
||||||
|
|
||||||
|
var Codec string
|
||||||
|
if dl_atmos {
|
||||||
|
Codec = "ATMOS"
|
||||||
|
} else if dl_aac {
|
||||||
|
Codec = "AAC"
|
||||||
|
} else {
|
||||||
|
Codec = "ALAC"
|
||||||
|
}
|
||||||
|
station.Codec = Codec
|
||||||
|
// Get Artist Folder
|
||||||
|
var singerFoldername string
|
||||||
|
if Config.ArtistFolderFormat != "" {
|
||||||
|
singerFoldername = strings.NewReplacer(
|
||||||
|
"{ArtistName}", "Apple Music Station",
|
||||||
|
"{ArtistId}", "",
|
||||||
|
"{UrlArtistName}", "Apple Music Station",
|
||||||
|
).Replace(Config.ArtistFolderFormat)
|
||||||
|
if strings.HasSuffix(singerFoldername, ".") {
|
||||||
|
singerFoldername = strings.ReplaceAll(singerFoldername, ".", "")
|
||||||
|
}
|
||||||
|
singerFoldername = strings.TrimSpace(singerFoldername)
|
||||||
|
fmt.Println(singerFoldername)
|
||||||
|
}
|
||||||
|
singerFolder := filepath.Join(Config.AlacSaveFolder, forbiddenNames.ReplaceAllString(singerFoldername, "_"))
|
||||||
|
if dl_atmos {
|
||||||
|
singerFolder = filepath.Join(Config.AtmosSaveFolder, forbiddenNames.ReplaceAllString(singerFoldername, "_"))
|
||||||
|
}
|
||||||
|
os.MkdirAll(singerFolder, os.ModePerm) // Create artist folder
|
||||||
|
station.SaveDir = singerFolder
|
||||||
|
|
||||||
|
//Get Playlist Folder Name
|
||||||
|
playlistFolder := strings.NewReplacer(
|
||||||
|
"{ArtistName}", "Apple Music Station",
|
||||||
|
"{PlaylistName}", LimitString(meta.Data[0].Attributes.Name),
|
||||||
|
"{PlaylistId}", station.ID,
|
||||||
|
"{Quality}", "",
|
||||||
|
"{Codec}", Codec,
|
||||||
|
"{Tag}", "",
|
||||||
|
).Replace(Config.PlaylistFolderFormat)
|
||||||
|
if strings.HasSuffix(playlistFolder, ".") {
|
||||||
|
playlistFolder = strings.ReplaceAll(playlistFolder, ".", "")
|
||||||
|
}
|
||||||
|
playlistFolder = strings.TrimSpace(playlistFolder)
|
||||||
|
playlistFolderPath := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(playlistFolder, "_"))
|
||||||
|
os.MkdirAll(playlistFolderPath, os.ModePerm)
|
||||||
|
station.SaveName = playlistFolder
|
||||||
|
fmt.Println(playlistFolder)
|
||||||
|
//先省略封面相关的获取
|
||||||
|
//get playlist cover
|
||||||
|
covPath, err := writeCover(playlistFolderPath, "cover", meta.Data[0].Attributes.Artwork.URL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to write cover.")
|
||||||
|
}
|
||||||
|
//get animated artwork
|
||||||
|
if Config.SaveAnimatedArtwork && meta.Data[0].Attributes.EditorialVideo.MotionSquare.Video != "" {
|
||||||
|
fmt.Println("Found Animation Artwork.")
|
||||||
|
|
||||||
|
// Download square version
|
||||||
|
motionvideoUrlSquare, err := extractVideo(meta.Data[0].Attributes.EditorialVideo.MotionSquare.Video)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("no motion video square.\n", err)
|
||||||
|
} else {
|
||||||
|
exists, err := fileExists(filepath.Join(playlistFolderPath, "square_animated_artwork.mp4"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to check if animated artwork square exists.")
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
fmt.Println("Animated artwork square already exists locally.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Animation Artwork Square Downloading...")
|
||||||
|
cmd := exec.Command("ffmpeg", "-loglevel", "quiet", "-y", "-i", motionvideoUrlSquare, "-c", "copy", filepath.Join(playlistFolderPath, "square_animated_artwork.mp4"))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Printf("animated artwork square dl err: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Animation Artwork Square Downloaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.EmbyAnimatedArtwork {
|
||||||
|
// Convert square version to gif
|
||||||
|
cmd3 := exec.Command("ffmpeg", "-i", filepath.Join(playlistFolderPath, "square_animated_artwork.mp4"), "-vf", "scale=440:-1", "-r", "24", "-f", "gif", filepath.Join(playlistFolderPath, "folder.jpg"))
|
||||||
|
if err := cmd3.Run(); err != nil {
|
||||||
|
fmt.Printf("animated artwork square to gif err: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range station.Tracks {
|
||||||
|
station.Tracks[i].CoverPath = covPath
|
||||||
|
station.Tracks[i].SaveDir = playlistFolderPath
|
||||||
|
station.Tracks[i].Codec = Codec
|
||||||
|
}
|
||||||
|
|
||||||
|
trackTotal := len(station.Tracks)
|
||||||
|
arr := make([]int, trackTotal)
|
||||||
|
for i := 0; i < trackTotal; i++ {
|
||||||
|
arr[i] = i + 1
|
||||||
|
}
|
||||||
|
var selected []int
|
||||||
|
|
||||||
|
if true {
|
||||||
|
selected = arr
|
||||||
|
}
|
||||||
|
//Download tracks
|
||||||
|
for i := range station.Tracks {
|
||||||
|
i++
|
||||||
|
// if isInArray(okDict[playlistId], i) {
|
||||||
|
// //fmt.Println("已完成直接跳过.\n")
|
||||||
|
// counter.Total++
|
||||||
|
// counter.Success++
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
if isInArray(selected, i) {
|
||||||
|
ripTrack(&station.Tracks[i-1], token, mediaUserToken)
|
||||||
|
//downloadTrack(trackNum, trackTotal, meta, track, albumId, token, storefront, mediaUserToken, sanAlbumFolder, Codec, covPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func ripAlbum(albumId string, token string, storefront string, mediaUserToken string, urlArg_i string) error {
|
func ripAlbum(albumId string, token string, storefront string, mediaUserToken string, urlArg_i string) error {
|
||||||
album := task.NewAlbum(storefront, albumId)
|
album := task.NewAlbum(storefront, albumId)
|
||||||
err := album.GetResp(token, Config.Language)
|
err := album.GetResp(token, Config.Language)
|
||||||
@@ -832,8 +973,8 @@ func ripAlbum(albumId string, token string, storefront string, mediaUserToken st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//填充子track信息
|
||||||
for i, _ := range album.Tracks {
|
for i := range album.Tracks {
|
||||||
album.Tracks[i].CoverPath = covPath
|
album.Tracks[i].CoverPath = covPath
|
||||||
album.Tracks[i].SaveDir = albumFolderPath
|
album.Tracks[i].SaveDir = albumFolderPath
|
||||||
album.Tracks[i].Codec = Codec
|
album.Tracks[i].Codec = Codec
|
||||||
@@ -850,7 +991,7 @@ func ripAlbum(albumId string, token string, storefront string, mediaUserToken st
|
|||||||
//fmt.Println("URL does not contain parameter 'i'. Please ensure the URL includes 'i' or use another mode.")
|
//fmt.Println("URL does not contain parameter 'i'. Please ensure the URL includes 'i' or use another mode.")
|
||||||
//return nil
|
//return nil
|
||||||
} else {
|
} else {
|
||||||
for i, _ := range album.Tracks {
|
for i := range album.Tracks {
|
||||||
if urlArg_i == album.Tracks[i].ID {
|
if urlArg_i == album.Tracks[i].ID {
|
||||||
ripTrack(&album.Tracks[i], token, mediaUserToken)
|
ripTrack(&album.Tracks[i], token, mediaUserToken)
|
||||||
//downloadTrack(trackNum, trackTotal, meta, track, albumId, token, storefront, mediaUserToken, albumFolderPath, Codec, covPath)
|
//downloadTrack(trackNum, trackTotal, meta, track, albumId, token, storefront, mediaUserToken, albumFolderPath, Codec, covPath)
|
||||||
@@ -867,7 +1008,7 @@ func ripAlbum(albumId string, token string, storefront string, mediaUserToken st
|
|||||||
selected = album.ShowSelect()
|
selected = album.ShowSelect()
|
||||||
}
|
}
|
||||||
//Download tracks
|
//Download tracks
|
||||||
for i, _ := range album.Tracks {
|
for i := range album.Tracks {
|
||||||
i++
|
i++
|
||||||
if isInArray(okDict[albumId], i) {
|
if isInArray(okDict[albumId], i) {
|
||||||
//fmt.Println("已完成直接跳过.\n")
|
//fmt.Println("已完成直接跳过.\n")
|
||||||
@@ -1047,7 +1188,7 @@ func ripPlaylist(playlistId string, token string, storefront string, mediaUserTo
|
|||||||
fmt.Println("Failed to write cover.")
|
fmt.Println("Failed to write cover.")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, _ := range playlist.Tracks {
|
for i := range playlist.Tracks {
|
||||||
playlist.Tracks[i].CoverPath = covPath
|
playlist.Tracks[i].CoverPath = covPath
|
||||||
playlist.Tracks[i].SaveDir = playlistFolderPath
|
playlist.Tracks[i].SaveDir = playlistFolderPath
|
||||||
playlist.Tracks[i].Codec = Codec
|
playlist.Tracks[i].Codec = Codec
|
||||||
@@ -1122,7 +1263,7 @@ func ripPlaylist(playlistId string, token string, storefront string, mediaUserTo
|
|||||||
selected = playlist.ShowSelect()
|
selected = playlist.ShowSelect()
|
||||||
}
|
}
|
||||||
//Download tracks
|
//Download tracks
|
||||||
for i, _ := range playlist.Tracks {
|
for i := range playlist.Tracks {
|
||||||
i++
|
i++
|
||||||
if isInArray(okDict[playlistId], i) {
|
if isInArray(okDict[playlistId], i) {
|
||||||
//fmt.Println("已完成直接跳过.\n")
|
//fmt.Println("已完成直接跳过.\n")
|
||||||
@@ -1189,7 +1330,7 @@ func writeMP4Tags(track *task.Track, lrc string) error {
|
|||||||
t.AlbumSort = track.PlaylistData.Attributes.Name
|
t.AlbumSort = track.PlaylistData.Attributes.Name
|
||||||
t.AlbumArtist = track.PlaylistData.Attributes.ArtistName
|
t.AlbumArtist = track.PlaylistData.Attributes.ArtistName
|
||||||
t.AlbumArtistSort = track.PlaylistData.Attributes.ArtistName
|
t.AlbumArtistSort = track.PlaylistData.Attributes.ArtistName
|
||||||
} else if track.PreType == "playlists" && Config.UseSongInfoForPlaylist {
|
} else if (track.PreType == "playlists" && Config.UseSongInfoForPlaylist) || track.PreType == "stations" {
|
||||||
//使用提前获取到的播放列表下track所在的专辑信息
|
//使用提前获取到的播放列表下track所在的专辑信息
|
||||||
len := len(track.AlbumData.Relationships.Tracks.Data)
|
len := len(track.AlbumData.Relationships.Tracks.Data)
|
||||||
t.DiscTotal = int16(track.AlbumData.Relationships.Tracks.Data[len-1].Attributes.DiscNumber)
|
t.DiscTotal = int16(track.AlbumData.Relationships.Tracks.Data[len-1].Attributes.DiscNumber)
|
||||||
@@ -1372,6 +1513,16 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to rip playlist:", err)
|
fmt.Println("Failed to rip playlist:", err)
|
||||||
}
|
}
|
||||||
|
} else if strings.Contains(urlRaw, "/station/") {
|
||||||
|
storefront, albumId = checkUrlStation(urlRaw)
|
||||||
|
if len(Config.MediaUserToken) <= 50 {
|
||||||
|
fmt.Println("meida-user-token is not set, skip station dl")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := ripStation(albumId, token, storefront, Config.MediaUserToken)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to rip station:", err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Invalid URL.")
|
fmt.Println("Invalid URL.")
|
||||||
}
|
}
|
||||||
|
|||||||
134
utils/ampapi/station.go
Normal file
134
utils/ampapi/station.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package ampapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStationResp(storefront string, id string, language string, token string) (*StationResp, error) {
|
||||||
|
var err error
|
||||||
|
if token == "" {
|
||||||
|
token, err = GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/stations/%s", storefront, id), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
||||||
|
req.Header.Set("Origin", "https://music.apple.com")
|
||||||
|
query := url.Values{}
|
||||||
|
query.Set("omit[resource]", "autos")
|
||||||
|
query.Set("extend", "editorialVideo")
|
||||||
|
query.Set("l", language)
|
||||||
|
req.URL.RawQuery = query.Encode()
|
||||||
|
do, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer do.Body.Close()
|
||||||
|
if do.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New(do.Status)
|
||||||
|
}
|
||||||
|
obj := new(StationResp)
|
||||||
|
err = json.NewDecoder(do.Body).Decode(&obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStationNextTracks(id, mutoken, language, token string) (*TrackResp, error) {
|
||||||
|
var err error
|
||||||
|
if token == "" {
|
||||||
|
token, err = GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", fmt.Sprintf("https://amp-api.music.apple.com/v1/me/stations/next-tracks/%s", id), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
||||||
|
req.Header.Set("Origin", "https://music.apple.com")
|
||||||
|
req.Header.Set("Media-User-Token", mutoken)
|
||||||
|
query := url.Values{}
|
||||||
|
query.Set("omit[resource]", "autos")
|
||||||
|
//query.Set("include", "tracks,artists,record-labels")
|
||||||
|
query.Set("include[songs]", "artists,albums")
|
||||||
|
query.Set("limit", "10")
|
||||||
|
query.Set("extend", "editorialVideo,extendedAssetUrls")
|
||||||
|
query.Set("l", language)
|
||||||
|
req.URL.RawQuery = query.Encode()
|
||||||
|
do, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer do.Body.Close()
|
||||||
|
if do.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New(do.Status)
|
||||||
|
}
|
||||||
|
obj := new(TrackResp)
|
||||||
|
err = json.NewDecoder(do.Body).Decode(&obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StationResp struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Next string `json:"next"`
|
||||||
|
Data []StationRespData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StationRespData struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
Attributes struct {
|
||||||
|
Artwork struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
BgColor string `json:"bgColor"`
|
||||||
|
TextColor1 string `json:"textColor1"`
|
||||||
|
TextColor2 string `json:"textColor2"`
|
||||||
|
TextColor3 string `json:"textColor3"`
|
||||||
|
TextColor4 string `json:"textColor4"`
|
||||||
|
} `json:"artwork"`
|
||||||
|
IsLive bool `json:"isLive"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
EditorialVideo struct {
|
||||||
|
MotionTall struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionTallVideo3x4"`
|
||||||
|
MotionSquare struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionSquareVideo1x1"`
|
||||||
|
MotionDetailTall struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionDetailTall"`
|
||||||
|
MotionDetailSquare struct {
|
||||||
|
Video string `json:"video"`
|
||||||
|
} `json:"motionDetailSquare"`
|
||||||
|
} `json:"editorialVideo"`
|
||||||
|
PlayParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
StationHash string `json:"stationHash"`
|
||||||
|
} `json:"playParams"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
}
|
||||||
207
utils/task/station.go
Normal file
207
utils/task/station.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
//"os"
|
||||||
|
//"strconv"
|
||||||
|
//"strings"
|
||||||
|
|
||||||
|
//"github.com/fatih/color"
|
||||||
|
//"github.com/olekukonko/tablewriter"
|
||||||
|
|
||||||
|
"main/utils/ampapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Station struct {
|
||||||
|
Storefront string
|
||||||
|
ID string
|
||||||
|
|
||||||
|
SaveDir string
|
||||||
|
SaveName string
|
||||||
|
Codec string
|
||||||
|
CoverPath string
|
||||||
|
|
||||||
|
Language string
|
||||||
|
Resp ampapi.StationResp
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Tracks []Track
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStation(st string, id string) *Station {
|
||||||
|
a := new(Station)
|
||||||
|
a.Storefront = st
|
||||||
|
a.ID = id
|
||||||
|
//fmt.Println("Album created")
|
||||||
|
return a
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Station) GetResp(mutoken, token, l string) error {
|
||||||
|
var err error
|
||||||
|
a.Language = l
|
||||||
|
resp, err := ampapi.GetStationResp(a.Storefront, a.ID, a.Language, token)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("error getting station response")
|
||||||
|
}
|
||||||
|
a.Resp = *resp
|
||||||
|
//简化高频调用名称
|
||||||
|
a.Type = a.Resp.Data[0].Attributes.PlayParams.Format
|
||||||
|
if a.Type != "tracks" {
|
||||||
|
return errors.New("stream类型暂未开发")
|
||||||
|
}
|
||||||
|
a.Name = a.Resp.Data[0].Attributes.Name
|
||||||
|
tracksResp, err := ampapi.GetStationNextTracks(a.ID, mutoken, a.Language, token)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("error getting station tracks response")
|
||||||
|
}
|
||||||
|
//fmt.Println("Getting album response")
|
||||||
|
//从resp中的Tracks数据中提取trackData信息到新的Track结构体中
|
||||||
|
for i, trackData := range tracksResp.Data {
|
||||||
|
albumResp, err := ampapi.GetAlbumRespByHref(trackData.Href, a.Language, token)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error getting album response:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
albumLen := len(albumResp.Data[0].Relationships.Tracks.Data)
|
||||||
|
a.Tracks = append(a.Tracks, Track{
|
||||||
|
ID: trackData.ID,
|
||||||
|
Type: trackData.Type,
|
||||||
|
Name: trackData.Attributes.Name,
|
||||||
|
Language: a.Language,
|
||||||
|
Storefront: a.Storefront,
|
||||||
|
|
||||||
|
//SaveDir: filepath.Join(a.SaveDir, a.SaveName),
|
||||||
|
//Codec: a.Codec,
|
||||||
|
TaskNum: i + 1,
|
||||||
|
TaskTotal: len(tracksResp.Data),
|
||||||
|
M3u8: trackData.Attributes.ExtendedAssetUrls.EnhancedHls,
|
||||||
|
WebM3u8: trackData.Attributes.ExtendedAssetUrls.EnhancedHls,
|
||||||
|
//CoverPath: a.CoverPath,
|
||||||
|
|
||||||
|
Resp: trackData,
|
||||||
|
PreType: "stations",
|
||||||
|
DiscTotal: albumResp.Data[0].Relationships.Tracks.Data[albumLen-1].Attributes.DiscNumber,
|
||||||
|
PreID: a.ID,
|
||||||
|
AlbumData: albumResp.Data[0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Station) GetArtwork() string {
|
||||||
|
return a.Resp.Data[0].Attributes.Artwork.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (a *Album) ShowSelect() []int {
|
||||||
|
// meta := a.Resp
|
||||||
|
// trackTotal := len(meta.Data[0].Relationships.Tracks.Data)
|
||||||
|
// arr := make([]int, trackTotal)
|
||||||
|
// for i := 0; i < trackTotal; i++ {
|
||||||
|
// arr[i] = i + 1
|
||||||
|
// }
|
||||||
|
// selected := []int{}
|
||||||
|
// var data [][]string
|
||||||
|
// for trackNum, track := range meta.Data[0].Relationships.Tracks.Data {
|
||||||
|
// trackNum++
|
||||||
|
// trackName := fmt.Sprintf("%02d. %s", track.Attributes.TrackNumber, track.Attributes.Name)
|
||||||
|
// data = append(data, []string{fmt.Sprint(trackNum),
|
||||||
|
// trackName,
|
||||||
|
// track.Attributes.ContentRating,
|
||||||
|
// track.Type})
|
||||||
|
|
||||||
|
// }
|
||||||
|
// table := tablewriter.NewWriter(os.Stdout)
|
||||||
|
// table.SetHeader([]string{"", "Track Name", "Rating", "Type"})
|
||||||
|
// //table.SetFooter([]string{"", "", "Footer", "Footer4"})
|
||||||
|
// table.SetRowLine(false)
|
||||||
|
// //table.SetAutoMergeCells(true)
|
||||||
|
// table.SetCaption(true, fmt.Sprintf("Storefront: %s, %d tracks missing", strings.ToUpper(a.Storefront), meta.Data[0].Attributes.TrackCount-trackTotal))
|
||||||
|
// table.SetHeaderColor(tablewriter.Colors{},
|
||||||
|
// tablewriter.Colors{tablewriter.FgRedColor, tablewriter.Bold},
|
||||||
|
// tablewriter.Colors{tablewriter.FgBlackColor, tablewriter.Bold},
|
||||||
|
// tablewriter.Colors{tablewriter.FgBlackColor, tablewriter.Bold})
|
||||||
|
|
||||||
|
// table.SetColumnColor(tablewriter.Colors{tablewriter.FgCyanColor},
|
||||||
|
// tablewriter.Colors{tablewriter.Bold, tablewriter.FgRedColor},
|
||||||
|
// tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor},
|
||||||
|
// tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
|
||||||
|
// for _, row := range data {
|
||||||
|
// if row[2] == "explicit" {
|
||||||
|
// row[2] = "E"
|
||||||
|
// } else if row[2] == "clean" {
|
||||||
|
// row[2] = "C"
|
||||||
|
// } else {
|
||||||
|
// row[2] = "None"
|
||||||
|
// }
|
||||||
|
// if row[3] == "music-videos" {
|
||||||
|
// row[3] = "MV"
|
||||||
|
// } else if row[3] == "songs" {
|
||||||
|
// row[3] = "SONG"
|
||||||
|
// }
|
||||||
|
// table.Append(row)
|
||||||
|
// }
|
||||||
|
// //table.AppendBulk(data)
|
||||||
|
// table.Render()
|
||||||
|
// fmt.Println("Please select from the track options above (multiple options separated by commas, ranges supported, or type 'all' to select all)")
|
||||||
|
// cyanColor := color.New(color.FgCyan)
|
||||||
|
// cyanColor.Print("select: ")
|
||||||
|
// reader := bufio.NewReader(os.Stdin)
|
||||||
|
// input, err := reader.ReadString('\n')
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// input = strings.TrimSpace(input)
|
||||||
|
// if input == "all" {
|
||||||
|
// fmt.Println("You have selected all options:")
|
||||||
|
// selected = arr
|
||||||
|
// } else {
|
||||||
|
// selectedOptions := [][]string{}
|
||||||
|
// parts := strings.Split(input, ",")
|
||||||
|
// for _, part := range parts {
|
||||||
|
// if strings.Contains(part, "-") { // Range setting
|
||||||
|
// rangeParts := strings.Split(part, "-")
|
||||||
|
// selectedOptions = append(selectedOptions, rangeParts)
|
||||||
|
// } else { // Single option
|
||||||
|
// selectedOptions = append(selectedOptions, []string{part})
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// //
|
||||||
|
// for _, opt := range selectedOptions {
|
||||||
|
// if len(opt) == 1 { // Single option
|
||||||
|
// num, err := strconv.Atoi(opt[0])
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println("Invalid option:", opt[0])
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// if num > 0 && num <= len(arr) {
|
||||||
|
// selected = append(selected, num)
|
||||||
|
// //args = append(args, urls[num-1])
|
||||||
|
// } else {
|
||||||
|
// fmt.Println("Option out of range:", opt[0])
|
||||||
|
// }
|
||||||
|
// } else if len(opt) == 2 { // Range
|
||||||
|
// start, err1 := strconv.Atoi(opt[0])
|
||||||
|
// end, err2 := strconv.Atoi(opt[1])
|
||||||
|
// if err1 != nil || err2 != nil {
|
||||||
|
// fmt.Println("Invalid range:", opt)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// if start < 1 || end > len(arr) || start > end {
|
||||||
|
// fmt.Println("Range out of range:", opt)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// for i := start; i <= end; i++ {
|
||||||
|
// //fmt.Println(options[i-1])
|
||||||
|
// selected = append(selected, i)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// fmt.Println("Invalid option:", opt)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return selected
|
||||||
|
// }
|
||||||
Reference in New Issue
Block a user