|
|
@ -5,6 +5,7 @@ import ( |
|
|
|
"bytes" |
|
|
|
"bytes" |
|
|
|
"encoding/binary" |
|
|
|
"encoding/binary" |
|
|
|
"encoding/json" |
|
|
|
"encoding/json" |
|
|
|
|
|
|
|
"gopkg.in/yaml.v2" |
|
|
|
"errors" |
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
"fmt" |
|
|
|
"io" |
|
|
|
"io" |
|
|
@ -22,8 +23,6 @@ import ( |
|
|
|
"strings" |
|
|
|
"strings" |
|
|
|
"time" |
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
|
|
"gopkg.in/yaml.v2" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/abema/go-mp4" |
|
|
|
"github.com/abema/go-mp4" |
|
|
|
"github.com/beevik/etree" |
|
|
|
"github.com/beevik/etree" |
|
|
|
"github.com/grafov/m3u8" |
|
|
|
"github.com/grafov/m3u8" |
|
|
@ -37,29 +36,28 @@ const ( |
|
|
|
var ( |
|
|
|
var ( |
|
|
|
forbiddenNames = regexp.MustCompile(`[/\\<>:"|?*]`) |
|
|
|
forbiddenNames = regexp.MustCompile(`[/\\<>:"|?*]`) |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
type Config struct { |
|
|
|
type Config struct { |
|
|
|
MediaUserToken string `yaml:"media-user-token"` |
|
|
|
MediaUserToken string `yaml:"media-user-token"` |
|
|
|
SaveLrcFile bool `yaml:"save-lrc-file"` |
|
|
|
SaveLrcFile bool `yaml:"save-lrc-file"`
|
|
|
|
EmbedLrc bool `yaml:"embed-lrc"` |
|
|
|
EmbedLrc bool `yaml:"embed-lrc"` |
|
|
|
EmbedCover bool `yaml:"embed-cover"` |
|
|
|
EmbedCover bool `yaml:"embed-cover"` |
|
|
|
CoverSize string `yaml:"cover-size"` |
|
|
|
CoverSize string `yaml:"cover-size"` |
|
|
|
CoverFormat string `yaml:"cover-format"` |
|
|
|
CoverFormat string `yaml:"cover-format"` |
|
|
|
AlacSaveFolder string `yaml:"alac-save-folder"` |
|
|
|
AlacSaveFolder string `yaml:"alac-save-folder"` |
|
|
|
AtmosSaveFolder string `yaml:"atmos-save-folder"` |
|
|
|
AtmosSaveFolder string `yaml:"atmos-save-folder"` |
|
|
|
AlbumFolderFormat string `yaml:"album-folder-format"` |
|
|
|
AlbumFolderFormat string `yaml:"album-folder-format"` |
|
|
|
PlaylistFolderFormat string `yaml:"playlist-folder-format"` |
|
|
|
PlaylistFolderFormat string `yaml:"playlist-folder-format"` |
|
|
|
ArtistFolderFormat string `yaml:"artist-folder-format"` |
|
|
|
ArtistFolderFormat string `yaml:"artist-folder-format"` |
|
|
|
SongFileFormat string `yaml:"song-file-format"` |
|
|
|
SongFileFormat string `yaml:"song-file-format"` |
|
|
|
ExplicitChoice string `yaml:"explicit-choice"` |
|
|
|
ExplicitChoice string `yaml:"explicit-choice"` |
|
|
|
CleanChoice string `yaml:"clean-choice"` |
|
|
|
CleanChoice string `yaml:"clean-choice"` |
|
|
|
AppleMasterChoice string `yaml:"apple-master-choice"` |
|
|
|
AppleMasterChoice string `yaml:"apple-master-choice"` |
|
|
|
ForceApi bool `yaml:"force-api"` |
|
|
|
ForceApi bool `yaml:"force-api"` |
|
|
|
Check string `yaml:"check"` |
|
|
|
Check string `yaml:"check"` |
|
|
|
GetM3u8FromDevice bool `yaml:"get-m3u8-from-device"` |
|
|
|
GetM3u8FromDevice bool `yaml:"get-m3u8-from-device"` |
|
|
|
AlacMax int `yaml:"alac-max"` |
|
|
|
AlacMax int `yaml:"alac-max"` |
|
|
|
UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` |
|
|
|
UseSongInfoForPlaylist bool `yaml:"use-songinfo-for-playlist"` |
|
|
|
DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` |
|
|
|
DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"` |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var config Config |
|
|
|
var config Config |
|
|
@ -78,19 +76,18 @@ type SongInfo struct { |
|
|
|
alacParam *Alac |
|
|
|
alacParam *Alac |
|
|
|
samples []SampleInfo |
|
|
|
samples []SampleInfo |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func loadConfig() error { |
|
|
|
func loadConfig() error { |
|
|
|
// 读取config.yaml文件内容
|
|
|
|
// 读取config.yaml文件内容
|
|
|
|
data, err := ioutil.ReadFile("config.yaml") |
|
|
|
data, err := ioutil.ReadFile("config.yaml") |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
// 将yaml解析到config变量中
|
|
|
|
// 将yaml解析到config变量中
|
|
|
|
err = yaml.Unmarshal(data, &config) |
|
|
|
err = yaml.Unmarshal(data, &config) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *SongInfo) Duration() (ret uint64) { |
|
|
|
func (s *SongInfo) Duration() (ret uint64) { |
|
|
@ -693,10 +690,10 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
AlbumName := meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName |
|
|
|
AlbumName:=meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName |
|
|
|
if strings.Contains(meta.Data[0].ID, "pl.") { |
|
|
|
if strings.Contains(meta.Data[0].ID, "pl."){ |
|
|
|
if !config.UseSongInfoForPlaylist { |
|
|
|
if !config.UseSongInfoForPlaylist { |
|
|
|
AlbumName = meta.Data[0].Attributes.Name |
|
|
|
AlbumName=meta.Data[0].Attributes.Name |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
err = addMeta(mp4.BoxType{'\251', 'a', 'l', 'b'}, AlbumName) |
|
|
|
err = addMeta(mp4.BoxType{'\251', 'a', 'l', 'b'}, AlbumName) |
|
|
@ -808,7 +805,7 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t |
|
|
|
return err |
|
|
|
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) |
|
|
|
plID, err := strconv.ParseUint(meta.Data[0].ID, 10, 32) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
@ -839,8 +836,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.PutUint32(trkn, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.TrackNumber)) |
|
|
|
binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) |
|
|
|
binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) |
|
|
|
binary.BigEndian.PutUint32(disk, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.DiscNumber)) |
|
|
|
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)) |
|
|
|
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 strings.Contains(meta.Data[0].ID, "pl."){ |
|
|
|
if !config.UseSongInfoForPlaylist { |
|
|
|
if !config.UseSongInfoForPlaylist { |
|
|
|
binary.BigEndian.PutUint32(trkn, uint32(trackNum)) |
|
|
|
binary.BigEndian.PutUint32(trkn, uint32(trackNum)) |
|
|
|
binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) |
|
|
|
binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal)) |
|
|
@ -1120,8 +1117,8 @@ func getSongLyrics(songId string, storefront string, token string, userToken str |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func writeCover(sanAlbumFolder, name string, url string) error { |
|
|
|
func writeCover(sanAlbumFolder,name string, url string) error { |
|
|
|
covPath := filepath.Join(sanAlbumFolder, name+"."+config.CoverFormat) |
|
|
|
covPath := filepath.Join(sanAlbumFolder, name+"." + config.CoverFormat) |
|
|
|
exists, err := fileExists(covPath) |
|
|
|
exists, err := fileExists(covPath) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
fmt.Println("Failed to check if cover exists.") |
|
|
|
fmt.Println("Failed to check if cover exists.") |
|
|
@ -1182,13 +1179,13 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
var singerFoldername string |
|
|
|
var singerFoldername string |
|
|
|
if config.ArtistFolderFormat != "" { |
|
|
|
if config.ArtistFolderFormat != ""{ |
|
|
|
if strings.Contains(albumId, "pl.") { |
|
|
|
if strings.Contains(albumId, "pl.") { |
|
|
|
singerFoldername = strings.NewReplacer( |
|
|
|
singerFoldername = strings.NewReplacer( |
|
|
|
"{ArtistName}", "Apple Music", |
|
|
|
"{ArtistName}", "Apple Music", |
|
|
|
"{ArtistId}", "", |
|
|
|
"{ArtistId}", "", |
|
|
|
).Replace(config.ArtistFolderFormat) |
|
|
|
).Replace(config.ArtistFolderFormat) |
|
|
|
} else { |
|
|
|
}else{ |
|
|
|
singerFoldername = strings.NewReplacer( |
|
|
|
singerFoldername = strings.NewReplacer( |
|
|
|
"{ArtistName}", meta.Data[0].Attributes.ArtistName, |
|
|
|
"{ArtistName}", meta.Data[0].Attributes.ArtistName, |
|
|
|
"{ArtistId}", meta.Data[0].Relationships.Artists.Data[0].ID, |
|
|
|
"{ArtistId}", meta.Data[0].Relationships.Artists.Data[0].ID, |
|
|
@ -1209,29 +1206,29 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
fmt.Println("Unavailable in ALAC.") |
|
|
|
fmt.Println("Unavailable in ALAC.") |
|
|
|
} |
|
|
|
} |
|
|
|
var Quality string |
|
|
|
var Quality string |
|
|
|
EnhancedHls_m3u8, err := checkM3u8(meta.Data[0].Relationships.Tracks.Data[0].ID, "album") |
|
|
|
EnhancedHls_m3u8,err:=checkM3u8(meta.Data[0].Relationships.Tracks.Data[0].ID,"album") |
|
|
|
if strings.HasPrefix(EnhancedHls_m3u8, "http") { |
|
|
|
if strings.HasPrefix(EnhancedHls_m3u8, "http"){ |
|
|
|
manifest1.Attributes.ExtendedAssetUrls.EnhancedHls = EnhancedHls_m3u8 |
|
|
|
manifest1.Attributes.ExtendedAssetUrls.EnhancedHls=EnhancedHls_m3u8 |
|
|
|
} |
|
|
|
} |
|
|
|
if strings.Contains(config.AlbumFolderFormat, "Quality") { |
|
|
|
if strings.Contains(config.AlbumFolderFormat, "Quality"){ |
|
|
|
Quality, err = extractMediaQuality(manifest1.Attributes.ExtendedAssetUrls.EnhancedHls) |
|
|
|
Quality,err = extractMediaQuality(manifest1.Attributes.ExtendedAssetUrls.EnhancedHls) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
fmt.Println("Failed to extract quality from manifest.\n", err) |
|
|
|
fmt.Println("Failed to extract quality from manifest.\n", err) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
stringsToJoin := []string{} |
|
|
|
stringsToJoin := []string{} |
|
|
|
if meta.Data[0].Attributes.IsAppleDigitalMaster || meta.Data[0].Attributes.IsMasteredForItunes { |
|
|
|
if meta.Data[0].Attributes.IsAppleDigitalMaster{ |
|
|
|
if config.AppleMasterChoice != "" { |
|
|
|
if config.AppleMasterChoice != ""{ |
|
|
|
stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) |
|
|
|
stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if meta.Data[0].Attributes.ContentRating == "explicit" { |
|
|
|
if meta.Data[0].Attributes.ContentRating=="explicit"{ |
|
|
|
if config.ExplicitChoice != "" { |
|
|
|
if config.ExplicitChoice != ""{ |
|
|
|
stringsToJoin = append(stringsToJoin, config.ExplicitChoice) |
|
|
|
stringsToJoin = append(stringsToJoin, config.ExplicitChoice) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if meta.Data[0].Attributes.ContentRating == "clean" { |
|
|
|
if meta.Data[0].Attributes.ContentRating=="clean"{ |
|
|
|
if config.CleanChoice != "" { |
|
|
|
if config.CleanChoice != ""{ |
|
|
|
stringsToJoin = append(stringsToJoin, config.CleanChoice) |
|
|
|
stringsToJoin = append(stringsToJoin, config.CleanChoice) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1242,11 +1239,11 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
"{ArtistName}", "Apple Music", |
|
|
|
"{ArtistName}", "Apple Music", |
|
|
|
"{PlaylistName}", meta.Data[0].Attributes.Name, |
|
|
|
"{PlaylistName}", meta.Data[0].Attributes.Name, |
|
|
|
"{PlaylistId}", albumId, |
|
|
|
"{PlaylistId}", albumId, |
|
|
|
"{Quality}", Quality, |
|
|
|
"{Quality}",Quality, |
|
|
|
"{Codec}", "ALAC", |
|
|
|
"{Codec}", "ALAC", |
|
|
|
"{Tag}", Tag_string, |
|
|
|
"{Tag}",Tag_string, |
|
|
|
).Replace(config.PlaylistFolderFormat) |
|
|
|
).Replace(config.PlaylistFolderFormat) |
|
|
|
} else { |
|
|
|
}else{ |
|
|
|
albumFolder = strings.NewReplacer( |
|
|
|
albumFolder = strings.NewReplacer( |
|
|
|
"{ReleaseDate}", meta.Data[0].Attributes.ReleaseDate, |
|
|
|
"{ReleaseDate}", meta.Data[0].Attributes.ReleaseDate, |
|
|
|
"{ReleaseYear}", meta.Data[0].Attributes.ReleaseDate[:4], |
|
|
|
"{ReleaseYear}", meta.Data[0].Attributes.ReleaseDate[:4], |
|
|
@ -1258,7 +1255,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
"{AlbumId}", albumId, |
|
|
|
"{AlbumId}", albumId, |
|
|
|
"{Quality}", Quality, |
|
|
|
"{Quality}", Quality, |
|
|
|
"{Codec}", "ALAC", |
|
|
|
"{Codec}", "ALAC", |
|
|
|
"{Tag}", Tag_string, |
|
|
|
"{Tag}",Tag_string, |
|
|
|
).Replace(config.AlbumFolderFormat) |
|
|
|
).Replace(config.AlbumFolderFormat) |
|
|
|
} |
|
|
|
} |
|
|
|
if strings.HasSuffix(albumFolder, ".") { |
|
|
|
if strings.HasSuffix(albumFolder, ".") { |
|
|
@ -1268,7 +1265,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_")) |
|
|
|
sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_")) |
|
|
|
os.MkdirAll(sanAlbumFolder, os.ModePerm) |
|
|
|
os.MkdirAll(sanAlbumFolder, os.ModePerm) |
|
|
|
fmt.Println(albumFolder) |
|
|
|
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 { |
|
|
|
if err != nil { |
|
|
|
fmt.Println("Failed to write cover.") |
|
|
|
fmt.Println("Failed to write cover.") |
|
|
|
} |
|
|
|
} |
|
|
@ -1286,31 +1283,31 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
fmt.Println("Unavailable in ALAC.") |
|
|
|
fmt.Println("Unavailable in ALAC.") |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
EnhancedHls_m3u8, err := checkM3u8(track.ID, "song") |
|
|
|
EnhancedHls_m3u8,err:=checkM3u8(track.ID,"song") |
|
|
|
if strings.HasPrefix(EnhancedHls_m3u8, "http") { |
|
|
|
if strings.HasPrefix(EnhancedHls_m3u8, "http"){ |
|
|
|
manifest.Attributes.ExtendedAssetUrls.EnhancedHls = EnhancedHls_m3u8 |
|
|
|
manifest.Attributes.ExtendedAssetUrls.EnhancedHls=EnhancedHls_m3u8 |
|
|
|
} |
|
|
|
} |
|
|
|
var Quality string |
|
|
|
var Quality string |
|
|
|
if strings.Contains(config.SongFileFormat, "Quality") { |
|
|
|
if strings.Contains(config.SongFileFormat, "Quality"){ |
|
|
|
Quality, err = extractMediaQuality(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) |
|
|
|
Quality,err = extractMediaQuality(manifest.Attributes.ExtendedAssetUrls.EnhancedHls) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
fmt.Println("Failed to extract quality from manifest.\n", err) |
|
|
|
fmt.Println("Failed to extract quality from manifest.\n", err) |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
stringsToJoin := []string{} |
|
|
|
stringsToJoin := []string{} |
|
|
|
if track.Attributes.IsAppleDigitalMaster { |
|
|
|
if track.Attributes.IsAppleDigitalMaster{ |
|
|
|
if config.AppleMasterChoice != "" { |
|
|
|
if config.AppleMasterChoice != ""{ |
|
|
|
stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) |
|
|
|
stringsToJoin = append(stringsToJoin, config.AppleMasterChoice) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if track.Attributes.ContentRating == "explicit" { |
|
|
|
if track.Attributes.ContentRating=="explicit"{ |
|
|
|
if config.ExplicitChoice != "" { |
|
|
|
if config.ExplicitChoice != ""{ |
|
|
|
stringsToJoin = append(stringsToJoin, config.ExplicitChoice) |
|
|
|
stringsToJoin = append(stringsToJoin, config.ExplicitChoice) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if track.Attributes.ContentRating == "clean" { |
|
|
|
if track.Attributes.ContentRating=="clean"{ |
|
|
|
if config.CleanChoice != "" { |
|
|
|
if config.CleanChoice != ""{ |
|
|
|
stringsToJoin = append(stringsToJoin, config.CleanChoice) |
|
|
|
stringsToJoin = append(stringsToJoin, config.CleanChoice) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1323,7 +1320,7 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
"{DiscNumber}", fmt.Sprintf("%0d", track.Attributes.DiscNumber), |
|
|
|
"{DiscNumber}", fmt.Sprintf("%0d", track.Attributes.DiscNumber), |
|
|
|
"{TrackNumber}", fmt.Sprintf("%0d", track.Attributes.TrackNumber), |
|
|
|
"{TrackNumber}", fmt.Sprintf("%0d", track.Attributes.TrackNumber), |
|
|
|
"{Quality}", Quality, |
|
|
|
"{Quality}", Quality, |
|
|
|
"{Tag}", Tag_string, |
|
|
|
"{Tag}",Tag_string, |
|
|
|
"{Codec}", "ALAC", |
|
|
|
"{Codec}", "ALAC", |
|
|
|
).Replace(config.SongFileFormat) |
|
|
|
).Replace(config.SongFileFormat) |
|
|
|
fmt.Println(songName) |
|
|
|
fmt.Println(songName) |
|
|
@ -1393,22 +1390,22 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
tags := []string{ |
|
|
|
tags := []string{ |
|
|
|
fmt.Sprintf("lyrics=%s", lrc), |
|
|
|
fmt.Sprintf("lyrics=%s", lrc), |
|
|
|
} |
|
|
|
} |
|
|
|
if track.Attributes.ContentRating == "explicit" { |
|
|
|
if track.Attributes.ContentRating=="explicit"{ |
|
|
|
tags = append(tags, "rating=1") |
|
|
|
tags = append(tags, "rating=1") |
|
|
|
} else if track.Attributes.ContentRating == "clean" { |
|
|
|
}else if track.Attributes.ContentRating=="clean"{ |
|
|
|
tags = append(tags, "rating=2") |
|
|
|
tags = append(tags, "rating=2") |
|
|
|
} else { |
|
|
|
}else{ |
|
|
|
tags = append(tags, "rating=0") |
|
|
|
tags = append(tags, "rating=0") |
|
|
|
} |
|
|
|
} |
|
|
|
if config.EmbedCover { |
|
|
|
if config.EmbedCover { |
|
|
|
if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { |
|
|
|
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 { |
|
|
|
if err != nil { |
|
|
|
fmt.Println("Failed to write cover.") |
|
|
|
fmt.Println("Failed to write cover.") |
|
|
|
} |
|
|
|
} |
|
|
|
tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)) |
|
|
|
tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,track.ID, config.CoverFormat)) |
|
|
|
} else { |
|
|
|
}else{ |
|
|
|
tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder, "cover", config.CoverFormat)) |
|
|
|
tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", sanAlbumFolder,"cover", config.CoverFormat)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
tagsString := strings.Join(tags, ":") |
|
|
|
tagsString := strings.Join(tags, ":") |
|
|
@ -1418,8 +1415,8 @@ func rip(albumId string, token string, storefront string, userToken string) erro |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { |
|
|
|
if strings.Contains(albumId, "pl.") && config.DlAlbumcoverForPlaylist { |
|
|
|
if err := os.Remove(fmt.Sprintf("%s/%s.%s", sanAlbumFolder, track.ID, config.CoverFormat)); err != nil { |
|
|
|
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) |
|
|
|
fmt.Printf("Error deleting file: %s/%s.%s\n", sanAlbumFolder,track.ID, config.CoverFormat) |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1520,12 +1517,12 @@ func conventTTMLToLRC(ttml string) (string, error) { |
|
|
|
return strings.Join(lrcLines, "\n"), nil |
|
|
|
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 |
|
|
|
var EnhancedHls string |
|
|
|
if config.Check != "" { |
|
|
|
if config.Check != ""{ |
|
|
|
config.Check = strings.TrimSpace(config.Check) |
|
|
|
config.Check=strings.TrimSpace(config.Check) |
|
|
|
if strings.HasSuffix(config.Check, "txt") { |
|
|
|
if strings.HasSuffix(config.Check, "txt") { |
|
|
|
txtpath = config.Check |
|
|
|
txtpath=config.Check |
|
|
|
} |
|
|
|
} |
|
|
|
if strings.HasPrefix(config.Check, "http") { |
|
|
|
if strings.HasPrefix(config.Check, "http") { |
|
|
|
req, err := http.NewRequest("GET", config.Check, nil) |
|
|
|
req, err := http.NewRequest("GET", config.Check, nil) |
|
|
@ -1547,8 +1544,8 @@ func checkM3u8(b string, f string) (string, error) { |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
fmt.Println(err) |
|
|
|
fmt.Println(err) |
|
|
|
} |
|
|
|
} |
|
|
|
if string(Checkbody) != "no_found" { |
|
|
|
if string(Checkbody) != "no_found"{ |
|
|
|
EnhancedHls = string(Checkbody) |
|
|
|
EnhancedHls=string(Checkbody) |
|
|
|
fmt.Println("Found m3u8 from API") |
|
|
|
fmt.Println("Found m3u8 from API") |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
if config.ForceApi { |
|
|
|
if config.ForceApi { |
|
|
@ -1558,14 +1555,14 @@ func checkM3u8(b string, f string) (string, error) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if config.GetM3u8FromDevice { |
|
|
|
if config.GetM3u8FromDevice{ |
|
|
|
adamID := b |
|
|
|
adamID := b |
|
|
|
conn, err := net.Dial("tcp", "127.0.0.1:20020") |
|
|
|
conn, err := net.Dial("tcp", "127.0.0.1:20020") |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
fmt.Println("Error connecting to device:", err) |
|
|
|
fmt.Println("Error connecting to device:", err) |
|
|
|
} |
|
|
|
} |
|
|
|
defer conn.Close() |
|
|
|
defer conn.Close() |
|
|
|
if f == "song" { |
|
|
|
if f =="song"{ |
|
|
|
fmt.Println("Connected to device") |
|
|
|
fmt.Println("Connected to device") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -1594,7 +1591,7 @@ func checkM3u8(b string, f string) (string, error) { |
|
|
|
|
|
|
|
|
|
|
|
response = bytes.TrimSpace(response) |
|
|
|
response = bytes.TrimSpace(response) |
|
|
|
if len(response) > 0 { |
|
|
|
if len(response) > 0 { |
|
|
|
if f == "song" { |
|
|
|
if f =="song"{ |
|
|
|
fmt.Println("Received URL:", string(response)) |
|
|
|
fmt.Println("Received URL:", string(response)) |
|
|
|
} |
|
|
|
} |
|
|
|
EnhancedHls = string(response) |
|
|
|
EnhancedHls = string(response) |
|
|
@ -1614,7 +1611,7 @@ func checkM3u8(b string, f string) (string, error) { |
|
|
|
if strings.HasPrefix(line, b) { |
|
|
|
if strings.HasPrefix(line, b) { |
|
|
|
parts := strings.SplitN(line, ",", 2) |
|
|
|
parts := strings.SplitN(line, ",", 2) |
|
|
|
if len(parts) == 2 { |
|
|
|
if len(parts) == 2 { |
|
|
|
EnhancedHls = parts[1] |
|
|
|
EnhancedHls=parts[1] |
|
|
|
fmt.Println("Found m3u8 from txt") |
|
|
|
fmt.Println("Found m3u8 from txt") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1653,16 +1650,16 @@ func extractMediaQuality(b string) (string, error) { |
|
|
|
if variant.Codecs == "alac" { |
|
|
|
if variant.Codecs == "alac" { |
|
|
|
split := strings.Split(variant.Audio, "-") |
|
|
|
split := strings.Split(variant.Audio, "-") |
|
|
|
length := len(split) |
|
|
|
length := len(split) |
|
|
|
length_int, err := strconv.Atoi(split[length-2]) |
|
|
|
length_int,err := strconv.Atoi(split[length-2]) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return "", err |
|
|
|
return "", err |
|
|
|
} |
|
|
|
} |
|
|
|
if length_int <= config.AlacMax { |
|
|
|
if length_int <= config.AlacMax{ |
|
|
|
HZ, err := strconv.Atoi(split[length-2]) |
|
|
|
HZ,err:=strconv.Atoi(split[length-2]) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
fmt.Println(err) |
|
|
|
fmt.Println(err) |
|
|
|
} |
|
|
|
} |
|
|
|
KHZ := float64(HZ) / 1000.0 |
|
|
|
KHZ:=float64(HZ) / 1000.0 |
|
|
|
Quality = fmt.Sprintf("%sB-%.1fkHz", split[length-1], KHZ) |
|
|
|
Quality = fmt.Sprintf("%sB-%.1fkHz", split[length-1], KHZ) |
|
|
|
break |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
@ -1702,11 +1699,11 @@ func extractMedia(b string) (string, []string, error) { |
|
|
|
if variant.Codecs == "alac" { |
|
|
|
if variant.Codecs == "alac" { |
|
|
|
split := strings.Split(variant.Audio, "-") |
|
|
|
split := strings.Split(variant.Audio, "-") |
|
|
|
length := len(split) |
|
|
|
length := len(split) |
|
|
|
length_int, err := strconv.Atoi(split[length-2]) |
|
|
|
length_int,err := strconv.Atoi(split[length-2]) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return "", nil, err |
|
|
|
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]) |
|
|
|
fmt.Printf("%s-bit / %s Hz\n", split[length-1], split[length-2]) |
|
|
|
streamUrlTemp, err := masterUrl.Parse(variant.URI) |
|
|
|
streamUrlTemp, err := masterUrl.Parse(variant.URI) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
@ -1975,32 +1972,32 @@ type SongAttributes struct { |
|
|
|
ExtendedAssetUrls struct { |
|
|
|
ExtendedAssetUrls struct { |
|
|
|
EnhancedHls string `json:"enhancedHls"` |
|
|
|
EnhancedHls string `json:"enhancedHls"` |
|
|
|
} `json:"extendedAssetUrls"` |
|
|
|
} `json:"extendedAssetUrls"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
Name string `json:"name"` |
|
|
|
Name string `json:"name"` |
|
|
|
Isrc string `json:"isrc"` |
|
|
|
Isrc string `json:"isrc"` |
|
|
|
AlbumName string `json:"albumName"` |
|
|
|
AlbumName string `json:"albumName"` |
|
|
|
TrackNumber int `json:"trackNumber"` |
|
|
|
TrackNumber int `json:"trackNumber"` |
|
|
|
ComposerName string `json:"composerName"` |
|
|
|
ComposerName string `json:"composerName"` |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type AlbumAttributes struct { |
|
|
|
type AlbumAttributes struct { |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
IsSingle bool `json:"isSingle"` |
|
|
|
IsSingle bool `json:"isSingle"` |
|
|
|
IsComplete bool `json:"isComplete"` |
|
|
|
IsComplete bool `json:"isComplete"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
TrackCount int `json:"trackCount"` |
|
|
|
TrackCount int `json:"trackCount"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
Name string `json:"name"` |
|
|
|
Name string `json:"name"` |
|
|
|
RecordLabel string `json:"recordLabel"` |
|
|
|
RecordLabel string `json:"recordLabel"` |
|
|
|
Upc string `json:"upc"` |
|
|
|
Upc string `json:"upc"` |
|
|
|
Copyright string `json:"copyright"` |
|
|
|
Copyright string `json:"copyright"` |
|
|
|
IsCompilation bool `json:"isCompilation"` |
|
|
|
IsCompilation bool `json:"isCompilation"` |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type SongData struct { |
|
|
|
type SongData struct { |
|
|
@ -2162,22 +2159,22 @@ type AutoGenerated struct { |
|
|
|
TextColor3 string `json:"textColor3"` |
|
|
|
TextColor3 string `json:"textColor3"` |
|
|
|
TextColor4 string `json:"textColor4"` |
|
|
|
TextColor4 string `json:"textColor4"` |
|
|
|
} `json:"artwork"` |
|
|
|
} `json:"artwork"` |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
IsSingle bool `json:"isSingle"` |
|
|
|
IsSingle bool `json:"isSingle"` |
|
|
|
URL string `json:"url"` |
|
|
|
URL string `json:"url"` |
|
|
|
IsComplete bool `json:"isComplete"` |
|
|
|
IsComplete bool `json:"isComplete"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
TrackCount int `json:"trackCount"` |
|
|
|
TrackCount int `json:"trackCount"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
Name string `json:"name"` |
|
|
|
Name string `json:"name"` |
|
|
|
RecordLabel string `json:"recordLabel"` |
|
|
|
RecordLabel string `json:"recordLabel"` |
|
|
|
Upc string `json:"upc"` |
|
|
|
Upc string `json:"upc"` |
|
|
|
AudioTraits []string `json:"audioTraits"` |
|
|
|
AudioTraits []string `json:"audioTraits"` |
|
|
|
Copyright string `json:"copyright"` |
|
|
|
Copyright string `json:"copyright"` |
|
|
|
PlayParams struct { |
|
|
|
PlayParams struct { |
|
|
|
ID string `json:"id"` |
|
|
|
ID string `json:"id"` |
|
|
|
Kind string `json:"kind"` |
|
|
|
Kind string `json:"kind"` |
|
|
|
} `json:"playParams"` |
|
|
|
} `json:"playParams"` |
|
|
@ -2220,22 +2217,22 @@ type AutoGenerated struct { |
|
|
|
TextColor3 string `json:"textColor3"` |
|
|
|
TextColor3 string `json:"textColor3"` |
|
|
|
TextColor4 string `json:"textColor4"` |
|
|
|
TextColor4 string `json:"textColor4"` |
|
|
|
} `json:"artwork"` |
|
|
|
} `json:"artwork"` |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
URL string `json:"url"` |
|
|
|
URL string `json:"url"` |
|
|
|
DiscNumber int `json:"discNumber"` |
|
|
|
DiscNumber int `json:"discNumber"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` |
|
|
|
HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
DurationInMillis int `json:"durationInMillis"` |
|
|
|
DurationInMillis int `json:"durationInMillis"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
Name string `json:"name"` |
|
|
|
Name string `json:"name"` |
|
|
|
Isrc string `json:"isrc"` |
|
|
|
Isrc string `json:"isrc"` |
|
|
|
AudioTraits []string `json:"audioTraits"` |
|
|
|
AudioTraits []string `json:"audioTraits"` |
|
|
|
HasLyrics bool `json:"hasLyrics"` |
|
|
|
HasLyrics bool `json:"hasLyrics"` |
|
|
|
AlbumName string `json:"albumName"` |
|
|
|
AlbumName string `json:"albumName"` |
|
|
|
PlayParams struct { |
|
|
|
PlayParams struct { |
|
|
|
ID string `json:"id"` |
|
|
|
ID string `json:"id"` |
|
|
|
Kind string `json:"kind"` |
|
|
|
Kind string `json:"kind"` |
|
|
|
} `json:"playParams"` |
|
|
|
} `json:"playParams"` |
|
|
@ -2283,22 +2280,22 @@ type AutoGeneratedTrack struct { |
|
|
|
TextColor3 string `json:"textColor3"` |
|
|
|
TextColor3 string `json:"textColor3"` |
|
|
|
TextColor4 string `json:"textColor4"` |
|
|
|
TextColor4 string `json:"textColor4"` |
|
|
|
} `json:"artwork"` |
|
|
|
} `json:"artwork"` |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
ArtistName string `json:"artistName"` |
|
|
|
URL string `json:"url"` |
|
|
|
URL string `json:"url"` |
|
|
|
DiscNumber int `json:"discNumber"` |
|
|
|
DiscNumber int `json:"discNumber"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
GenreNames []string `json:"genreNames"` |
|
|
|
HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` |
|
|
|
HasTimeSyncedLyrics bool `json:"hasTimeSyncedLyrics"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsMasteredForItunes bool `json:"isMasteredForItunes"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
IsAppleDigitalMaster bool `json:"isAppleDigitalMaster"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
ContentRating string `json:"contentRating"` |
|
|
|
DurationInMillis int `json:"durationInMillis"` |
|
|
|
DurationInMillis int `json:"durationInMillis"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
ReleaseDate string `json:"releaseDate"` |
|
|
|
Name string `json:"name"` |
|
|
|
Name string `json:"name"` |
|
|
|
Isrc string `json:"isrc"` |
|
|
|
Isrc string `json:"isrc"` |
|
|
|
AudioTraits []string `json:"audioTraits"` |
|
|
|
AudioTraits []string `json:"audioTraits"` |
|
|
|
HasLyrics bool `json:"hasLyrics"` |
|
|
|
HasLyrics bool `json:"hasLyrics"` |
|
|
|
AlbumName string `json:"albumName"` |
|
|
|
AlbumName string `json:"albumName"` |
|
|
|
PlayParams struct { |
|
|
|
PlayParams struct { |
|
|
|
ID string `json:"id"` |
|
|
|
ID string `json:"id"` |
|
|
|
Kind string `json:"kind"` |
|
|
|
Kind string `json:"kind"` |
|
|
|
} `json:"playParams"` |
|
|
|
} `json:"playParams"` |
|
|
|