add mv-audio-type mv-max

This commit is contained in:
zhaarey 2025-02-11 19:10:07 +08:00
parent 60942cd286
commit 560a6b45f4
3 changed files with 92 additions and 19 deletions

View File

@ -42,3 +42,5 @@ apple-master-choice : "[M]"
use-songinfo-for-playlist: false use-songinfo-for-playlist: false
#if set true,will download album cover for playlist #if set true,will download album cover for playlist
dl-albumcover-for-playlist: false dl-albumcover-for-playlist: false
mv-audio-type: atmos #atmos ac3 aac
mv-max: 2160

99
main.go
View File

@ -44,6 +44,8 @@ var (
debug_mode bool debug_mode bool
alac_max *int alac_max *int
atmos_max *int atmos_max *int
mv_max *int
mv_audio_type *string
aac_type *string aac_type *string
Config structs.ConfigSet Config structs.ConfigSet
counter structs.Counter counter structs.Counter
@ -100,7 +102,7 @@ func checkUrl(url string) (string, string) {
return matches[0][1], matches[0][2] return matches[0][1], matches[0][2]
} }
} }
func checkUrlMv(url string) (string) { func checkUrlMv(url string) string {
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/music-video|\/music-video\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`) pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/music-video|\/music-video\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
matches := pat.FindAllStringSubmatch(url, -1) matches := pat.FindAllStringSubmatch(url, -1)
@ -1104,6 +1106,8 @@ func main() {
alac_max = pflag.Int("alac-max", Config.AlacMax, "Specify the max quality for download alac") alac_max = pflag.Int("alac-max", Config.AlacMax, "Specify the max quality for download alac")
atmos_max = pflag.Int("atmos-max", Config.AtmosMax, "Specify the max quality for download atmos") atmos_max = pflag.Int("atmos-max", Config.AtmosMax, "Specify the max quality for download atmos")
aac_type = pflag.String("aac-type", Config.AacType, "Select AAC type, aac aac-binaural aac-downmix") aac_type = pflag.String("aac-type", Config.AacType, "Select AAC type, aac aac-binaural aac-downmix")
mv_audio_type = pflag.String("mv-audio-type", Config.MVAudioType, "Select MV audio type, atmos ac3 aac")
mv_max = pflag.Int("mv-max", Config.MVMax, "Specify the max quality for download MV")
// Custom usage message for help // Custom usage message for help
pflag.Usage = func() { pflag.Usage = func() {
@ -1117,6 +1121,8 @@ func main() {
Config.AlacMax = *alac_max Config.AlacMax = *alac_max
Config.AtmosMax = *atmos_max Config.AtmosMax = *atmos_max
Config.AacType = *aac_type Config.AacType = *aac_type
Config.MVAudioType = *mv_audio_type
Config.MVMax = *mv_max
args := pflag.Args() args := pflag.Args()
if len(args) == 0 { if len(args) == 0 {
@ -1209,10 +1215,10 @@ func main() {
counter = structs.Counter{} counter = structs.Counter{}
} }
} }
func mvDownloader(adamID string, saveDir string, token string, mediaUserToken string)(error){ func mvDownloader(adamID string, saveDir string, token string, mediaUserToken string) error {
vidPath := filepath.Join(saveDir, fmt.Sprintf("%s_vid.mp4", adamID)) vidPath := filepath.Join(saveDir, fmt.Sprintf("%s_vid.mp4", adamID))
audPath := filepath.Join(saveDir, fmt.Sprintf("%s_aud.mp4", adamID)) audPath := filepath.Join(saveDir, fmt.Sprintf("%s_aud.mp4", adamID))
mvOutPath := filepath.Join(saveDir, fmt.Sprintf("%s.mkv", adamID)) mvOutPath := filepath.Join(saveDir, fmt.Sprintf("%s.mp4", adamID))
exists, _ := fileExists(mvOutPath) exists, _ := fileExists(mvOutPath)
if exists { if exists {
fmt.Println("MV already exists locally.") fmt.Println("MV already exists locally.")
@ -1222,7 +1228,6 @@ func mvDownloader(adamID string, saveDir string, token string, mediaUserToken st
//videom3u8url, audiom3u8url , _ := mvQualitySelect(mvm3u8url) //videom3u8url, audiom3u8url , _ := mvQualitySelect(mvm3u8url)
videom3u8url, _ := extractVideo(mvm3u8url) videom3u8url, _ := extractVideo(mvm3u8url)
audiom3u8url, _ := extractMvAudio(mvm3u8url) audiom3u8url, _ := extractMvAudio(mvm3u8url)
//fmt.Println(videom3u8url)
//fmt.Println(audiom3u8url) //fmt.Println(audiom3u8url)
videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true) videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true)
@ -1255,37 +1260,77 @@ func extractMvAudio(c string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
resp, err := http.Get(c) resp, err := http.Get(c)
if err != nil { if err != nil {
return "", err return "", err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return "", errors.New(resp.Status) return "", errors.New(resp.Status)
} }
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return "", err
} }
videoString := string(body) videoString := string(body)
from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true) from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true)
if err != nil || listType != m3u8.MASTER { if err != nil || listType != m3u8.MASTER {
return "", errors.New("m3u8 not of media type") return "", errors.New("m3u8 not of media type")
} }
video := from.(*m3u8.MasterPlaylist) video := from.(*m3u8.MasterPlaylist)
var streamUrl *url.URL
var audioPriority = []string{"audio-atmos", "audio-ac3", "audio-stereo-256"}
if Config.MVAudioType == "ac3" {
audioPriority = []string{"audio-ac3", "audio-stereo-256"}
} else if Config.MVAudioType == "aac" {
audioPriority = []string{"audio-stereo-256"}
}
re := regexp.MustCompile(`_gr(\d+)_`)
type AudioStream struct {
URL string
Rank int
GroupID string
}
var audioStreams []AudioStream
for _, variant := range video.Variants { for _, variant := range video.Variants {
for _, audiov := range variant.Alternatives { for _, audiov := range variant.Alternatives {
if audiov.GroupId == "audio-stereo-256" { if audiov.URI != "" {
streamUrl, _ = MediaUrl.Parse(audiov.URI) for _, priority := range audioPriority {
break if audiov.GroupId == priority {
matches := re.FindStringSubmatch(audiov.URI)
if len(matches) == 2 {
var rank int
fmt.Sscanf(matches[1], "%d", &rank)
streamUrl, _ := MediaUrl.Parse(audiov.URI)
audioStreams = append(audioStreams, AudioStream{
URL: streamUrl.String(),
Rank: rank,
GroupID: audiov.GroupId,
})
} }
} }
} }
if streamUrl == nil {
return "", errors.New("no video codec found")
} }
return streamUrl.String(), nil }
}
if len(audioStreams) == 0 {
return "", errors.New("no suitable audio stream found")
}
sort.Slice(audioStreams, func(i, j int) bool {
return audioStreams[i].Rank > audioStreams[j].Rank
})
return audioStreams[0].URL, nil
} }
func conventSyllableTTMLToLRC(ttml string) (string, error) { func conventSyllableTTMLToLRC(ttml string) (string, error) {
@ -1775,38 +1820,62 @@ func extractVideo(c string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
resp, err := http.Get(c) resp, err := http.Get(c)
if err != nil { if err != nil {
return "", err return "", err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return "", errors.New(resp.Status) return "", errors.New(resp.Status)
} }
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return "", err
} }
videoString := string(body) videoString := string(body)
from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true) from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true)
if err != nil || listType != m3u8.MASTER { if err != nil || listType != m3u8.MASTER {
return "", errors.New("m3u8 not of media type") return "", errors.New("m3u8 not of media type")
} }
video := from.(*m3u8.MasterPlaylist) video := from.(*m3u8.MasterPlaylist)
re := regexp.MustCompile(`_(\d+)x(\d+)_`)
var streamUrl *url.URL var streamUrl *url.URL
sort.Slice(video.Variants, func(i, j int) bool { sort.Slice(video.Variants, func(i, j int) bool {
return video.Variants[i].AverageBandwidth > video.Variants[j].AverageBandwidth return video.Variants[i].AverageBandwidth > video.Variants[j].AverageBandwidth
}) })
if len(video.Variants) > 0 {
highestBandwidthVariant := video.Variants[0] maxHeight := Config.MVMax
streamUrl, err = MediaUrl.Parse(highestBandwidthVariant.URI)
for _, variant := range video.Variants {
matches := re.FindStringSubmatch(variant.URI)
if len(matches) == 3 {
height := matches[2]
var h int
_, err := fmt.Sscanf(height, "%d", &h)
if err != nil {
continue
}
if h <= maxHeight {
streamUrl, err = MediaUrl.Parse(variant.URI)
if err != nil { if err != nil {
return "", err return "", err
} }
break
} }
}
}
if streamUrl == nil { if streamUrl == nil {
return "", errors.New("no video codec found") return "", errors.New("no suitable video stream found")
} }
return streamUrl.String(), nil return streamUrl.String(), nil
} }

View File

@ -34,6 +34,8 @@ type ConfigSet struct {
LimitMax int `yaml:"limit-max"` LimitMax int `yaml:"limit-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"`
MVAudioType string `yaml:"mv-audio-type"`
MVMax int `yaml:"mv-max"`
} }
type Counter struct { type Counter struct {