fix apple master tag
This commit is contained in:
parent
5f528e76a9
commit
fa02c77ff8
333
main.go
333
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)
|
||||
@ -805,7 +808,7 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t
|
||||
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
|
||||
@ -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)
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -1591,7 +1594,7 @@ func checkM3u8(b string,f string) (string, error) {
|
||||
|
||||
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"`
|
||||
|
279
main_atmos.go
279
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.")
|
||||
}
|
||||
@ -1214,18 +1215,18 @@ func rip(albumId string, token string, storefront string, userToken string) erro
|
||||
}
|
||||
|
||||
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"`
|
||||
|
311
main_select.go
311
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)
|
||||
@ -800,7 +802,7 @@ func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, t
|
||||
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
|
||||
@ -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"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user