dev: MV dl (need n-m3u8dl-re and mp4decrypt)
This commit is contained in:
parent
dccaca5376
commit
3726ac4c8a
100
main.go
100
main.go
@ -100,6 +100,16 @@ 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) {
|
||||||
|
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/music-video|\/music-video\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
||||||
|
matches := pat.FindAllStringSubmatch(url, -1)
|
||||||
|
|
||||||
|
if matches == nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return matches[0][2]
|
||||||
|
}
|
||||||
|
}
|
||||||
func checkUrlSong(url string) (string, string) {
|
func checkUrlSong(url string) (string, string) {
|
||||||
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/song|\/song\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/song|\/song\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
||||||
matches := pat.FindAllStringSubmatch(url, -1)
|
matches := pat.FindAllStringSubmatch(url, -1)
|
||||||
@ -583,7 +593,7 @@ func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, tr
|
|||||||
counter.Error++
|
counter.Error++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := runv3.Run(track.ID, trackPath, token, mediaUserToken)
|
_, err := runv3.Run(track.ID, trackPath, token, mediaUserToken, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to dl aac-lc:", err)
|
fmt.Println("Failed to dl aac-lc:", err)
|
||||||
counter.Error++
|
counter.Error++
|
||||||
@ -1093,6 +1103,11 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
os.Args = args
|
os.Args = args
|
||||||
|
//mv dl dev
|
||||||
|
if strings.Contains(os.Args[0], "/music-video/") {
|
||||||
|
_ = mvDownloader(checkUrlMv(os.Args[0]), Config.AlacSaveFolder, token, Config.MediaUserToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
if strings.Contains(os.Args[0], "/artist/") {
|
if strings.Contains(os.Args[0], "/artist/") {
|
||||||
urlArtistName, err := getUrlArtistName(os.Args[0], token)
|
urlArtistName, err := getUrlArtistName(os.Args[0], token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1153,6 +1168,89 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mvDownloader(adamID string, saveDir string, token string, mediaUserToken string)(error){
|
||||||
|
vidPath := filepath.Join(saveDir, fmt.Sprintf("%s.mp4", adamID))
|
||||||
|
audPath := filepath.Join(saveDir, fmt.Sprintf("%s.m4a", adamID))
|
||||||
|
mvOutPath := filepath.Join(saveDir, fmt.Sprintf("%s.mkv", adamID))
|
||||||
|
exists, _ := fileExists(mvOutPath)
|
||||||
|
if exists {
|
||||||
|
fmt.Println("MV already exists locally.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mvm3u8url, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true)
|
||||||
|
//videom3u8url, audiom3u8url , _ := mvQualitySelect(mvm3u8url)
|
||||||
|
videom3u8url, _ := extractVideo(mvm3u8url)
|
||||||
|
audiom3u8url, _ := extractMvAudio(mvm3u8url)
|
||||||
|
//fmt.Println(videom3u8url)
|
||||||
|
//fmt.Println(audiom3u8url)
|
||||||
|
videokey, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true)
|
||||||
|
audiokey, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true)
|
||||||
|
//fmt.Println(videokey)
|
||||||
|
//fmt.Println(audiokey)
|
||||||
|
cmd1 := exec.Command("n-m3u8dl-re", videom3u8url, "--key", videokey, "--decryption-engine", "MP4DECRYPT", "--save-dir", saveDir, "--save-name", adamID)
|
||||||
|
cmd2 := exec.Command("n-m3u8dl-re", audiom3u8url, "--key", audiokey, "--decryption-engine", "MP4DECRYPT", "--save-dir", saveDir, "--save-name", adamID)
|
||||||
|
cmd3 := exec.Command("MP4Box", "-quiet", "-add", vidPath, "-add", audPath, "-keep-utc", "-new", mvOutPath)
|
||||||
|
fmt.Printf("MVvid Downloading...")
|
||||||
|
if err := cmd1.Run(); err != nil {
|
||||||
|
fmt.Printf("MVvid Download failed: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("\rMVvid Downloaded. \n")
|
||||||
|
fmt.Printf("MVaud Downloading...")
|
||||||
|
if err := cmd2.Run(); err != nil {
|
||||||
|
fmt.Printf("MVaud Download failed: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("\rMVaud Downloaded. \n")
|
||||||
|
fmt.Printf("MV Remuxing...")
|
||||||
|
if err := cmd3.Run(); err != nil {
|
||||||
|
fmt.Printf("MV mux failed: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("\rMV Remuxed. \n")
|
||||||
|
_ = os.Remove(vidPath)
|
||||||
|
_ = os.Remove(audPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractMvAudio(c string) (string, error) {
|
||||||
|
MediaUrl, err := url.Parse(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
resp, err := http.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
videoString := string(body)
|
||||||
|
from, listType, err := m3u8.DecodeFrom(strings.NewReader(videoString), true)
|
||||||
|
if err != nil || listType != m3u8.MASTER {
|
||||||
|
return "", errors.New("m3u8 not of media type")
|
||||||
|
}
|
||||||
|
video := from.(*m3u8.MasterPlaylist)
|
||||||
|
var streamUrl *url.URL
|
||||||
|
for _, variant := range video.Variants {
|
||||||
|
for _, audiov := range variant.Alternatives {
|
||||||
|
if audiov.GroupId == "audio-stereo-256" {
|
||||||
|
streamUrl, _ = MediaUrl.Parse(audiov.URI)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if streamUrl == nil {
|
||||||
|
return "", errors.New("no video codec found")
|
||||||
|
}
|
||||||
|
return streamUrl.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func conventSyllableTTMLToLRC(ttml string) (string, error) {
|
func conventSyllableTTMLToLRC(ttml string) (string, error) {
|
||||||
parsedTTML := etree.NewDocument()
|
parsedTTML := etree.NewDocument()
|
||||||
err := parsedTTML.ReadFromString(ttml)
|
err := parsedTTML.ReadFromString(ttml)
|
||||||
|
@ -64,8 +64,8 @@ func (w *Key) GetKey(ctx context.Context, licenseServerURL string, PSSH string,
|
|||||||
|
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if key.Type == wv.License_KeyContainer_CONTENT {
|
if key.Type == wv.License_KeyContainer_CONTENT {
|
||||||
// command += "--key " + hex.EncodeToString(key.ID) + ":" + hex.EncodeToString(key.Value)
|
command += hex.EncodeToString(key.ID) + ":" + hex.EncodeToString(key.Value)
|
||||||
command += hex.EncodeToString(key.Value)
|
//command += hex.EncodeToString(key.Value)
|
||||||
keybt = key.Value
|
keybt = key.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ func AfterRequest(Response *requests.Response) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return License, nil
|
return License, nil
|
||||||
}
|
}
|
||||||
func getWebplayback(adamId string, authtoken string, mutoken string) (string, string, error) {
|
func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool) (string, string, error) {
|
||||||
url := "https://play.music.apple.com/WebObjects/MZPlay.woa/wa/webPlayback"
|
url := "https://play.music.apple.com/WebObjects/MZPlay.woa/wa/webPlayback"
|
||||||
postData := map[string]string{
|
postData := map[string]string{
|
||||||
"salableAdamId": adamId,
|
"salableAdamId": adamId,
|
||||||
@ -144,6 +144,9 @@ func getWebplayback(adamId string, authtoken string, mutoken string) (string, st
|
|||||||
fmt.Println("json err:", err)
|
fmt.Println("json err:", err)
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
if mvmode {
|
||||||
|
return obj.List[0].HlsPlaylistUrl, "", nil
|
||||||
|
}
|
||||||
if len(obj.List) > 0 {
|
if len(obj.List) > 0 {
|
||||||
// 遍历 Assets
|
// 遍历 Assets
|
||||||
for i, _ := range obj.List[0].Assets {
|
for i, _ := range obj.List[0].Assets {
|
||||||
@ -162,6 +165,7 @@ func getWebplayback(adamId string, authtoken string, mutoken string) (string, st
|
|||||||
type Songlist struct {
|
type Songlist struct {
|
||||||
List []struct {
|
List []struct {
|
||||||
Hlsurl string `json:"hls-key-cert-url"`
|
Hlsurl string `json:"hls-key-cert-url"`
|
||||||
|
HlsPlaylistUrl string `json:"hls-playlist-url"`
|
||||||
Assets []struct {
|
Assets []struct {
|
||||||
Flavor string `json:"flavor"`
|
Flavor string `json:"flavor"`
|
||||||
URL string `json:"URL"`
|
URL string `json:"URL"`
|
||||||
@ -235,11 +239,21 @@ func extsong(b string)(bytes.Buffer){
|
|||||||
io.Copy(io.MultiWriter(&buffer, bar), resp.Body)
|
io.Copy(io.MultiWriter(&buffer, bar), resp.Body)
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
func Run(adamId string, trackpath string, authtoken string, mutoken string)(error) {
|
func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmode bool)(string, error) {
|
||||||
|
var keystr string //for mv key
|
||||||
fileurl, kidBase64, err := getWebplayback(adamId, authtoken, mutoken)
|
var fileurl string
|
||||||
if err != nil {
|
var kidBase64 string
|
||||||
return err
|
var err error
|
||||||
|
if mvmode {
|
||||||
|
kidBase64, _, err = extractKidBase64(trackpath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileurl, kidBase64, err = GetWebplayback(adamId, authtoken, mutoken, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = context.WithValue(ctx, "pssh", kidBase64)
|
ctx = context.WithValue(ctx, "pssh", kidBase64)
|
||||||
@ -248,7 +262,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
|||||||
//fmt.Println(pssh)
|
//fmt.Println(pssh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
headers := map[string]interface{}{
|
headers := map[string]interface{}{
|
||||||
"authorization": "Bearer " + authtoken,
|
"authorization": "Bearer " + authtoken,
|
||||||
@ -263,10 +277,13 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
|||||||
AfterRequest: AfterRequest,
|
AfterRequest: AfterRequest,
|
||||||
}
|
}
|
||||||
key.CdmInit()
|
key.CdmInit()
|
||||||
_, keybt, err := key.GetKey(ctx, "https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/acquireWebPlaybackLicense", pssh, nil)
|
keystr, keybt, err := key.GetKey(ctx, "https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/acquireWebPlaybackLicense", pssh, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return err
|
return "", err
|
||||||
|
}
|
||||||
|
if mvmode {
|
||||||
|
return keystr, nil
|
||||||
}
|
}
|
||||||
body := extsong(fileurl)
|
body := extsong(fileurl)
|
||||||
fmt.Print("Downloaded\n")
|
fmt.Print("Downloaded\n")
|
||||||
@ -276,7 +293,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
|||||||
err = DecryptMP4(&body, keybt, &buffer)
|
err = DecryptMP4(&body, keybt, &buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print("Decryption failed\n")
|
fmt.Print("Decryption failed\n")
|
||||||
return err
|
return "", err
|
||||||
} else {
|
} else {
|
||||||
fmt.Print("Decrypted\n")
|
fmt.Print("Decrypted\n")
|
||||||
}
|
}
|
||||||
@ -284,16 +301,16 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
|||||||
ofh, err := os.Create(trackpath)
|
ofh, err := os.Create(trackpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("创建文件失败: %v\n", err)
|
fmt.Printf("创建文件失败: %v\n", err)
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
defer ofh.Close()
|
defer ofh.Close()
|
||||||
|
|
||||||
_, err = ofh.Write(buffer.Bytes())
|
_, err = ofh.Write(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("写入文件失败: %v\n", err)
|
fmt.Printf("写入文件失败: %v\n", err)
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
return nil
|
return "", nil
|
||||||
}
|
}
|
||||||
// DecryptMP4Auto decrypts a fragmented MP4 file with the set of keys retreived from the widevice license
|
// DecryptMP4Auto decrypts a fragmented MP4 file with the set of keys retreived from the widevice license
|
||||||
// by automatically selecting the appropriate key. Supports CENC and CBCS schemes.
|
// by automatically selecting the appropriate key. Supports CENC and CBCS schemes.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user