mirror of
https://github.com/zhaarey/apple-music-downloader.git
synced 2025-10-23 15:11:05 +00:00
12
main.go
12
main.go
@@ -918,7 +918,7 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
|||||||
counter.Error++
|
counter.Error++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err := runv3.Run(track.ID, trackPath, token, mediaUserToken, false)
|
_, err := runv3.Run(track.ID, trackPath, token, mediaUserToken, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to dl aac-lc:", err)
|
fmt.Println("Failed to dl aac-lc:", err)
|
||||||
if err.Error() == "Unavailable" {
|
if err.Error() == "Unavailable" {
|
||||||
@@ -1106,14 +1106,14 @@ func ripStation(albumId string, token string, storefront string, mediaUserToken
|
|||||||
fmt.Println("Radio already exists locally.")
|
fmt.Println("Radio already exists locally.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
assetsUrl, err := ampapi.GetStationAssetsUrl(station.ID, mediaUserToken, token)
|
assetsUrl, serverUrl, err := ampapi.GetStationAssetsUrlAndServerUrl(station.ID, mediaUserToken, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to get station assets url.", err)
|
fmt.Println("Failed to get station assets url.", err)
|
||||||
counter.Error++
|
counter.Error++
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trackM3U8 := strings.ReplaceAll(assetsUrl, "index.m3u8", "256/prog_index.m3u8")
|
trackM3U8 := strings.ReplaceAll(assetsUrl, "index.m3u8", "256/prog_index.m3u8")
|
||||||
keyAndUrls, _ := runv3.Run(station.ID, trackM3U8, token, mediaUserToken, true)
|
keyAndUrls, _ := runv3.Run(station.ID, trackM3U8, token, mediaUserToken, true, serverUrl)
|
||||||
err = runv3.ExtMvData(keyAndUrls, trackPath)
|
err = runv3.ExtMvData(keyAndUrls, trackPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to download station stream.", err)
|
fmt.Println("Failed to download station stream.", err)
|
||||||
@@ -1993,17 +1993,17 @@ func mvDownloader(adamID string, saveDir string, token string, storefront string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mvm3u8url, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true)
|
mvm3u8url, _, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true)
|
||||||
if mvm3u8url == "" {
|
if mvm3u8url == "" {
|
||||||
return errors.New("media-user-token may wrong or expired")
|
return errors.New("media-user-token may wrong or expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
os.MkdirAll(saveDir, os.ModePerm)
|
os.MkdirAll(saveDir, os.ModePerm)
|
||||||
videom3u8url, _ := extractVideo(mvm3u8url)
|
videom3u8url, _ := extractVideo(mvm3u8url)
|
||||||
videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true)
|
videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true, "")
|
||||||
_ = runv3.ExtMvData(videokeyAndUrls, vidPath)
|
_ = runv3.ExtMvData(videokeyAndUrls, vidPath)
|
||||||
audiom3u8url, _ := extractMvAudio(mvm3u8url)
|
audiom3u8url, _ := extractMvAudio(mvm3u8url)
|
||||||
audiokeyAndUrls, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true)
|
audiokeyAndUrls, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true, "")
|
||||||
_ = runv3.ExtMvData(audiokeyAndUrls, audPath)
|
_ = runv3.ExtMvData(audiokeyAndUrls, audPath)
|
||||||
|
|
||||||
tags := []string{
|
tags := []string{
|
||||||
|
|||||||
@@ -45,18 +45,18 @@ 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) {
|
func GetStationAssetsUrlAndServerUrl(id string, mutoken string, token string) (string, string, error) {
|
||||||
var err error
|
var err error
|
||||||
if token == "" {
|
if token == "" {
|
||||||
token, err = GetToken()
|
token, err = GetToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "https://amp-api.music.apple.com/v1/play/assets", nil)
|
req, err := http.NewRequest("GET", "https://amp-api.music.apple.com/v1/play/assets", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
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("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")
|
||||||
@@ -71,18 +71,18 @@ func GetStationAssetsUrl(id string, mutoken string, token string) (string, error
|
|||||||
req.URL.RawQuery = query.Encode()
|
req.URL.RawQuery = query.Encode()
|
||||||
do, err := http.DefaultClient.Do(req)
|
do, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
defer do.Body.Close()
|
defer do.Body.Close()
|
||||||
if do.StatusCode != http.StatusOK {
|
if do.StatusCode != http.StatusOK {
|
||||||
return "", errors.New(do.Status)
|
return "", "", errors.New(do.Status)
|
||||||
}
|
}
|
||||||
obj := new(StationAssets)
|
obj := new(StationAssets)
|
||||||
err = json.NewDecoder(do.Body).Decode(&obj)
|
err = json.NewDecoder(do.Body).Decode(&obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return obj.Results.Assets[0].Url, nil
|
return obj.Results.Assets[0].Url, obj.Results.Assets[0].KeyServerUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStationNextTracks(id, mutoken, language, token string) (*TrackResp, error) {
|
func GetStationNextTracks(id, mutoken, language, token string) (*TrackResp, error) {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func BeforeRequest(cl *resty.Client, ctx context.Context, url string, body []byt
|
|||||||
jsondata := map[string]interface{}{
|
jsondata := map[string]interface{}{
|
||||||
"challenge": base64.StdEncoding.EncodeToString(body), // 'body' is passed in directly
|
"challenge": base64.StdEncoding.EncodeToString(body), // 'body' is passed in directly
|
||||||
"key-system": "com.widevine.alpha",
|
"key-system": "com.widevine.alpha",
|
||||||
"uri": "data:;base64," + ctx.Value("pssh").(string),
|
"uri": ctx.Value("uriPrefix").(string) + "," + ctx.Value("pssh").(string),
|
||||||
"adamId": ctx.Value("adamId").(string),
|
"adamId": ctx.Value("adamId").(string),
|
||||||
"isLibrary": false,
|
"isLibrary": false,
|
||||||
"user-initiated": true,
|
"user-initiated": true,
|
||||||
@@ -102,7 +102,7 @@ func AfterRequest(response *resty.Response) ([]byte, error) {
|
|||||||
return license, nil
|
return license, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool) (string, string, error) {
|
func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool) (string, string, string, error) {
|
||||||
url := "https://play.music.apple.com/WebObjects/MZPlay.woa/wa/webPlayback"
|
url := "https://play.music.apple.com/WebObjects/MZPlay.woa/wa/webPlayback"
|
||||||
postData := map[string]string{
|
postData := map[string]string{
|
||||||
"salableAdamId": adamId,
|
"salableAdamId": adamId,
|
||||||
@@ -110,12 +110,12 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool
|
|||||||
jsonData, err := json.Marshal(postData)
|
jsonData, err := json.Marshal(postData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error encoding JSON:", err)
|
fmt.Println("Error encoding JSON:", err)
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonData)))
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonData)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error creating request:", err)
|
fmt.Println("Error creating request:", err)
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Origin", "https://music.apple.com")
|
req.Header.Set("Origin", "https://music.apple.com")
|
||||||
@@ -130,7 +130,7 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool
|
|||||||
//resp, err := client.Do(req)
|
//resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error sending request:", err)
|
fmt.Println("Error sending request:", err)
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
//fmt.Println("Response Status:", resp.Status)
|
//fmt.Println("Response Status:", resp.Status)
|
||||||
@@ -138,25 +138,25 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool
|
|||||||
err = json.NewDecoder(resp.Body).Decode(&obj)
|
err = json.NewDecoder(resp.Body).Decode(&obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("json err:", err)
|
fmt.Println("json err:", err)
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
if len(obj.List) > 0 {
|
if len(obj.List) > 0 {
|
||||||
if mvmode {
|
if mvmode {
|
||||||
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, uriPrefix, err := extractKidBase64(obj.List[0].Assets[i].URL, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
return fileurl, kidBase64, nil
|
return fileurl, kidBase64, uriPrefix, nil
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", "", errors.New("Unavailable")
|
return "", "", "", errors.New("Unavailable")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Songlist struct {
|
type Songlist struct {
|
||||||
@@ -171,30 +171,32 @@ type Songlist struct {
|
|||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractKidBase64(b string, mvmode bool) (string, string, error) {
|
func extractKidBase64(b string, mvmode bool) (string, string, string, error) {
|
||||||
resp, err := http.Get(b)
|
resp, err := http.Get(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return "", "", errors.New(resp.Status)
|
return "", "", "", errors.New(resp.Status)
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
masterString := string(body)
|
masterString := string(body)
|
||||||
from, listType, err := m3u8.DecodeFrom(strings.NewReader(masterString), true)
|
from, listType, err := m3u8.DecodeFrom(strings.NewReader(masterString), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
var kidbase64 string
|
var kidbase64 string
|
||||||
|
var uriPrefix string
|
||||||
var urlBuilder strings.Builder
|
var urlBuilder strings.Builder
|
||||||
if listType == m3u8.MEDIA {
|
if listType == m3u8.MEDIA {
|
||||||
mediaPlaylist := from.(*m3u8.MediaPlaylist)
|
mediaPlaylist := from.(*m3u8.MediaPlaylist)
|
||||||
if mediaPlaylist.Key != nil {
|
if mediaPlaylist.Key != nil {
|
||||||
split := strings.Split(mediaPlaylist.Key.URI, ",")
|
split := strings.Split(mediaPlaylist.Key.URI, ",")
|
||||||
|
uriPrefix = split[0]
|
||||||
kidbase64 = split[1]
|
kidbase64 = split[1]
|
||||||
lastSlashIndex := strings.LastIndex(b, "/")
|
lastSlashIndex := strings.LastIndex(b, "/")
|
||||||
// 截取最后一个斜杠之前的部分
|
// 截取最后一个斜杠之前的部分
|
||||||
@@ -221,7 +223,7 @@ func extractKidBase64(b string, mvmode bool) (string, string, error) {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println("Not a media playlist")
|
fmt.Println("Not a media playlist")
|
||||||
}
|
}
|
||||||
return kidbase64, urlBuilder.String(), nil
|
return kidbase64, urlBuilder.String(), uriPrefix, nil
|
||||||
}
|
}
|
||||||
func extsong(b string) bytes.Buffer {
|
func extsong(b string) bytes.Buffer {
|
||||||
resp, err := http.Get(b)
|
resp, err := http.Get(b)
|
||||||
@@ -251,18 +253,19 @@ func extsong(b string) bytes.Buffer {
|
|||||||
io.Copy(io.MultiWriter(&buffer, bar), resp.Body)
|
io.Copy(io.MultiWriter(&buffer, bar), resp.Body)
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmode bool) (string, error) {
|
func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmode bool, serverUrl string) (string, error) {
|
||||||
var keystr string //for mv key
|
var keystr string //for mv key
|
||||||
var fileurl string
|
var fileurl string
|
||||||
var kidBase64 string
|
var kidBase64 string
|
||||||
|
var uriPrefix string
|
||||||
var err error
|
var err error
|
||||||
if mvmode {
|
if mvmode {
|
||||||
kidBase64, fileurl, err = extractKidBase64(trackpath, true)
|
kidBase64, fileurl, uriPrefix, err = extractKidBase64(trackpath, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileurl, kidBase64, err = GetWebplayback(adamId, authtoken, mutoken, false)
|
fileurl, kidBase64, uriPrefix, err = GetWebplayback(adamId, authtoken, mutoken, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -270,6 +273,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = context.WithValue(ctx, "pssh", kidBase64)
|
ctx = context.WithValue(ctx, "pssh", kidBase64)
|
||||||
ctx = context.WithValue(ctx, "adamId", adamId)
|
ctx = context.WithValue(ctx, "adamId", adamId)
|
||||||
|
ctx = context.WithValue(ctx, "uriPrefix", uriPrefix)
|
||||||
pssh, err := getPSSH("", kidBase64)
|
pssh, err := getPSSH("", kidBase64)
|
||||||
//fmt.Println(pssh)
|
//fmt.Println(pssh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -289,8 +293,8 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo
|
|||||||
}
|
}
|
||||||
key.CdmInit()
|
key.CdmInit()
|
||||||
var keybt []byte
|
var keybt []byte
|
||||||
if strings.Contains(adamId, "ra.") {
|
if serverUrl != "" {
|
||||||
keystr, keybt, err = key.GetKey(ctx, "https://play.itunes.apple.com/WebObjects/MZPlay.woa/web/radio/versions/1/license", pssh, nil)
|
keystr, keybt, err = key.GetKey(ctx, serverUrl, pssh, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
Reference in New Issue
Block a user