add mv-audio-type mv-max
This commit is contained in:
parent
60942cd286
commit
560a6b45f4
@ -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
|
||||||
|
103
main.go
103
main.go
@ -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)
|
||||||
|
|
||||||
@ -478,7 +480,7 @@ func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, tr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := mvDownloader(track.ID, sanAlbumFolder, token, mediaUserToken)
|
err := mvDownloader(track.ID, sanAlbumFolder, token, mediaUserToken)
|
||||||
if err !=nil {
|
if err != nil {
|
||||||
fmt.Println("\u26A0 Failed to dl MV:", err)
|
fmt.Println("\u26A0 Failed to dl MV:", err)
|
||||||
counter.Error++
|
counter.Error++
|
||||||
return
|
return
|
||||||
@ -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 {
|
||||||
@ -1164,7 +1170,7 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err := mvDownloader(checkUrlMv(urlRaw), Config.AlacSaveFolder, token, Config.MediaUserToken)
|
err := mvDownloader(checkUrlMv(urlRaw), Config.AlacSaveFolder, token, Config.MediaUserToken)
|
||||||
if err !=nil {
|
if err != nil {
|
||||||
fmt.Println("\u26A0 Failed to dl MV:", err)
|
fmt.Println("\u26A0 Failed to dl MV:", err)
|
||||||
counter.Error++
|
counter.Error++
|
||||||
continue
|
continue
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user