add support for artist
This commit is contained in:
parent
6c9015200c
commit
93cfd3f7f7
@ -10,6 +10,8 @@
|
|||||||
7. main 支持使用 go run main.go "txt文件地址" txt文件名需要指定格式 例如 cn_1707581102_THE BOOK 3.txt 建议使用这个[Reqable 脚本代码](https://telegra.ph/Reqable-For-Apple-Music-05-01) 自动生成
|
7. main 支持使用 go run main.go "txt文件地址" txt文件名需要指定格式 例如 cn_1707581102_THE BOOK 3.txt 建议使用这个[Reqable 脚本代码](https://telegra.ph/Reqable-For-Apple-Music-05-01) 自动生成
|
||||||
8. main 支持check 可以填入文本地址 或API数据库.
|
8. main 支持check 可以填入文本地址 或API数据库.
|
||||||
9. 新增get-m3u8-from-device 改为true 且设置端口`adb forward tcp:20020 tcp:20020`即从模拟器获取m3u8
|
9. 新增get-m3u8-from-device 改为true 且设置端口`adb forward tcp:20020 tcp:20020`即从模拟器获取m3u8
|
||||||
|
10. 文件夹和文件支持模板
|
||||||
|
11. 支持下载歌手 `go run main.go https://music.apple.com/us/artist/taylor-swift/159260351`
|
||||||
|
|
||||||
本项目仅支持ALAC和Atmos
|
本项目仅支持ALAC和Atmos
|
||||||
- `alac (audio-alac-stereo)`
|
- `alac (audio-alac-stereo)`
|
||||||
|
164
main.go
164
main.go
@ -1019,6 +1019,116 @@ func checkUrlPlaylist(url string) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkUrlArtist(url string) (string, string) {
|
||||||
|
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/artist|\/artist\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
||||||
|
matches := pat.FindAllStringSubmatch(url, -1)
|
||||||
|
|
||||||
|
if matches == nil {
|
||||||
|
return "", ""
|
||||||
|
} else {
|
||||||
|
return matches[0][1], matches[0][2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkArtist(artistUrl string, token string) ([]string, error) {
|
||||||
|
storefront, artistId := checkUrlArtist(artistUrl)
|
||||||
|
Num := 0
|
||||||
|
|
||||||
|
var args []string
|
||||||
|
var urls []string
|
||||||
|
var options []string
|
||||||
|
for {
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/artists/%s/albums?limit=100&offset=%d", storefront, artistId, Num), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
||||||
|
req.Header.Set("Origin", "https://music.apple.com")
|
||||||
|
do, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer do.Body.Close()
|
||||||
|
if do.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New(do.Status)
|
||||||
|
}
|
||||||
|
obj := new(AutoGeneratedArtist)
|
||||||
|
err = json.NewDecoder(do.Body).Decode(&obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, album := range obj.Data {
|
||||||
|
urls = append(urls, album.Attributes.URL)
|
||||||
|
options = append(options, fmt.Sprintf("AlbumName: %s(%s)", album.Attributes.Name, album.ID))
|
||||||
|
}
|
||||||
|
Num = Num + 100
|
||||||
|
if len(obj.Next) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, option := range options {
|
||||||
|
fmt.Printf("%02d: %s\n", i+1, option)
|
||||||
|
}
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
fmt.Println("Please select from the following options (multiple options separated by commas, ranges supported, or type 'all' to select all)")
|
||||||
|
fmt.Print("Enter your choice: ")
|
||||||
|
input, _ := reader.ReadString('\n')
|
||||||
|
|
||||||
|
// Remove newline and whitespace
|
||||||
|
input = strings.TrimSpace(input)
|
||||||
|
if input == "all" {
|
||||||
|
fmt.Println("You have selected all options:")
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split input into string slices
|
||||||
|
selectedOptions := [][]string{}
|
||||||
|
parts := strings.Split(input, ",")
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.Contains(part, "-") { // Range setting
|
||||||
|
rangeParts := strings.Split(part, "-")
|
||||||
|
selectedOptions = append(selectedOptions, rangeParts)
|
||||||
|
} else { // Single option
|
||||||
|
selectedOptions = append(selectedOptions, []string{part})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print selected options
|
||||||
|
fmt.Println("You have selected the following options:")
|
||||||
|
for _, opt := range selectedOptions {
|
||||||
|
if len(opt) == 1 { // Single option
|
||||||
|
num, err := strconv.Atoi(opt[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Invalid option:", opt[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if num > 0 && num <= len(options) {
|
||||||
|
args = append(args, urls[num-1])
|
||||||
|
} else {
|
||||||
|
fmt.Println("Option out of range:", opt[0])
|
||||||
|
}
|
||||||
|
} else if len(opt) == 2 { // Range
|
||||||
|
start, err1 := strconv.Atoi(opt[0])
|
||||||
|
end, err2 := strconv.Atoi(opt[1])
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
fmt.Println("Invalid range:", opt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < 1 || end > len(options) || start > end {
|
||||||
|
fmt.Println("Range out of range:", opt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
args = append(args, urls[i-1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Invalid option:", opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getMeta(albumId string, token string, storefront string) (*AutoGenerated, error) {
|
func getMeta(albumId string, token string, storefront string) (*AutoGenerated, error) {
|
||||||
var mtype string
|
var mtype string
|
||||||
var page int
|
var page int
|
||||||
@ -1439,6 +1549,14 @@ func main() {
|
|||||||
fmt.Println("Failed to get token.")
|
fmt.Println("Failed to get token.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.Contains(os.Args[1], "/artist/") {
|
||||||
|
newArgs, err := checkArtist(os.Args[1], token)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to get artist.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Args = append([]string{os.Args[0]}, newArgs...)
|
||||||
|
}
|
||||||
albumTotal := len(os.Args[1:])
|
albumTotal := len(os.Args[1:])
|
||||||
for albumNum, url := range os.Args[1:] {
|
for albumNum, url := range os.Args[1:] {
|
||||||
fmt.Printf("Album %d of %d:\n", albumNum+1, albumTotal)
|
fmt.Printf("Album %d of %d:\n", albumNum+1, albumTotal)
|
||||||
@ -2322,6 +2440,52 @@ type AutoGeneratedTrack struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AutoGeneratedArtist struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
Data []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
Attributes struct {
|
||||||
|
Previews []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"previews"`
|
||||||
|
Artwork struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
BgColor string `json:"bgColor"`
|
||||||
|
TextColor1 string `json:"textColor1"`
|
||||||
|
TextColor2 string `json:"textColor2"`
|
||||||
|
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 {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
} `json:"playParams"`
|
||||||
|
TrackNumber int `json:"trackNumber"`
|
||||||
|
AudioLocale string `json:"audioLocale"`
|
||||||
|
ComposerName string `json:"composerName"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
type SongLyrics struct {
|
type SongLyrics struct {
|
||||||
Data []struct {
|
Data []struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
165
main_atmos.go
165
main_atmos.go
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -964,6 +965,116 @@ func checkUrlPlaylist(url string) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkUrlArtist(url string) (string, string) {
|
||||||
|
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/artist|\/artist\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
||||||
|
matches := pat.FindAllStringSubmatch(url, -1)
|
||||||
|
|
||||||
|
if matches == nil {
|
||||||
|
return "", ""
|
||||||
|
} else {
|
||||||
|
return matches[0][1], matches[0][2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkArtist(artistUrl string, token string) ([]string, error) {
|
||||||
|
storefront, artistId := checkUrlArtist(artistUrl)
|
||||||
|
Num := 0
|
||||||
|
|
||||||
|
var args []string
|
||||||
|
var urls []string
|
||||||
|
var options []string
|
||||||
|
for {
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/artists/%s/albums?limit=100&offset=%d", storefront, artistId, Num), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
||||||
|
req.Header.Set("Origin", "https://music.apple.com")
|
||||||
|
do, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer do.Body.Close()
|
||||||
|
if do.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New(do.Status)
|
||||||
|
}
|
||||||
|
obj := new(AutoGeneratedArtist)
|
||||||
|
err = json.NewDecoder(do.Body).Decode(&obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, album := range obj.Data {
|
||||||
|
urls = append(urls, album.Attributes.URL)
|
||||||
|
options = append(options, fmt.Sprintf("AlbumName: %s(%s)", album.Attributes.Name, album.ID))
|
||||||
|
}
|
||||||
|
Num = Num + 100
|
||||||
|
if len(obj.Next) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, option := range options {
|
||||||
|
fmt.Printf("%02d: %s\n", i+1, option)
|
||||||
|
}
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
fmt.Println("Please select from the following options (multiple options separated by commas, ranges supported, or type 'all' to select all)")
|
||||||
|
fmt.Print("Enter your choice: ")
|
||||||
|
input, _ := reader.ReadString('\n')
|
||||||
|
|
||||||
|
// Remove newline and whitespace
|
||||||
|
input = strings.TrimSpace(input)
|
||||||
|
if input == "all" {
|
||||||
|
fmt.Println("You have selected all options:")
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split input into string slices
|
||||||
|
selectedOptions := [][]string{}
|
||||||
|
parts := strings.Split(input, ",")
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.Contains(part, "-") { // Range setting
|
||||||
|
rangeParts := strings.Split(part, "-")
|
||||||
|
selectedOptions = append(selectedOptions, rangeParts)
|
||||||
|
} else { // Single option
|
||||||
|
selectedOptions = append(selectedOptions, []string{part})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print selected options
|
||||||
|
fmt.Println("You have selected the following options:")
|
||||||
|
for _, opt := range selectedOptions {
|
||||||
|
if len(opt) == 1 { // Single option
|
||||||
|
num, err := strconv.Atoi(opt[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Invalid option:", opt[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if num > 0 && num <= len(options) {
|
||||||
|
args = append(args, urls[num-1])
|
||||||
|
} else {
|
||||||
|
fmt.Println("Option out of range:", opt[0])
|
||||||
|
}
|
||||||
|
} else if len(opt) == 2 { // Range
|
||||||
|
start, err1 := strconv.Atoi(opt[0])
|
||||||
|
end, err2 := strconv.Atoi(opt[1])
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
fmt.Println("Invalid range:", opt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < 1 || end > len(options) || start > end {
|
||||||
|
fmt.Println("Range out of range:", opt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
args = append(args, urls[i-1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Invalid option:", opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getMeta(albumId string, token string, storefront string) (*AutoGenerated, error) {
|
func getMeta(albumId string, token string, storefront string) (*AutoGenerated, error) {
|
||||||
var mtype string
|
var mtype string
|
||||||
var page int
|
var page int
|
||||||
@ -1391,6 +1502,14 @@ func main() {
|
|||||||
fmt.Println("Failed to get token.")
|
fmt.Println("Failed to get token.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.Contains(os.Args[1], "/artist/") {
|
||||||
|
newArgs, err := checkArtist(os.Args[1], token)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to get artist.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Args = append([]string{os.Args[0]}, newArgs...)
|
||||||
|
}
|
||||||
albumTotal := len(os.Args[1:])
|
albumTotal := len(os.Args[1:])
|
||||||
for albumNum, url := range os.Args[1:] {
|
for albumNum, url := range os.Args[1:] {
|
||||||
fmt.Printf("Album %d of %d:\n", albumNum+1, albumTotal)
|
fmt.Printf("Album %d of %d:\n", albumNum+1, albumTotal)
|
||||||
@ -2113,6 +2232,52 @@ type AutoGeneratedTrack struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AutoGeneratedArtist struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
Data []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
Attributes struct {
|
||||||
|
Previews []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"previews"`
|
||||||
|
Artwork struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
BgColor string `json:"bgColor"`
|
||||||
|
TextColor1 string `json:"textColor1"`
|
||||||
|
TextColor2 string `json:"textColor2"`
|
||||||
|
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 {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
} `json:"playParams"`
|
||||||
|
TrackNumber int `json:"trackNumber"`
|
||||||
|
AudioLocale string `json:"audioLocale"`
|
||||||
|
ComposerName string `json:"composerName"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
type SongLyrics struct {
|
type SongLyrics struct {
|
||||||
Data []struct {
|
Data []struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user