mirror of
https://github.com/zhaarey/apple-music-downloader.git
synced 2025-10-23 15:11:05 +00:00
add: station dl(need media-user-token)
This commit is contained in:
77
main.go
77
main.go
@@ -465,7 +465,7 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
|||||||
if dl_atmos {
|
if dl_atmos {
|
||||||
Quality = fmt.Sprintf("%dkbps", Config.AtmosMax-2000)
|
Quality = fmt.Sprintf("%dkbps", Config.AtmosMax-2000)
|
||||||
} else if needDlAacLc {
|
} else if needDlAacLc {
|
||||||
Quality = "256kbps"
|
Quality = "256Kbps"
|
||||||
} else {
|
} else {
|
||||||
_, Quality, err = extractMedia(track.M3u8, true)
|
_, Quality, err = extractMedia(track.M3u8, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -651,7 +651,7 @@ func ripStation(albumId string, token string, storefront string, mediaUserToken
|
|||||||
//Get Playlist Folder Name
|
//Get Playlist Folder Name
|
||||||
playlistFolder := strings.NewReplacer(
|
playlistFolder := strings.NewReplacer(
|
||||||
"{ArtistName}", "Apple Music Station",
|
"{ArtistName}", "Apple Music Station",
|
||||||
"{PlaylistName}", LimitString(meta.Data[0].Attributes.Name),
|
"{PlaylistName}", LimitString(station.Name),
|
||||||
"{PlaylistId}", station.ID,
|
"{PlaylistId}", station.ID,
|
||||||
"{Quality}", "",
|
"{Quality}", "",
|
||||||
"{Codec}", Codec,
|
"{Codec}", Codec,
|
||||||
@@ -671,6 +671,8 @@ func ripStation(albumId string, token string, storefront string, mediaUserToken
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to write cover.")
|
fmt.Println("Failed to write cover.")
|
||||||
}
|
}
|
||||||
|
station.CoverPath = covPath
|
||||||
|
|
||||||
//get animated artwork
|
//get animated artwork
|
||||||
if Config.SaveAnimatedArtwork && meta.Data[0].Attributes.EditorialVideo.MotionSquare.Video != "" {
|
if Config.SaveAnimatedArtwork && meta.Data[0].Attributes.EditorialVideo.MotionSquare.Video != "" {
|
||||||
fmt.Println("Found Animation Artwork.")
|
fmt.Println("Found Animation Artwork.")
|
||||||
@@ -705,6 +707,73 @@ func ripStation(albumId string, token string, storefront string, mediaUserToken
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 处理stream类型的station
|
||||||
|
if station.Type == "stream" {
|
||||||
|
counter.Total++
|
||||||
|
if isInArray(okDict[station.ID], 1) {
|
||||||
|
counter.Success++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
songName := strings.NewReplacer(
|
||||||
|
"{SongId}", station.ID,
|
||||||
|
"{SongNumer}", "01",
|
||||||
|
"{SongName}", LimitString(station.Name),
|
||||||
|
"{DiscNumber}", "1",
|
||||||
|
"{TrackNumber}", "01",
|
||||||
|
"{Quality}", "256Kbps",
|
||||||
|
"{Tag}", "",
|
||||||
|
"{Codec}", "AAC",
|
||||||
|
).Replace(Config.SongFileFormat)
|
||||||
|
fmt.Println(songName)
|
||||||
|
trackPath := filepath.Join(playlistFolderPath, fmt.Sprintf("%s.m4a", forbiddenNames.ReplaceAllString(songName, "_")))
|
||||||
|
exists, _ := fileExists(trackPath)
|
||||||
|
if exists {
|
||||||
|
counter.Success++
|
||||||
|
okDict[station.ID] = append(okDict[station.ID], 1)
|
||||||
|
|
||||||
|
fmt.Println("Radio already exists locally.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
assetsUrl, err := ampapi.GetStationAssetsUrl(station.ID, mediaUserToken, token)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to get station assets url.", err)
|
||||||
|
counter.Error++
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
trackM3U8 := strings.ReplaceAll(assetsUrl, "index.m3u8", "256/prog_index.m3u8")
|
||||||
|
//testM3U8 := "https://itsliveradio.apple.com/bb/aod/exp_aod/jpopnowradio/AkinaNakamori/cmaf/256/prog_index.m3u8"
|
||||||
|
keyAndUrls, _ := runv3.Run(station.ID, trackM3U8, token, mediaUserToken, true)
|
||||||
|
err = runv3.ExtMvData(keyAndUrls, trackPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to download station stream.", err)
|
||||||
|
counter.Error++
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//tags
|
||||||
|
tags := []string{
|
||||||
|
"tool=",
|
||||||
|
"disk=1/1",
|
||||||
|
"track=1",
|
||||||
|
"tracknum=1/1",
|
||||||
|
fmt.Sprintf("artist=%s", "Apple Music Station"),
|
||||||
|
fmt.Sprintf("performer=%s", "Apple Music Station"),
|
||||||
|
fmt.Sprintf("album_artist=%s", "Apple Music Station"),
|
||||||
|
fmt.Sprintf("artist=%s", "Apple Music Station"),
|
||||||
|
fmt.Sprintf("album=%s", station.Name),
|
||||||
|
fmt.Sprintf("title=%s", station.Name),
|
||||||
|
}
|
||||||
|
if Config.EmbedCover {
|
||||||
|
tags = append(tags, fmt.Sprintf("cover=%s", station.CoverPath))
|
||||||
|
}
|
||||||
|
tagsString := strings.Join(tags, ":")
|
||||||
|
cmd := exec.Command("MP4Box", "-itags", tagsString, trackPath)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Printf("Embed failed: %v\n", err)
|
||||||
|
}
|
||||||
|
counter.Success++
|
||||||
|
okDict[station.ID] = append(okDict[station.ID], 1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for i := range station.Tracks {
|
for i := range station.Tracks {
|
||||||
station.Tracks[i].CoverPath = covPath
|
station.Tracks[i].CoverPath = covPath
|
||||||
@@ -1113,7 +1182,7 @@ func ripPlaylist(playlistId string, token string, storefront string, mediaUserTo
|
|||||||
if dl_atmos {
|
if dl_atmos {
|
||||||
Quality = fmt.Sprintf("%dkbps", Config.AtmosMax-2000)
|
Quality = fmt.Sprintf("%dkbps", Config.AtmosMax-2000)
|
||||||
} else if dl_aac && Config.AacType == "aac-lc" {
|
} else if dl_aac && Config.AacType == "aac-lc" {
|
||||||
Quality = "256kbps"
|
Quality = "256Kbps"
|
||||||
} else {
|
} else {
|
||||||
manifest1, err := ampapi.GetSongResp(storefront, meta.Data[0].Relationships.Tracks.Data[0].ID, playlist.Language, token)
|
manifest1, err := ampapi.GetSongResp(storefront, meta.Data[0].Relationships.Tracks.Data[0].ID, playlist.Language, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1121,7 +1190,7 @@ func ripPlaylist(playlistId string, token string, storefront string, mediaUserTo
|
|||||||
} else {
|
} else {
|
||||||
if manifest1.Data[0].Attributes.ExtendedAssetUrls.EnhancedHls == "" {
|
if manifest1.Data[0].Attributes.ExtendedAssetUrls.EnhancedHls == "" {
|
||||||
Codec = "AAC"
|
Codec = "AAC"
|
||||||
Quality = "256kbps"
|
Quality = "256Kbps"
|
||||||
//fmt.Println("Unavailable.\n")
|
//fmt.Println("Unavailable.\n")
|
||||||
} else {
|
} else {
|
||||||
needCheck := false
|
needCheck := false
|
||||||
|
|||||||
@@ -45,6 +45,46 @@ func GetStationResp(storefront string, id string, language string, token string)
|
|||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetStationAssetsUrl(id string, mutoken string, token string) (string, error) {
|
||||||
|
var err error
|
||||||
|
if token == "" {
|
||||||
|
token, err = GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "https://amp-api.music.apple.com/v1/play/assets", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", 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("extend", "editorialVideo")
|
||||||
|
query.Set("id", id)
|
||||||
|
query.Set("kind", "radioStation")
|
||||||
|
query.Set("keyFormat", "web")
|
||||||
|
req.URL.RawQuery = query.Encode()
|
||||||
|
do, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer do.Body.Close()
|
||||||
|
if do.StatusCode != http.StatusOK {
|
||||||
|
return "", errors.New(do.Status)
|
||||||
|
}
|
||||||
|
obj := new(StationAssets)
|
||||||
|
err = json.NewDecoder(do.Body).Decode(&obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return obj.Results.Assets[0].Url, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetStationNextTracks(id, mutoken, language, token string) (*TrackResp, error) {
|
func GetStationNextTracks(id, mutoken, language, token string) (*TrackResp, error) {
|
||||||
var err error
|
var err error
|
||||||
if token == "" {
|
if token == "" {
|
||||||
@@ -92,6 +132,17 @@ type StationResp struct {
|
|||||||
Data []StationRespData `json:"data"`
|
Data []StationRespData `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StationAssets struct {
|
||||||
|
Results struct {
|
||||||
|
Assets []struct {
|
||||||
|
KeyServerUrl string `json:"keyServerUrl"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
WidevineKeyCertificateUrl string `json:"widevineKeyCertificateUrl"`
|
||||||
|
FairPlayKeyCertificateUrl string `json:"fairPlayKeyCertificateUrl"`
|
||||||
|
} `json:"assets"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
type StationRespData struct {
|
type StationRespData struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gospider007/requests"
|
"github.com/gospider007/requests"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool
|
|||||||
return obj.List[0].HlsPlaylistUrl, "", nil
|
return obj.List[0].HlsPlaylistUrl, "", nil
|
||||||
}
|
}
|
||||||
// 遍历 Assets
|
// 遍历 Assets
|
||||||
for i, _ := range obj.List[0].Assets {
|
for i := range obj.List[0].Assets {
|
||||||
if obj.List[0].Assets[i].Flavor == "28:ctrp256" {
|
if obj.List[0].Assets[i].Flavor == "28:ctrp256" {
|
||||||
kidBase64, fileurl, err := extractKidBase64(obj.List[0].Assets[i].URL, false)
|
kidBase64, fileurl, err := extractKidBase64(obj.List[0].Assets[i].URL, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -298,11 +299,20 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo
|
|||||||
AfterRequest: AfterRequest,
|
AfterRequest: AfterRequest,
|
||||||
}
|
}
|
||||||
key.CdmInit()
|
key.CdmInit()
|
||||||
keystr, keybt, err := key.GetKey(ctx, "https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/acquireWebPlaybackLicense", pssh, nil)
|
var keybt []byte
|
||||||
|
if strings.Contains(adamId, "ra.") {
|
||||||
|
keystr, keybt, err = key.GetKey(ctx, "https://play.itunes.apple.com/WebObjects/MZPlay.woa/web/radio/versions/1/license", pssh, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
keystr, keybt, err = key.GetKey(ctx, "https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/acquireWebPlaybackLicense", pssh, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
if mvmode {
|
if mvmode {
|
||||||
keyAndUrls := "1:" + keystr + ";" + fileurl
|
keyAndUrls := "1:" + keystr + ";" + fileurl
|
||||||
return keyAndUrls, nil
|
return keyAndUrls, nil
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ func (a *Station) GetResp(mutoken, token, l string) error {
|
|||||||
a.Resp = *resp
|
a.Resp = *resp
|
||||||
//简化高频调用名称
|
//简化高频调用名称
|
||||||
a.Type = a.Resp.Data[0].Attributes.PlayParams.Format
|
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
|
a.Name = a.Resp.Data[0].Attributes.Name
|
||||||
|
if a.Type != "tracks" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
tracksResp, err := ampapi.GetStationNextTracks(a.ID, mutoken, a.Language, token)
|
tracksResp, err := ampapi.GetStationNextTracks(a.ID, mutoken, a.Language, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("error getting station tracks response")
|
return errors.New("error getting station tracks response")
|
||||||
|
|||||||
Reference in New Issue
Block a user