diff --git a/main.go b/main.go index 53a9dd9..c36b242 100644 --- a/main.go +++ b/main.go @@ -155,11 +155,11 @@ func getUrlSong(songUrl string, token string) (string, error) { songAlbumUrl := fmt.Sprintf("https://music.apple.com/%s/album/1/%s?i=%s", storefront, albumId, songId) return songAlbumUrl, nil } -func getUrlArtistName(artistUrl string, token string) (string, error) { +func getUrlArtistName(artistUrl string, token string) (string, string, error) { storefront, artistId := checkUrlArtist(artistUrl) req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/artists/%s", storefront, artistId), nil) if err != nil { - return "", err + 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") @@ -168,18 +168,18 @@ func getUrlArtistName(artistUrl string, token string) (string, error) { query.Set("l", Config.Language) do, err := http.DefaultClient.Do(req) if err != nil { - return "", err + return "", "", err } defer do.Body.Close() if do.StatusCode != http.StatusOK { - return "", errors.New(do.Status) + return "", "", errors.New(do.Status) } obj := new(structs.AutoGeneratedArtist) err = json.NewDecoder(do.Body).Decode(&obj) if err != nil { - return "", err + return "", "", err } - return obj.Data[0].Attributes.Name, nil + return obj.Data[0].Attributes.Name, obj.Data[0].ID , nil } func checkArtist(artistUrl string, token string) ([]string, error) { @@ -479,7 +479,7 @@ func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, tr counter.Success++ return } - err := mvDownloader(track.ID, sanAlbumFolder, token, storefront, mediaUserToken) + err := mvDownloader(track.ID, sanAlbumFolder, token, storefront, mediaUserToken, meta) if err != nil { fmt.Println("\u26A0 Failed to dl MV:", err) counter.Error++ @@ -1132,7 +1132,7 @@ func main() { } os.Args = args if strings.Contains(os.Args[0], "/artist/") { - urlArtistName, err := getUrlArtistName(os.Args[0], token) + urlArtistName, urlArtistID, err := getUrlArtistName(os.Args[0], token) if err != nil { fmt.Println("Failed to get artistname.") return @@ -1140,6 +1140,7 @@ func main() { //fmt.Println("get artistname:", urlArtistName) Config.ArtistFolderFormat = strings.NewReplacer( "{UrlArtistName}", LimitString(urlArtistName), + "{ArtistId}", urlArtistID, ).Replace(Config.ArtistFolderFormat) newArgs, err := checkArtist(os.Args[0], token) if err != nil { @@ -1169,8 +1170,18 @@ func main() { counter.Success++ continue } + mvSaveDir := strings.NewReplacer( + "{ArtistName}", "", + "{UrlArtistName}", "", + "{ArtistId}", "", + ).Replace(Config.ArtistFolderFormat) + if mvSaveDir != "" { + mvSaveDir = filepath.Join(Config.AlacSaveFolder, mvSaveDir) + } else { + mvSaveDir = Config.AlacSaveFolder + } storefront, albumId = checkUrlMv(urlRaw) - err := mvDownloader(albumId, Config.AlacSaveFolder, token, storefront, Config.MediaUserToken) + err := mvDownloader(albumId, mvSaveDir, token, storefront, Config.MediaUserToken, nil) if err != nil { fmt.Println("\u26A0 Failed to dl MV:", err) counter.Error++ @@ -1216,48 +1227,109 @@ func main() { counter = structs.Counter{} } } -func mvDownloader(adamID string, saveDir string, token string, storefront string, mediaUserToken string) error { +func mvDownloader(adamID string, saveDir string, token string, storefront string, mediaUserToken string, meta *structs.AutoGenerated) error { MVInfo, err := getMVInfoFromAdam(adamID, token, storefront) if err != nil { fmt.Println("\u26A0 Failed to get MV manifest:", err) - counter.NotSong++ return nil } vidPath := filepath.Join(saveDir, fmt.Sprintf("%s_vid.mp4", adamID)) audPath := filepath.Join(saveDir, fmt.Sprintf("%s_aud.mp4", adamID)) mvOutPath := filepath.Join(saveDir, fmt.Sprintf("%s.mp4", forbiddenNames.ReplaceAllString(MVInfo.Data[0].Attributes.Name, "_"))) + + fmt.Println(MVInfo.Data[0].Attributes.Name) + exists, _ := fileExists(mvOutPath) if exists { fmt.Println("MV already exists locally.") return nil } - mvm3u8url, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true) - //videom3u8url, audiom3u8url , _ := mvQualitySelect(mvm3u8url) - videom3u8url, _ := extractVideo(mvm3u8url) - audiom3u8url, _ := extractMvAudio(mvm3u8url) - //fmt.Println(audiom3u8url) - videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true) - audiokeyAndUrls, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true) - //fmt.Println(videokeyAndUrls) - //fmt.Println(audiokeyAndUrls) + mvm3u8url, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true) os.MkdirAll(saveDir, os.ModePerm) - fmt.Println(MVInfo.Data[0].Attributes.Name + "-VIDEO") + //video + videom3u8url, _ := extractVideo(mvm3u8url) + videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true) _ = runv3.ExtMvData(videokeyAndUrls, vidPath) - - fmt.Println(MVInfo.Data[0].Attributes.Name + "-AUDIO") + //audio + audiom3u8url, _ := extractMvAudio(mvm3u8url) + audiokeyAndUrls, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true) _ = runv3.ExtMvData(audiokeyAndUrls, audPath) - muxCmd := exec.Command("MP4Box", "-quiet", "-add", vidPath, "-add", audPath, "-keep-utc", "-new", mvOutPath) + + //tags + tags := []string{ + "tool=", + fmt.Sprintf("artist=%s", MVInfo.Data[0].Attributes.ArtistName), + fmt.Sprintf("title=%s", MVInfo.Data[0].Attributes.Name), + fmt.Sprintf("genre=%s", MVInfo.Data[0].Attributes.GenreNames), + fmt.Sprintf("created=%s", MVInfo.Data[0].Attributes.ReleaseDate), + fmt.Sprintf("ISRC=%s", MVInfo.Data[0].Attributes.Isrc), + } + + // ContentRating tag + if MVInfo.Data[0].Attributes.ContentRating == "explicit" { + tags = append(tags, "rating=1") + } else if MVInfo.Data[0].Attributes.ContentRating == "clean" { + tags = append(tags, "rating=2") + } else { + tags = append(tags, "rating=0") + } + + //获取传入的专辑信息当中该mv所在的位置 + var trackTotal int + var trackNum int + var index int + if meta !=nil { + trackTotal = len(meta.Data[0].Relationships.Tracks.Data) + for i, track := range meta.Data[0].Relationships.Tracks.Data { + if adamID == track.ID { + index = i + trackNum = i + 1 + } + } + } + //根据情况额外添加可使用的tags + if meta != nil { + if meta.Data[0].Type == "playlists" && !Config.UseSongInfoForPlaylist { + tags = append(tags, "disk=1/1") + tags = append(tags, fmt.Sprintf("album=%s", meta.Data[0].Attributes.Name)) + tags = append(tags, fmt.Sprintf("track=%d", trackNum)) + tags = append(tags, fmt.Sprintf("tracknum=%d/%d", trackNum, trackTotal)) + tags = append(tags, fmt.Sprintf("album_artist=%s", meta.Data[0].Attributes.ArtistName)) + tags = append(tags, fmt.Sprintf("performer=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName)) + tags = append(tags, fmt.Sprintf("copyright=%s", meta.Data[0].Attributes.Copyright)) + tags = append(tags, fmt.Sprintf("UPC=%s", meta.Data[0].Attributes.Upc)) + } else { + tags = append(tags, fmt.Sprintf("album=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName)) + tags = append(tags, fmt.Sprintf("disk=%d/%d", meta.Data[0].Relationships.Tracks.Data[index].Attributes.DiscNumber, meta.Data[0].Relationships.Tracks.Data[trackTotal-1].Attributes.DiscNumber)) + tags = append(tags, fmt.Sprintf("track=%d", meta.Data[0].Relationships.Tracks.Data[index].Attributes.TrackNumber)) + tags = append(tags, fmt.Sprintf("tracknum=%d/%d", meta.Data[0].Relationships.Tracks.Data[index].Attributes.TrackNumber, trackTotal)) + tags = append(tags, fmt.Sprintf("album_artist=%s", meta.Data[0].Attributes.ArtistName)) + tags = append(tags, fmt.Sprintf("performer=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName)) + tags = append(tags, fmt.Sprintf("copyright=%s", meta.Data[0].Attributes.Copyright)) + tags = append(tags, fmt.Sprintf("UPC=%s", meta.Data[0].Attributes.Upc)) + } + } else { + tags = append(tags, fmt.Sprintf("album=%s", MVInfo.Data[0].Attributes.AlbumName)) + tags = append(tags, fmt.Sprintf("disk=%d", MVInfo.Data[0].Attributes.DiscNumber)) + tags = append(tags, fmt.Sprintf("track=%d", MVInfo.Data[0].Attributes.TrackNumber)) + tags = append(tags, fmt.Sprintf("tracknum=%d", MVInfo.Data[0].Attributes.TrackNumber)) + //tags = append(tags, fmt.Sprintf("album_artist=%s", MVInfo.Data[0].Attributes.ArtistName)) + tags = append(tags, fmt.Sprintf("performer=%s", MVInfo.Data[0].Attributes.ArtistName)) + } + //mux and add tag + tagsString := strings.Join(tags, ":") + muxCmd := exec.Command("MP4Box", "-itags", tagsString, "-quiet", "-add", vidPath, "-add", audPath, "-keep-utc", "-new", mvOutPath) fmt.Printf("MV Remuxing...") if err := muxCmd.Run(); err != nil { fmt.Printf("MV mux failed: %v\n", err) return err } fmt.Printf("\rMV Remuxed. \n") - _ = os.Remove(vidPath) - _ = os.Remove(audPath) + defer os.Remove(vidPath) + defer os.Remove(audPath) return nil } @@ -1283,13 +1355,13 @@ func extractMvAudio(c string) (string, error) { return "", err } - videoString := string(body) - from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true) + audioString := string(body) + from, listType, err := m3u8.DecodeFrom(strings.NewReader(audioString), true) if err != nil || listType != m3u8.MASTER { return "", errors.New("m3u8 not of media type") } - video := from.(*m3u8.MasterPlaylist) + audio := from.(*m3u8.MasterPlaylist) var audioPriority = []string{"audio-atmos", "audio-ac3", "audio-stereo-256"} if Config.MVAudioType == "ac3" { @@ -1307,7 +1379,7 @@ func extractMvAudio(c string) (string, error) { } var audioStreams []AudioStream - for _, variant := range video.Variants { + for _, variant := range audio.Variants { for _, audiov := range variant.Alternatives { if audiov.URI != "" { for _, priority := range audioPriority { @@ -1336,7 +1408,7 @@ func extractMvAudio(c string) (string, error) { sort.Slice(audioStreams, func(i, j int) bool { return audioStreams[i].Rank > audioStreams[j].Rank }) - + fmt.Println("Audio: " + audioStreams[0].GroupID) return audioStreams[0].URL, nil } @@ -1874,6 +1946,7 @@ func extractVideo(c string) (string, error) { if err != nil { return "", err } + fmt.Println("Video: " + variant.Resolution + "-" + variant.VideoRange) break } } diff --git a/utils/runv3/runv3.go b/utils/runv3/runv3.go index 5247aad..3c529b2 100644 --- a/utils/runv3/runv3.go +++ b/utils/runv3/runv3.go @@ -355,7 +355,10 @@ func ExtMvData (keyAndUrls string, savePath string)(error) { fmt.Printf("下载链接 %s 失败:%v\n", url, err) return err } - + if resp.StatusCode != http.StatusOK { + fmt.Printf("链接 %s 响应失败:%v\n", url, resp.Status) + return errors.New(resp.Status) + } // 将响应体写入输出文件 _, err = io.Copy(barWriter, resp.Body) defer resp.Body.Close() // 注意及时关闭响应体,避免资源泄露 diff --git a/utils/structs/structs.go b/utils/structs/structs.go index b7b088c..33a16df 100644 --- a/utils/structs/structs.go +++ b/utils/structs/structs.go @@ -437,11 +437,15 @@ type AutoGeneratedMusicVideo struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` + AlbumName string `json:"albumName"` ArtistName string `json:"artistName"` URL string `json:"url"` GenreNames []string `json:"genreNames"` DurationInMillis int `json:"durationInMillis"` Isrc string `json:"isrc"` + TrackNumber int `json:"trackNumber"` + DiscNumber int `json:"discNumber"` + ContentRating string `json:"contentRating"` ReleaseDate string `json:"releaseDate"` Name string `json:"name"` Has4K bool `json:"has4K"`