diff --git a/main.go b/main.go index d0133a4..d4fdd2c 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "bytes" "encoding/binary" "encoding/json" - "gopkg.in/yaml.v2" "errors" "fmt" "io" @@ -23,6 +22,8 @@ import ( "strings" "time" + "gopkg.in/yaml.v2" + "github.com/abema/go-mp4" "github.com/beevik/etree" "github.com/grafov/m3u8" @@ -36,28 +37,29 @@ const ( var ( forbiddenNames = regexp.MustCompile(`[/\\<>:"|?*]`) ) + type Config struct { - MediaUserToken string `yaml:"media-user-token"` - SaveLrcFile bool `yaml:"save-lrc-file"` - EmbedLrc bool `yaml:"embed-lrc"` - EmbedCover bool `yaml:"embed-cover"` - CoverSize string `yaml:"cover-size"` - CoverFormat string `yaml:"cover-format"` - AlacSaveFolder string `yaml:"alac-save-folder"` - AtmosSaveFolder string `yaml:"atmos-save-folder"` - AlbumFolderFormat string `yaml:"album-folder-format"` - PlaylistFolderFormat string `yaml:"playlist-folder-format"` + MediaUserToken string `yaml:"media-user-token"` + SaveLrcFile bool `yaml:"save-lrc-file"` + EmbedLrc bool `yaml:"embed-lrc"` + EmbedCover bool `yaml:"embed-cover"` + CoverSize string `yaml:"cover-size"` + CoverFormat string `yaml:"cover-format"` + AlacSaveFolder string `yaml:"alac-save-folder"` + AtmosSaveFolder string `yaml:"atmos-save-folder"` + AlbumFolderFormat string `yaml:"album-folder-format"` + PlaylistFolderFormat string `yaml:"playlist-folder-format"` ArtistFolderFormat string `yaml:"artist-folder-format"` - SongFileFormat string `yaml:"song-file-format"` - ExplicitChoice string `yaml:"explicit-choice"` - CleanChoice string `yaml:"clean-choice"` - AppleMasterChoice string `yaml:"apple-master-choice"` - ForceApi bool `yaml:"force-api"` - Check string `yaml:"check"` - GetM3u8FromDevice bool `yaml:"get-m3u8-from-device"` - AlacMax int `yaml:"alac-max"` - UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` - DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` + SongFileFormat string `yaml:"song-file-format"` + ExplicitChoice string `yaml:"explicit-choice"` + CleanChoice string `yaml:"clean-choice"` + AppleMasterChoice string `yaml:"apple-master-choice"` + ForceApi bool `yaml:"force-api"` + Check string `yaml:"check"` + GetM3u8FromDevice bool `yaml:"get-m3u8-from-device"` + AlacMax int `yaml:"alac-max"` + UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` + DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` } var config Config @@ -76,18 +78,19 @@ type SongInfo struct { alacParam *Alac samples []SampleInfo } + func loadConfig() error { - // 读取config.yaml文件内容 - data, err := ioutil.ReadFile("config.yaml") - if err != nil { - return err - } - // 将yaml解析到config变量中 - err = yaml.Unmarshal(data, &config) - if err != nil { - return err - } - return nil + // 读取config.yaml文件内容 + data, err := ioutil.ReadFile("config.yaml") + if err != nil { + return err + } + // 将yaml解析到config变量中 + err = yaml.Unmarshal(data, &config) + if err != nil { + return err + } + return nil } func (s *SongInfo) Duration() (ret uint64) { @@ -690,10 +693,10 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t if err != nil { return err } - AlbumName:=meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName - if strings.Contains(meta.Data[0].ID, "pl."){ + AlbumName := meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName + if strings.Contains(meta.Data[0].ID, "pl.") { if !config.UseSongInfoForPlaylist { - AlbumName=meta.Data[0].Attributes.Name + AlbumName = meta.Data[0].Attributes.Name } } err = addMeta(mp4.BoxType{'\251', 'a', 'l', 'b'}, AlbumName) @@ -804,13 +807,13 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t if err != nil { return err } - - if !strings.Contains(meta.Data[0].ID, "pl."){ + + if !strings.Contains(meta.Data[0].ID, "pl.") { plID, err := strconv.ParseUint(meta.Data[0].ID, 10, 32) if err != nil { return err } - + err = addMeta(mp4.BoxType{'p', 'l', 'I', 'D'}, uint32(plID)) if err != nil { return err @@ -836,8 +839,8 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t binary.BigEndian.PutUint32(trkn, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.TrackNumber)) binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) binary.BigEndian.PutUint32(disk, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.DiscNumber)) - binary.BigEndian.PutUint16(disk[4:], uint16(meta.Data[0].Relationships.Tracks.Data[trackTotal - 1].Attributes.DiscNumber)) - if strings.Contains(meta.Data[0].ID, "pl."){ + binary.BigEndian.PutUint16(disk[4:], uint16(meta.Data[0].Relationships.Tracks.Data[trackTotal-1].Attributes.DiscNumber)) + if strings.Contains(meta.Data[0].ID, "pl.") { if !config.UseSongInfoForPlaylist { binary.BigEndian.PutUint32(trkn, uint32(trackNum)) binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) @@ -1117,8 +1120,8 @@ func getSongLyrics(songId string, storefront string, token string, userToken str } } -func writeCover(sanAlbumFolder,name string, url string) error { - covPath := filepath.Join(sanAlbumFolder, name+"." + config.CoverFormat) +func writeCover(sanAlbumFolder, name string, url string) error { + covPath := filepath.Join(sanAlbumFolder, name+"."+config.CoverFormat) exists, err := fileExists(covPath) if err != nil { fmt.Println("Failed to check if cover exists.") @@ -1179,13 +1182,13 @@ func rip(albumId string, token string, storefront string, userToken string) erro return err } var singerFoldername string - if config.ArtistFolderFormat != ""{ + if config.ArtistFolderFormat != "" { if strings.Contains(albumId, "pl.") { singerFoldername = strings.NewReplacer( "{ArtistName}", "Apple Music", "{ArtistId}", "", ).Replace(config.ArtistFolderFormat) - }else{ + } else { singerFoldername = strings.NewReplacer( "{ArtistName}", meta.Data[0].Attributes.ArtistName, "{ArtistId}", meta.Data[0].Relationships.Artists.Data[0].ID, @@ -1206,29 +1209,29 @@ func rip(albumId string, token string, storefront string, userToken string) erro fmt.Println("Unavailable in ALAC.") } var Quality string - EnhancedHls_m3u8,err:=checkM3u8(meta.Data[0].Relationships.Tracks.Data[0].ID,"album") - if strings.HasPrefix(EnhancedHls_m3u8, "http"){ - manifest1.Attributes.ExtendedAssetUrls.EnhancedHls=EnhancedHls_m3u8 + EnhancedHls_m3u8, err := checkM3u8(meta.Data[0].Relationships.Tracks.Data[0].ID, "album") + if strings.HasPrefix(EnhancedHls_m3u8, "http") { + manifest1.Attributes.ExtendedAssetUrls.EnhancedHls = EnhancedHls_m3u8 } - if strings.Contains(config.AlbumFolderFormat, "Quality"){ - Quality,err = extractMediaQuality(manifest1.Attributes.ExtendedAssetUrls.EnhancedHls) + if strings.Contains(config.AlbumFolderFormat, "Quality") { + Quality, err = extractMediaQuality(manifest1.Attributes.ExtendedAssetUrls.EnhancedHls) if err != nil { fmt.Println("Failed to extract quality from manifest.\n", err) } } stringsToJoin := []string{} - if meta.Data[0].Attributes.IsAppleDigitalMaster{ - if config.AppleMasterChoice != ""{ + if meta.Data[0].Attributes.IsAppleDigitalMaster || meta.Data[0].Attributes.IsMasteredForItunes { + if config.AppleMasterChoice != "" { stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) } } - if meta.Data[0].Attributes.ContentRating=="explicit"{ - if config.ExplicitChoice != ""{ + if meta.Data[0].Attributes.ContentRating == "explicit" { + if config.ExplicitChoice != "" { stringsToJoin = append(stringsToJoin, config.ExplicitChoice) } } - if meta.Data[0].Attributes.ContentRating=="clean"{ - if config.CleanChoice != ""{ + if meta.Data[0].Attributes.ContentRating == "clean" { + if config.CleanChoice != "" { stringsToJoin = append(stringsToJoin, config.CleanChoice) } } @@ -1239,11 +1242,11 @@ func rip(albumId string, token string, storefront string, userToken string) erro "{ArtistName}", "Apple Music", "{PlaylistName}", meta.Data[0].Attributes.Name, "{PlaylistId}", albumId, - "{Quality}",Quality, + "{Quality}", Quality, "{Codec}", "ALAC", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.PlaylistFolderFormat) - }else{ + } else { albumFolder = strings.NewReplacer( "{ReleaseDate}", meta.Data[0].Attributes.ReleaseDate, "{ReleaseYear}", meta.Data[0].Attributes.ReleaseDate[:4], @@ -1255,7 +1258,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro "{AlbumId}", albumId, "{Quality}", Quality, "{Codec}", "ALAC", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.AlbumFolderFormat) } if strings.HasSuffix(albumFolder, ".") { @@ -1265,7 +1268,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_")) os.MkdirAll(sanAlbumFolder, os.ModePerm) fmt.Println(albumFolder) - err = writeCover(sanAlbumFolder,"cover", meta.Data[0].Attributes.Artwork.URL) + err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL) if err != nil { fmt.Println("Failed to write cover.") } @@ -1283,31 +1286,31 @@ func rip(albumId string, token string, storefront string, userToken string) erro fmt.Println("Unavailable in ALAC.") continue } - EnhancedHls_m3u8,err:=checkM3u8(track.ID,"song") - if strings.HasPrefix(EnhancedHls_m3u8, "http"){ - manifest.Attributes.ExtendedAssetUrls.EnhancedHls=EnhancedHls_m3u8 + EnhancedHls_m3u8, err := checkM3u8(track.ID, "song") + if strings.HasPrefix(EnhancedHls_m3u8, "http") { + manifest.Attributes.ExtendedAssetUrls.EnhancedHls = EnhancedHls_m3u8 } var Quality string - if strings.Contains(config.SongFileFormat, "Quality"){ - Quality,err = extractMediaQuality(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) + if strings.Contains(config.SongFileFormat, "Quality") { + Quality, err = extractMediaQuality(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) if err != nil { fmt.Println("Failed to extract quality from manifest.\n", err) continue } } stringsToJoin := []string{} - if track.Attributes.IsAppleDigitalMaster{ - if config.AppleMasterChoice != ""{ + if track.Attributes.IsAppleDigitalMaster { + if config.AppleMasterChoice != "" { stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) } } - if track.Attributes.ContentRating=="explicit"{ - if config.ExplicitChoice != ""{ + if track.Attributes.ContentRating == "explicit" { + if config.ExplicitChoice != "" { stringsToJoin = append(stringsToJoin, config.ExplicitChoice) } } - if track.Attributes.ContentRating=="clean"{ - if config.CleanChoice != ""{ + if track.Attributes.ContentRating == "clean" { + if config.CleanChoice != "" { stringsToJoin = append(stringsToJoin, config.CleanChoice) } } @@ -1320,7 +1323,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro "{DiscNumber}", fmt.Sprintf("%0d", track.Attributes.DiscNumber), "{TrackNumber}", fmt.Sprintf("%0d", track.Attributes.TrackNumber), "{Quality}", Quality, - "{Tag}",Tag_string, + "{Tag}", Tag_string, "{Codec}", "ALAC", ).Replace(config.SongFileFormat) fmt.Println(songName) @@ -1358,7 +1361,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro oktrackNum += 1 continue } - + trackUrl, keys, err := extractMedia(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) if err != nil { fmt.Println("Failed to extract info from manifest.\n", err) @@ -1390,22 +1393,22 @@ func rip(albumId string, token string, storefront string, userToken string) erro tags := []string{ fmt.Sprintf("lyrics=%s", lrc), } - if track.Attributes.ContentRating=="explicit"{ + if track.Attributes.ContentRating == "explicit" { tags = append(tags, "rating=1") - }else if track.Attributes.ContentRating=="clean"{ + } else if track.Attributes.ContentRating == "clean" { tags = append(tags, "rating=2") - }else{ + } else { tags = append(tags, "rating=0") } if config.EmbedCover { if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { - err = writeCover(sanAlbumFolder,track.ID, track.Attributes.Artwork.URL) + err = writeCover(sanAlbumFolder, track.ID, track.Attributes.Artwork.URL) if err != nil { fmt.Println("Failed to write cover.") } - tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,track.ID, config.CoverFormat)) - }else{ - tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,"cover", config.CoverFormat)) + tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)) + } else { + tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, "cover", config.CoverFormat)) } } tagsString := strings.Join(tags, ":") @@ -1415,8 +1418,8 @@ func rip(albumId string, token string, storefront string, userToken string) erro continue } if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { - if err := os.Remove(fmt.Sprintf("%s/%s.%s", sanAlbumFolder,track.ID, config.CoverFormat)); err != nil { - fmt.Printf("Error deleting file: %s/%s.%s\n", sanAlbumFolder,track.ID, config.CoverFormat) + if err := os.Remove(fmt.Sprintf("%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)); err != nil { + fmt.Printf("Error deleting file: %s/%s.%s\n", sanAlbumFolder, track.ID, config.CoverFormat) continue } } @@ -1517,12 +1520,12 @@ func conventTTMLToLRC(ttml string) (string, error) { return strings.Join(lrcLines, "\n"), nil } -func checkM3u8(b string,f string) (string, error) { +func checkM3u8(b string, f string) (string, error) { var EnhancedHls string - if config.Check != ""{ - config.Check=strings.TrimSpace(config.Check) + if config.Check != "" { + config.Check = strings.TrimSpace(config.Check) if strings.HasSuffix(config.Check, "txt") { - txtpath=config.Check + txtpath = config.Check } if strings.HasPrefix(config.Check, "http") { req, err := http.NewRequest("GET", config.Check, nil) @@ -1544,8 +1547,8 @@ func checkM3u8(b string,f string) (string, error) { if err != nil { fmt.Println(err) } - if string(Checkbody) != "no_found"{ - EnhancedHls=string(Checkbody) + if string(Checkbody) != "no_found" { + EnhancedHls = string(Checkbody) fmt.Println("Found m3u8 from API") } else { if config.ForceApi { @@ -1555,14 +1558,14 @@ func checkM3u8(b string,f string) (string, error) { } } } - if config.GetM3u8FromDevice{ + if config.GetM3u8FromDevice { adamID := b conn, err := net.Dial("tcp", "127.0.0.1:20020") if err != nil { fmt.Println("Error connecting to device:", err) } defer conn.Close() - if f =="song"{ + if f == "song" { fmt.Println("Connected to device") } @@ -1588,10 +1591,10 @@ func checkM3u8(b string,f string) (string, error) { } // Trim any newline characters from the response - + response = bytes.TrimSpace(response) if len(response) > 0 { - if f =="song"{ + if f == "song" { fmt.Println("Received URL:", string(response)) } EnhancedHls = string(response) @@ -1611,7 +1614,7 @@ func checkM3u8(b string,f string) (string, error) { if strings.HasPrefix(line, b) { parts := strings.SplitN(line, ",", 2) if len(parts) == 2 { - EnhancedHls=parts[1] + EnhancedHls = parts[1] fmt.Println("Found m3u8 from txt") } } @@ -1650,16 +1653,16 @@ func extractMediaQuality(b string) (string, error) { if variant.Codecs == "alac" { split := strings.Split(variant.Audio, "-") length := len(split) - length_int,err := strconv.Atoi(split[length-2]) + length_int, err := strconv.Atoi(split[length-2]) if err != nil { return "", err } - if length_int <= config.AlacMax{ - HZ,err:=strconv.Atoi(split[length-2]) + if length_int <= config.AlacMax { + HZ, err := strconv.Atoi(split[length-2]) if err != nil { fmt.Println(err) } - KHZ:=float64(HZ) / 1000.0 + KHZ := float64(HZ) / 1000.0 Quality = fmt.Sprintf("%sB-%.1fkHz", split[length-1], KHZ) break } @@ -1699,11 +1702,11 @@ func extractMedia(b string) (string, []string, error) { if variant.Codecs == "alac" { split := strings.Split(variant.Audio, "-") length := len(split) - length_int,err := strconv.Atoi(split[length-2]) + length_int, err := strconv.Atoi(split[length-2]) if err != nil { return "", nil, err } - if length_int <= config.AlacMax{ + if length_int <= config.AlacMax { fmt.Printf("%s-bit / %s Hz\n", split[length-1], split[length-2]) streamUrlTemp, err := masterUrl.Parse(variant.URI) if err != nil { @@ -1972,32 +1975,32 @@ type SongAttributes struct { ExtendedAssetUrls struct { EnhancedHls string `json:"enhancedHls"` } `json:"extendedAssetUrls"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AlbumName string `json:"albumName"` - TrackNumber int `json:"trackNumber"` - ComposerName string `json:"composerName"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AlbumName string `json:"albumName"` + TrackNumber int `json:"trackNumber"` + ComposerName string `json:"composerName"` } type AlbumAttributes struct { - ArtistName string `json:"artistName"` - IsSingle bool `json:"isSingle"` - IsComplete bool `json:"isComplete"` - GenreNames []string `json:"genreNames"` - TrackCount int `json:"trackCount"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - RecordLabel string `json:"recordLabel"` - Upc string `json:"upc"` - Copyright string `json:"copyright"` - IsCompilation bool `json:"isCompilation"` + ArtistName string `json:"artistName"` + IsSingle bool `json:"isSingle"` + IsComplete bool `json:"isComplete"` + GenreNames []string `json:"genreNames"` + TrackCount int `json:"trackCount"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + RecordLabel string `json:"recordLabel"` + Upc string `json:"upc"` + Copyright string `json:"copyright"` + IsCompilation bool `json:"isCompilation"` } type SongData struct { @@ -2159,22 +2162,22 @@ type AutoGenerated struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - IsSingle bool `json:"isSingle"` - URL string `json:"url"` - IsComplete bool `json:"isComplete"` - GenreNames []string `json:"genreNames"` - TrackCount int `json:"trackCount"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - RecordLabel string `json:"recordLabel"` - Upc string `json:"upc"` - AudioTraits []string `json:"audioTraits"` - Copyright string `json:"copyright"` - PlayParams struct { + ArtistName string `json:"artistName"` + IsSingle bool `json:"isSingle"` + URL string `json:"url"` + IsComplete bool `json:"isComplete"` + GenreNames []string `json:"genreNames"` + TrackCount int `json:"trackCount"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + RecordLabel string `json:"recordLabel"` + Upc string `json:"upc"` + AudioTraits []string `json:"audioTraits"` + Copyright string `json:"copyright"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` @@ -2217,22 +2220,22 @@ type AutoGenerated struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - URL string `json:"url"` - DiscNumber int `json:"discNumber"` - GenreNames []string `json:"genreNames"` - HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - DurationInMillis int `json:"durationInMillis"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AudioTraits []string `json:"audioTraits"` - HasLyrics bool `json:"hasLyrics"` - AlbumName string `json:"albumName"` - PlayParams struct { + ArtistName string `json:"artistName"` + URL string `json:"url"` + DiscNumber int `json:"discNumber"` + GenreNames []string `json:"genreNames"` + HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + DurationInMillis int `json:"durationInMillis"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AudioTraits []string `json:"audioTraits"` + HasLyrics bool `json:"hasLyrics"` + AlbumName string `json:"albumName"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` @@ -2280,22 +2283,22 @@ type AutoGeneratedTrack struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - URL string `json:"url"` - DiscNumber int `json:"discNumber"` - GenreNames []string `json:"genreNames"` - HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - DurationInMillis int `json:"durationInMillis"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AudioTraits []string `json:"audioTraits"` - HasLyrics bool `json:"hasLyrics"` - AlbumName string `json:"albumName"` - PlayParams struct { + ArtistName string `json:"artistName"` + URL string `json:"url"` + DiscNumber int `json:"discNumber"` + GenreNames []string `json:"genreNames"` + HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + DurationInMillis int `json:"durationInMillis"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AudioTraits []string `json:"audioTraits"` + HasLyrics bool `json:"hasLyrics"` + AlbumName string `json:"albumName"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` diff --git a/main_atmos.go b/main_atmos.go index 2aedb86..37d984a 100644 --- a/main_atmos.go +++ b/main_atmos.go @@ -4,10 +4,8 @@ import ( "bytes" "encoding/binary" "encoding/json" - "gopkg.in/yaml.v2" "errors" "fmt" - "github.com/beevik/etree" "io" "io/ioutil" "math" @@ -23,6 +21,9 @@ import ( "strings" "time" + "github.com/beevik/etree" + "gopkg.in/yaml.v2" + "github.com/abema/go-mp4" "github.com/grafov/m3u8" ) @@ -35,25 +36,26 @@ const ( var ( forbiddenNames = regexp.MustCompile(`[/\\<>:"|?*]`) ) + type Config struct { - MediaUserToken string `yaml:"media-user-token"` - SaveLrcFile bool `yaml:"save-lrc-file"` - EmbedLrc bool `yaml:"embed-lrc"` - EmbedCover bool `yaml:"embed-cover"` - CoverSize string `yaml:"cover-size"` - CoverFormat string `yaml:"cover-format"` - AlacSaveFolder string `yaml:"alac-save-folder"` - AtmosSaveFolder string `yaml:"atmos-save-folder"` - AlbumFolderFormat string `yaml:"album-folder-format"` - PlaylistFolderFormat string `yaml:"playlist-folder-format"` + MediaUserToken string `yaml:"media-user-token"` + SaveLrcFile bool `yaml:"save-lrc-file"` + EmbedLrc bool `yaml:"embed-lrc"` + EmbedCover bool `yaml:"embed-cover"` + CoverSize string `yaml:"cover-size"` + CoverFormat string `yaml:"cover-format"` + AlacSaveFolder string `yaml:"alac-save-folder"` + AtmosSaveFolder string `yaml:"atmos-save-folder"` + AlbumFolderFormat string `yaml:"album-folder-format"` + PlaylistFolderFormat string `yaml:"playlist-folder-format"` ArtistFolderFormat string `yaml:"artist-folder-format"` - SongFileFormat string `yaml:"song-file-format"` - ExplicitChoice string `yaml:"explicit-choice"` - CleanChoice string `yaml:"clean-choice"` - AppleMasterChoice string `yaml:"apple-master-choice"` - AtmosMax int `yaml:"atmos-max"` - UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` - DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` + SongFileFormat string `yaml:"song-file-format"` + ExplicitChoice string `yaml:"explicit-choice"` + CleanChoice string `yaml:"clean-choice"` + AppleMasterChoice string `yaml:"apple-master-choice"` + AtmosMax int `yaml:"atmos-max"` + UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` + DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` } var config Config @@ -73,17 +75,17 @@ type SongInfo struct { } func loadConfig() error { - // 读取config.yaml文件内容 - data, err := ioutil.ReadFile("config.yaml") - if err != nil { - return err - } - // 将yaml解析到config变量中 - err = yaml.Unmarshal(data, &config) - if err != nil { - return err - } - return nil + // 读取config.yaml文件内容 + data, err := ioutil.ReadFile("config.yaml") + if err != nil { + return err + } + // 将yaml解析到config变量中 + err = yaml.Unmarshal(data, &config) + if err != nil { + return err + } + return nil } func (s *SongInfo) Duration() (ret uint64) { @@ -1063,8 +1065,8 @@ func getSongLyrics(songId string, storefront string, token string, userToken str } } -func writeCover(sanAlbumFolder,name string, url string) error { - covPath := filepath.Join(sanAlbumFolder, name+"." + config.CoverFormat) +func writeCover(sanAlbumFolder, name string, url string) error { + covPath := filepath.Join(sanAlbumFolder, name+"."+config.CoverFormat) exists, err := fileExists(covPath) if err != nil { fmt.Println("Failed to check if cover exists.") @@ -1125,13 +1127,13 @@ func rip(albumId string, token string, storefront string, userToken string) erro return err } var singerFoldername string - if config.ArtistFolderFormat != ""{ + if config.ArtistFolderFormat != "" { if strings.Contains(albumId, "pl.") { singerFoldername = strings.NewReplacer( "{ArtistName}", "Apple Music", "{ArtistId}", "", ).Replace(config.ArtistFolderFormat) - }else{ + } else { singerFoldername = strings.NewReplacer( "{ArtistName}", meta.Data[0].Attributes.ArtistName, "{ArtistId}", meta.Data[0].Relationships.Artists.Data[0].ID, @@ -1145,46 +1147,45 @@ func rip(albumId string, token string, storefront string, userToken string) erro } singerFolder := filepath.Join(config.AtmosSaveFolder, forbiddenNames.ReplaceAllString(singerFoldername, "_")) stringsToJoin := []string{} - if meta.Data[0].Attributes.IsAppleDigitalMaster{ - if config.AppleMasterChoice != ""{ + if meta.Data[0].Attributes.IsAppleDigitalMaster || meta.Data[0].Attributes.IsMasteredForItunes { + if config.AppleMasterChoice != "" { stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) } } - if meta.Data[0].Attributes.ContentRating=="explicit"{ - if config.ExplicitChoice != ""{ + if meta.Data[0].Attributes.ContentRating == "explicit" { + if config.ExplicitChoice != "" { stringsToJoin = append(stringsToJoin, config.ExplicitChoice) } } - if meta.Data[0].Attributes.ContentRating=="clean"{ - if config.CleanChoice != ""{ + if meta.Data[0].Attributes.ContentRating == "clean" { + if config.CleanChoice != "" { stringsToJoin = append(stringsToJoin, config.CleanChoice) } } Tag_string := strings.Join(stringsToJoin, " ") var albumFolder string - Quality:=fmt.Sprintf("%dkbps", config.AtmosMax-2000) + Quality := fmt.Sprintf("%dkbps", config.AtmosMax-2000) if strings.Contains(albumId, "pl.") { albumFolder = strings.NewReplacer( "{ArtistName}", "Apple Music", "{PlaylistName}", meta.Data[0].Attributes.Name, "{PlaylistId}", albumId, - "{Quality}",Quality, + "{Quality}", Quality, "{Codec}", "Atmos", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.PlaylistFolderFormat) - }else{ + } else { albumFolder = strings.NewReplacer( "{ReleaseDate}", meta.Data[0].Attributes.ReleaseDate, "{ReleaseYear}", meta.Data[0].Attributes.ReleaseDate[:4], "{ArtistName}", meta.Data[0].Attributes.ArtistName, "{AlbumName}", meta.Data[0].Attributes.Name, "{UPC}", meta.Data[0].Attributes.Upc, - "{RecordLabel}", meta.Data[0].Attributes.RecordLabel, "{Copyright}", meta.Data[0].Attributes.Copyright, "{AlbumId}", albumId, - "{Quality}",Quality, + "{Quality}", Quality, "{Codec}", "Atmos", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.AlbumFolderFormat) } if strings.HasSuffix(albumFolder, ".") { @@ -1194,7 +1195,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_")) os.MkdirAll(sanAlbumFolder, os.ModePerm) fmt.Println(albumFolder) - err = writeCover(sanAlbumFolder,"cover", meta.Data[0].Attributes.Artwork.URL) + err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL) if err != nil { fmt.Println("Failed to write cover.") } @@ -1212,20 +1213,20 @@ func rip(albumId string, token string, storefront string, userToken string) erro fmt.Println("Unavailable in ALAC.") continue } - + stringsToJoin := []string{} - if track.Attributes.IsAppleDigitalMaster{ - if config.AppleMasterChoice != ""{ + if track.Attributes.IsAppleDigitalMaster { + if config.AppleMasterChoice != "" { stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) } } - if track.Attributes.ContentRating=="explicit"{ - if config.ExplicitChoice != ""{ + if track.Attributes.ContentRating == "explicit" { + if config.ExplicitChoice != "" { stringsToJoin = append(stringsToJoin, config.ExplicitChoice) } } - if track.Attributes.ContentRating=="clean"{ - if config.CleanChoice != ""{ + if track.Attributes.ContentRating == "clean" { + if config.CleanChoice != "" { stringsToJoin = append(stringsToJoin, config.CleanChoice) } } @@ -1236,9 +1237,9 @@ func rip(albumId string, token string, storefront string, userToken string) erro "{SongName}", track.Attributes.Name, "{DiscNumber}", string(track.Attributes.DiscNumber), "{TrackNumber}", fmt.Sprintf("%02d", track.Attributes.TrackNumber), - "{Quality}",Quality, + "{Quality}", Quality, "{Codec}", "Atmos", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.SongFileFormat) fmt.Println(songName) filename := fmt.Sprintf("%s.ec3", forbiddenNames.ReplaceAllString(songName, "_")) @@ -1327,13 +1328,13 @@ func rip(albumId string, token string, storefront string, userToken string) erro } if config.EmbedCover { if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { - err = writeCover(sanAlbumFolder,track.ID, track.Attributes.Artwork.URL) + err = writeCover(sanAlbumFolder, track.ID, track.Attributes.Artwork.URL) if err != nil { fmt.Println("Failed to write cover.") } - tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,track.ID, config.CoverFormat)) - }else{ - tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,"cover", config.CoverFormat)) + tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)) + } else { + tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, "cover", config.CoverFormat)) } } if strings.Contains(albumId, "pl.") && !config.UseSongInfoForPlaylist { @@ -1341,17 +1342,17 @@ func rip(albumId string, token string, storefront string, userToken string) erro tags = append(tags, fmt.Sprintf("track=%s", trackNum)) tags = append(tags, fmt.Sprintf("tracknum=%d/%d", trackNum, trackTotal)) tags = append(tags, fmt.Sprintf("album=%s", meta.Data[0].Attributes.Name)) - }else{ - 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)) + } else { + 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=%s", 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=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName)) } - if track.Attributes.ContentRating=="explicit"{ + if track.Attributes.ContentRating == "explicit" { tags = append(tags, "rating=1") - }else if track.Attributes.ContentRating=="clean"{ + } else if track.Attributes.ContentRating == "clean" { tags = append(tags, "rating=2") - }else{ + } else { tags = append(tags, "rating=0") } tagsString := strings.Join(tags, ":") @@ -1367,8 +1368,8 @@ func rip(albumId string, token string, storefront string, userToken string) erro continue } if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { - if err := os.Remove(fmt.Sprintf("%s/%s.%s", sanAlbumFolder,track.ID, config.CoverFormat)); err != nil { - fmt.Printf("Error deleting file: %s/%s.%s\n", sanAlbumFolder,track.ID, config.CoverFormat) + if err := os.Remove(fmt.Sprintf("%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)); err != nil { + fmt.Printf("Error deleting file: %s/%s.%s\n", sanAlbumFolder, track.ID, config.CoverFormat) continue } } @@ -1491,11 +1492,11 @@ func extractMedia(b string) (string, []string, error) { if variant.Codecs == "ec-3" { split := strings.Split(variant.Audio, "-") length := len(split) - length_int,err := strconv.Atoi(split[length-1]) + length_int, err := strconv.Atoi(split[length-1]) if err != nil { return "", nil, err } - if length_int <= config.AtmosMax{ + if length_int <= config.AtmosMax { fmt.Printf("%s\n", variant.Audio) streamUrlTemp, err := masterUrl.Parse(variant.URI) if err != nil { @@ -1765,32 +1766,32 @@ type SongAttributes struct { ExtendedAssetUrls struct { EnhancedHls string `json:"enhancedHls"` } `json:"extendedAssetUrls"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AlbumName string `json:"albumName"` - TrackNumber int `json:"trackNumber"` - ComposerName string `json:"composerName"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AlbumName string `json:"albumName"` + TrackNumber int `json:"trackNumber"` + ComposerName string `json:"composerName"` } type AlbumAttributes struct { - ArtistName string `json:"artistName"` - IsSingle bool `json:"isSingle"` - IsComplete bool `json:"isComplete"` - GenreNames []string `json:"genreNames"` - TrackCount int `json:"trackCount"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - RecordLabel string `json:"recordLabel"` - Upc string `json:"upc"` - Copyright string `json:"copyright"` - IsCompilation bool `json:"isCompilation"` + ArtistName string `json:"artistName"` + IsSingle bool `json:"isSingle"` + IsComplete bool `json:"isComplete"` + GenreNames []string `json:"genreNames"` + TrackCount int `json:"trackCount"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + RecordLabel string `json:"recordLabel"` + Upc string `json:"upc"` + Copyright string `json:"copyright"` + IsCompilation bool `json:"isCompilation"` } type SongData struct { @@ -1952,22 +1953,22 @@ type AutoGenerated struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - IsSingle bool `json:"isSingle"` - URL string `json:"url"` - IsComplete bool `json:"isComplete"` - GenreNames []string `json:"genreNames"` - TrackCount int `json:"trackCount"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - RecordLabel string `json:"recordLabel"` - Upc string `json:"upc"` - AudioTraits []string `json:"audioTraits"` - Copyright string `json:"copyright"` - PlayParams struct { + ArtistName string `json:"artistName"` + IsSingle bool `json:"isSingle"` + URL string `json:"url"` + IsComplete bool `json:"isComplete"` + GenreNames []string `json:"genreNames"` + TrackCount int `json:"trackCount"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + RecordLabel string `json:"recordLabel"` + Upc string `json:"upc"` + AudioTraits []string `json:"audioTraits"` + Copyright string `json:"copyright"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` @@ -2010,22 +2011,22 @@ type AutoGenerated struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - URL string `json:"url"` - DiscNumber int `json:"discNumber"` - GenreNames []string `json:"genreNames"` - HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - DurationInMillis int `json:"durationInMillis"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AudioTraits []string `json:"audioTraits"` - HasLyrics bool `json:"hasLyrics"` - AlbumName string `json:"albumName"` - PlayParams struct { + ArtistName string `json:"artistName"` + URL string `json:"url"` + DiscNumber int `json:"discNumber"` + GenreNames []string `json:"genreNames"` + HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + DurationInMillis int `json:"durationInMillis"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AudioTraits []string `json:"audioTraits"` + HasLyrics bool `json:"hasLyrics"` + AlbumName string `json:"albumName"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` @@ -2073,22 +2074,22 @@ type AutoGeneratedTrack struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - URL string `json:"url"` - DiscNumber int `json:"discNumber"` - GenreNames []string `json:"genreNames"` - HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - DurationInMillis int `json:"durationInMillis"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AudioTraits []string `json:"audioTraits"` - HasLyrics bool `json:"hasLyrics"` - AlbumName string `json:"albumName"` - PlayParams struct { + ArtistName string `json:"artistName"` + URL string `json:"url"` + DiscNumber int `json:"discNumber"` + GenreNames []string `json:"genreNames"` + HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + DurationInMillis int `json:"durationInMillis"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AudioTraits []string `json:"audioTraits"` + HasLyrics bool `json:"hasLyrics"` + AlbumName string `json:"albumName"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` diff --git a/main_select.go b/main_select.go index 30bccb1..9967bfb 100644 --- a/main_select.go +++ b/main_select.go @@ -5,10 +5,8 @@ import ( "bytes" "encoding/binary" "encoding/json" - "gopkg.in/yaml.v2" "errors" "fmt" - "github.com/beevik/etree" "io" "io/ioutil" "math" @@ -24,6 +22,9 @@ import ( "strings" "time" + "github.com/beevik/etree" + "gopkg.in/yaml.v2" + "github.com/abema/go-mp4" "github.com/grafov/m3u8" ) @@ -36,25 +37,26 @@ const ( var ( forbiddenNames = regexp.MustCompile(`[/\\<>:"|?*]`) ) + type Config struct { - MediaUserToken string `yaml:"media-user-token"` - SaveLrcFile bool `yaml:"save-lrc-file"` - EmbedLrc bool `yaml:"embed-lrc"` - EmbedCover bool `yaml:"embed-cover"` - CoverSize string `yaml:"cover-size"` - CoverFormat string `yaml:"cover-format"` - AlacSaveFolder string `yaml:"alac-save-folder"` - AtmosSaveFolder string `yaml:"atmos-save-folder"` - AlbumFolderFormat string `yaml:"album-folder-format"` - PlaylistFolderFormat string `yaml:"playlist-folder-format"` + MediaUserToken string `yaml:"media-user-token"` + SaveLrcFile bool `yaml:"save-lrc-file"` + EmbedLrc bool `yaml:"embed-lrc"` + EmbedCover bool `yaml:"embed-cover"` + CoverSize string `yaml:"cover-size"` + CoverFormat string `yaml:"cover-format"` + AlacSaveFolder string `yaml:"alac-save-folder"` + AtmosSaveFolder string `yaml:"atmos-save-folder"` + AlbumFolderFormat string `yaml:"album-folder-format"` + PlaylistFolderFormat string `yaml:"playlist-folder-format"` ArtistFolderFormat string `yaml:"artist-folder-format"` - SongFileFormat string `yaml:"song-file-format"` - ExplicitChoice string `yaml:"explicit-choice"` - CleanChoice string `yaml:"clean-choice"` - AppleMasterChoice string `yaml:"apple-master-choice"` - AlacMax int `yaml:"alac-max"` - UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` - DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` + SongFileFormat string `yaml:"song-file-format"` + ExplicitChoice string `yaml:"explicit-choice"` + CleanChoice string `yaml:"clean-choice"` + AppleMasterChoice string `yaml:"apple-master-choice"` + AlacMax int `yaml:"alac-max"` + UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` + DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` } var config Config @@ -72,17 +74,17 @@ type SongInfo struct { } func loadConfig() error { - // 读取config.yaml文件内容 - data, err := ioutil.ReadFile("config.yaml") - if err != nil { - return err - } - // 将yaml解析到config变量中 - err = yaml.Unmarshal(data, &config) - if err != nil { - return err - } - return nil + // 读取config.yaml文件内容 + data, err := ioutil.ReadFile("config.yaml") + if err != nil { + return err + } + // 将yaml解析到config变量中 + err = yaml.Unmarshal(data, &config) + if err != nil { + return err + } + return nil } func (s *SongInfo) Duration() (ret uint64) { @@ -685,10 +687,10 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t if err != nil { return err } - AlbumName:=meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName - if strings.Contains(meta.Data[0].ID, "pl."){ + AlbumName := meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName + if strings.Contains(meta.Data[0].ID, "pl.") { if !config.UseSongInfoForPlaylist { - AlbumName=meta.Data[0].Attributes.Name + AlbumName = meta.Data[0].Attributes.Name } } err = addMeta(mp4.BoxType{'\251', 'a', 'l', 'b'}, AlbumName) @@ -799,13 +801,13 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t if err != nil { return err } - - if !strings.Contains(meta.Data[0].ID, "pl."){ + + if !strings.Contains(meta.Data[0].ID, "pl.") { plID, err := strconv.ParseUint(meta.Data[0].ID, 10, 32) if err != nil { return err } - + err = addMeta(mp4.BoxType{'p', 'l', 'I', 'D'}, uint32(plID)) if err != nil { return err @@ -830,8 +832,8 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t binary.BigEndian.PutUint32(trkn, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.TrackNumber)) binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) binary.BigEndian.PutUint32(disk, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.DiscNumber)) - binary.BigEndian.PutUint16(disk[4:], uint16(meta.Data[0].Relationships.Tracks.Data[trackTotal - 1].Attributes.DiscNumber)) - if strings.Contains(meta.Data[0].ID, "pl."){ + binary.BigEndian.PutUint16(disk[4:], uint16(meta.Data[0].Relationships.Tracks.Data[trackTotal-1].Attributes.DiscNumber)) + if strings.Contains(meta.Data[0].ID, "pl.") { if !config.UseSongInfoForPlaylist { binary.BigEndian.PutUint32(trkn, uint32(trackNum)) binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) @@ -1111,8 +1113,8 @@ func getSongLyrics(songId string, storefront string, token string, userToken str } } -func writeCover(sanAlbumFolder,name string, url string) error { - covPath := filepath.Join(sanAlbumFolder, name+"." + config.CoverFormat) +func writeCover(sanAlbumFolder, name string, url string) error { + covPath := filepath.Join(sanAlbumFolder, name+"."+config.CoverFormat) exists, err := fileExists(covPath) if err != nil { fmt.Println("Failed to check if cover exists.") @@ -1183,13 +1185,13 @@ func rip(albumId string, token string, storefront string, userToken string) erro return err } var singerFoldername string - if config.ArtistFolderFormat != ""{ + if config.ArtistFolderFormat != "" { if strings.Contains(albumId, "pl.") { singerFoldername = strings.NewReplacer( "{ArtistName}", "Apple Music", "{ArtistId}", "", ).Replace(config.ArtistFolderFormat) - }else{ + } else { singerFoldername = strings.NewReplacer( "{ArtistName}", meta.Data[0].Attributes.ArtistName, "{ArtistId}", meta.Data[0].Relationships.Artists.Data[0].ID, @@ -1203,18 +1205,18 @@ func rip(albumId string, token string, storefront string, userToken string) erro } singerFolder := filepath.Join(config.AlacSaveFolder, forbiddenNames.ReplaceAllString(singerFoldername, "_")) stringsToJoin := []string{} - if meta.Data[0].Attributes.IsAppleDigitalMaster{ - if config.AppleMasterChoice != ""{ + if meta.Data[0].Attributes.IsAppleDigitalMaster || meta.Data[0].Attributes.IsMasteredForItunes { + if config.AppleMasterChoice != "" { stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) } } - if meta.Data[0].Attributes.ContentRating=="explicit"{ - if config.ExplicitChoice != ""{ + if meta.Data[0].Attributes.ContentRating == "explicit" { + if config.ExplicitChoice != "" { stringsToJoin = append(stringsToJoin, config.ExplicitChoice) } } - if meta.Data[0].Attributes.ContentRating=="clean"{ - if config.CleanChoice != ""{ + if meta.Data[0].Attributes.ContentRating == "clean" { + if config.CleanChoice != "" { stringsToJoin = append(stringsToJoin, config.CleanChoice) } } @@ -1227,8 +1229,8 @@ func rip(albumId string, token string, storefront string, userToken string) erro fmt.Println("Unavailable in ALAC.") } var Quality string - if strings.Contains(config.AlbumFolderFormat, "Quality"){ - Quality,err = extractMediaQuality(manifest1.Attributes.ExtendedAssetUrls.EnhancedHls) + if strings.Contains(config.AlbumFolderFormat, "Quality") { + Quality, err = extractMediaQuality(manifest1.Attributes.ExtendedAssetUrls.EnhancedHls) if err != nil { fmt.Println("Failed to extract quality from manifest.\n", err) } @@ -1239,23 +1241,22 @@ func rip(albumId string, token string, storefront string, userToken string) erro "{ArtistName}", "Apple Music", "{PlaylistName}", meta.Data[0].Attributes.Name, "{PlaylistId}", albumId, - "{Quality}",Quality, + "{Quality}", Quality, "{Codec}", "ALAC", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.PlaylistFolderFormat) - }else{ + } else { albumFolder = strings.NewReplacer( "{ReleaseDate}", meta.Data[0].Attributes.ReleaseDate, "{ReleaseYear}", meta.Data[0].Attributes.ReleaseDate[:4], "{ArtistName}", meta.Data[0].Attributes.ArtistName, "{AlbumName}", meta.Data[0].Attributes.Name, "{UPC}", meta.Data[0].Attributes.Upc, - "{RecordLabel}", meta.Data[0].Attributes.RecordLabel, "{Copyright}", meta.Data[0].Attributes.Copyright, "{AlbumId}", albumId, "{Quality}", Quality, "{Codec}", "ALAC", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.AlbumFolderFormat) } if strings.HasSuffix(albumFolder, ".") { @@ -1265,7 +1266,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_")) os.MkdirAll(sanAlbumFolder, os.ModePerm) fmt.Println(albumFolder) - err = writeCover(sanAlbumFolder,"cover", meta.Data[0].Attributes.Artwork.URL) + err = writeCover(sanAlbumFolder, "cover", meta.Data[0].Attributes.Artwork.URL) if err != nil { fmt.Println("Failed to write cover.") } @@ -1284,7 +1285,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro manually_txt := false m3u8_txt := "" if strings.Contains(input, "txt") { - m3u8_txt= strings.TrimSpace(input) + m3u8_txt = strings.TrimSpace(input) fmt.Print(m3u8_txt) strArr := make([]string, len(arr)) for i, num := range arr { @@ -1292,11 +1293,11 @@ func rip(albumId string, token string, storefront string, userToken string) erro } input = strings.Join(strArr, " ") manually_txt = true - } + } if strings.Contains(input, "#") { - input = strings.ReplaceAll(input, "#", "") + input = strings.ReplaceAll(input, "#", "") manually = true - } + } input = strings.TrimSpace(input) inputs := strings.Fields(input) for _, str := range inputs { @@ -1328,26 +1329,26 @@ func rip(albumId string, token string, storefront string, userToken string) erro continue } var Quality string - if strings.Contains(config.SongFileFormat, "Quality"){ - Quality,err = extractMediaQuality(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) + if strings.Contains(config.SongFileFormat, "Quality") { + Quality, err = extractMediaQuality(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) if err != nil { fmt.Println("Failed to extract quality from manifest.\n", err) continue } } stringsToJoin := []string{} - if track.Attributes.IsAppleDigitalMaster{ - if config.AppleMasterChoice != ""{ + if track.Attributes.IsAppleDigitalMaster { + if config.AppleMasterChoice != "" { stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) } } - if track.Attributes.ContentRating=="explicit"{ - if config.ExplicitChoice != ""{ + if track.Attributes.ContentRating == "explicit" { + if config.ExplicitChoice != "" { stringsToJoin = append(stringsToJoin, config.ExplicitChoice) } } - if track.Attributes.ContentRating=="clean"{ - if config.CleanChoice != ""{ + if track.Attributes.ContentRating == "clean" { + if config.CleanChoice != "" { stringsToJoin = append(stringsToJoin, config.CleanChoice) } } @@ -1358,9 +1359,9 @@ func rip(albumId string, token string, storefront string, userToken string) erro "{SongName}", track.Attributes.Name, "{DiscNumber}", string(track.Attributes.DiscNumber), "{TrackNumber}", fmt.Sprintf("%02d", track.Attributes.TrackNumber), - "{Quality}",Quality, + "{Quality}", Quality, "{Codec}", "ALAC", - "{Tag}",Tag_string, + "{Tag}", Tag_string, ).Replace(config.SongFileFormat) fmt.Println(songName) filename := fmt.Sprintf("%s.m4a", forbiddenNames.ReplaceAllString(songName, "_")) @@ -1408,7 +1409,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro if strings.HasPrefix(line, track.ID) { parts := strings.SplitN(line, ",", 2) if len(parts) == 2 { - manifest.Attributes.ExtendedAssetUrls.EnhancedHls=parts[1] + manifest.Attributes.ExtendedAssetUrls.EnhancedHls = parts[1] } } } @@ -1424,7 +1425,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro fmt.Println(err) } m3u8_url = strings.TrimSpace(m3u8_url) - manifest.Attributes.ExtendedAssetUrls.EnhancedHls=m3u8_url + manifest.Attributes.ExtendedAssetUrls.EnhancedHls = m3u8_url } trackUrl, keys, err := extractMedia(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) if err != nil { @@ -1459,21 +1460,21 @@ func rip(albumId string, token string, storefront string, userToken string) erro } if config.EmbedCover { if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { - err = writeCover(sanAlbumFolder,track.ID, track.Attributes.Artwork.URL) + err = writeCover(sanAlbumFolder, track.ID, track.Attributes.Artwork.URL) if err != nil { fmt.Println("Failed to write cover.") } - tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,track.ID, config.CoverFormat)) - }else{ - tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,"cover", config.CoverFormat)) + tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)) + } else { + tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, "cover", config.CoverFormat)) } } - - if track.Attributes.ContentRating=="explicit"{ + + if track.Attributes.ContentRating == "explicit" { tags = append(tags, "rating=1") - }else if track.Attributes.ContentRating=="clean"{ + } else if track.Attributes.ContentRating == "clean" { tags = append(tags, "rating=2") - }else{ + } else { tags = append(tags, "rating=0") } tagsString := strings.Join(tags, ":") @@ -1483,8 +1484,8 @@ func rip(albumId string, token string, storefront string, userToken string) erro continue } if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { - if err := os.Remove(fmt.Sprintf("%s/%s.%s", sanAlbumFolder,track.ID, config.CoverFormat)); err != nil { - fmt.Printf("Error deleting file: %s/%s.%s\n", sanAlbumFolder,track.ID, config.CoverFormat) + if err := os.Remove(fmt.Sprintf("%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)); err != nil { + fmt.Printf("Error deleting file: %s/%s.%s\n", sanAlbumFolder, track.ID, config.CoverFormat) continue } } @@ -1600,16 +1601,16 @@ func extractMediaQuality(b string) (string, error) { if variant.Codecs == "alac" { split := strings.Split(variant.Audio, "-") length := len(split) - length_int,err := strconv.Atoi(split[length-2]) + length_int, err := strconv.Atoi(split[length-2]) if err != nil { return "", err } - if length_int <= config.AlacMax{ - HZ,err:=strconv.Atoi(split[length-2]) + if length_int <= config.AlacMax { + HZ, err := strconv.Atoi(split[length-2]) if err != nil { fmt.Println(err) } - KHZ:=float64(HZ) / 1000.0 + KHZ := float64(HZ) / 1000.0 Quality = fmt.Sprintf("%sB-%.1fkHz", split[length-1], KHZ) break } @@ -1649,11 +1650,11 @@ func extractMedia(b string) (string, []string, error) { if variant.Codecs == "alac" { split := strings.Split(variant.Audio, "-") length := len(split) - length_int,err := strconv.Atoi(split[length-2]) + length_int, err := strconv.Atoi(split[length-2]) if err != nil { return "", nil, err } - if length_int <= config.AlacMax{ + if length_int <= config.AlacMax { fmt.Printf("%s-bit / %s Hz\n", split[length-1], split[length-2]) streamUrlTemp, err := masterUrl.Parse(variant.URI) if err != nil { @@ -1922,32 +1923,32 @@ type SongAttributes struct { ExtendedAssetUrls struct { EnhancedHls string `json:"enhancedHls"` } `json:"extendedAssetUrls"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AlbumName string `json:"albumName"` - TrackNumber int `json:"trackNumber"` - ComposerName string `json:"composerName"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AlbumName string `json:"albumName"` + TrackNumber int `json:"trackNumber"` + ComposerName string `json:"composerName"` } type AlbumAttributes struct { - ArtistName string `json:"artistName"` - IsSingle bool `json:"isSingle"` - IsComplete bool `json:"isComplete"` - GenreNames []string `json:"genreNames"` - TrackCount int `json:"trackCount"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - RecordLabel string `json:"recordLabel"` - Upc string `json:"upc"` - Copyright string `json:"copyright"` - IsCompilation bool `json:"isCompilation"` + ArtistName string `json:"artistName"` + IsSingle bool `json:"isSingle"` + IsComplete bool `json:"isComplete"` + GenreNames []string `json:"genreNames"` + TrackCount int `json:"trackCount"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + RecordLabel string `json:"recordLabel"` + Upc string `json:"upc"` + Copyright string `json:"copyright"` + IsCompilation bool `json:"isCompilation"` } type SongData struct { @@ -2109,22 +2110,22 @@ type AutoGenerated struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - IsSingle bool `json:"isSingle"` - URL string `json:"url"` - IsComplete bool `json:"isComplete"` - GenreNames []string `json:"genreNames"` - TrackCount int `json:"trackCount"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - RecordLabel string `json:"recordLabel"` - Upc string `json:"upc"` - AudioTraits []string `json:"audioTraits"` - Copyright string `json:"copyright"` - PlayParams struct { + ArtistName string `json:"artistName"` + IsSingle bool `json:"isSingle"` + URL string `json:"url"` + IsComplete bool `json:"isComplete"` + GenreNames []string `json:"genreNames"` + TrackCount int `json:"trackCount"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + RecordLabel string `json:"recordLabel"` + Upc string `json:"upc"` + AudioTraits []string `json:"audioTraits"` + Copyright string `json:"copyright"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` @@ -2167,22 +2168,22 @@ type AutoGenerated struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - URL string `json:"url"` - DiscNumber int `json:"discNumber"` - GenreNames []string `json:"genreNames"` - HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - DurationInMillis int `json:"durationInMillis"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AudioTraits []string `json:"audioTraits"` - HasLyrics bool `json:"hasLyrics"` - AlbumName string `json:"albumName"` - PlayParams struct { + ArtistName string `json:"artistName"` + URL string `json:"url"` + DiscNumber int `json:"discNumber"` + GenreNames []string `json:"genreNames"` + HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + DurationInMillis int `json:"durationInMillis"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AudioTraits []string `json:"audioTraits"` + HasLyrics bool `json:"hasLyrics"` + AlbumName string `json:"albumName"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"` @@ -2230,22 +2231,22 @@ type AutoGeneratedTrack struct { TextColor3 string `json:"textColor3"` TextColor4 string `json:"textColor4"` } `json:"artwork"` - ArtistName string `json:"artistName"` - URL string `json:"url"` - DiscNumber int `json:"discNumber"` - GenreNames []string `json:"genreNames"` - HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` - IsMasteredForItunes bool `json:"isMasteredForItunes"` - IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` - ContentRating string `json:"contentRating"` - DurationInMillis int `json:"durationInMillis"` - ReleaseDate string `json:"releaseDate"` - Name string `json:"name"` - Isrc string `json:"isrc"` - AudioTraits []string `json:"audioTraits"` - HasLyrics bool `json:"hasLyrics"` - AlbumName string `json:"albumName"` - PlayParams struct { + ArtistName string `json:"artistName"` + URL string `json:"url"` + DiscNumber int `json:"discNumber"` + GenreNames []string `json:"genreNames"` + HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` + IsMasteredForItunes bool `json:"isMasteredForItunes"` + IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` + ContentRating string `json:"contentRating"` + DurationInMillis int `json:"durationInMillis"` + ReleaseDate string `json:"releaseDate"` + Name string `json:"name"` + Isrc string `json:"isrc"` + AudioTraits []string `json:"audioTraits"` + HasLyrics bool `json:"hasLyrics"` + AlbumName string `json:"albumName"` + PlayParams struct { ID string `json:"id"` Kind string `json:"kind"` } `json:"playParams"`