commit
67623889a3
55
README.md
55
README.md
@ -2,23 +2,15 @@
|
||||
|
||||
### 添加功能
|
||||
|
||||
1. 调用外部MP4Box添加tag
|
||||
2. 更改目录结构为 歌手名\专辑名 ;Atmos下载文件则另外移动到AM-DL-Atmos downloads,并更改目录结构为 歌手名\专辑名 [Atmos]
|
||||
3. 运行结束后显示总体完成情况
|
||||
4. 自动内嵌封面和LRC歌词(需要media-user-token,获取方式看最后的说明)
|
||||
5. 自动构建 可以到 [Actions](https://github.com/zhaarey/apple-music-alac-atmos-downloader/actions) 页面下载最新自动构建版本 可以直接`main.exe url`
|
||||
6. 支持逐词与未同步歌词
|
||||
7. 新增get-m3u8-from-device 改为true 且设置端口`adb forward tcp:20020 tcp:20020`即从模拟器获取m3u8
|
||||
8. 文件夹和文件支持模板
|
||||
9. 支持下载歌手 `go run main.go https://music.apple.com/us/artist/taylor-swift/159260351` `--all-album` 自动选择歌手的所有专辑
|
||||
10. 新增[wrapper](https://github.com/zhaarey/wrapper/releases)模式 目前只能linux运行,解密速度超快,基本秒解
|
||||
11. `limit-max`支持限制长度 默认200
|
||||
12. 现已支持arm64解密
|
||||
13. 下载解密部分更换为Sendy McSenderson的代码,实现边下载边解密
|
||||
1. 支持内嵌封面和LRC歌词(需要`media-user-token`,获取方式看最后的说明)
|
||||
2. 支持获取逐词与未同步歌词
|
||||
3. 支持下载歌手 `go run main.go https://music.apple.com/us/artist/taylor-swift/159260351` `--all-album` 自动选择歌手的所有专辑
|
||||
4. 下载解密部分更换为Sendy McSenderson的代码,实现边下载边解密,解决大文件解密时内存不足
|
||||
5. MV下载,需要安装[mp4decrypt](https://www.bento4.com/downloads/)
|
||||
|
||||
### Special thanks to `chocomint` for creating `agent-arm64.js`
|
||||
|
||||
本项目暂不支持MV下载,对于下载aac-lc则必须填入有订阅的media-user-token
|
||||
对于获取`aac-lc` `MV` `歌词` 必须填入有订阅的`media-user-token`
|
||||
|
||||
- `alac (audio-alac-stereo)`
|
||||
- `ec3 (audio-atmos / audio-ec3)`
|
||||
@ -26,38 +18,21 @@
|
||||
- `aac-lc (audio-stereo)`
|
||||
- `aac-binaural (audio-stereo-binaural)`
|
||||
- `aac-downmix (audio-stereo-downmix)`
|
||||
|
||||
- `MV`
|
||||
|
||||
# Apple Music ALAC / Dolby Atmos Downloader
|
||||
|
||||
Original script by Sorrow. Modified by me to include some fixes and improvements.
|
||||
|
||||
## How to use
|
||||
|
||||
1. Create a virtual device on Android Studio with a image that doesn't have Google APIs.
|
||||
2. Install Apple Music
|
||||
|
||||
for x86 install this version of [Apple Music 3.6.0 beta4](https://www.apkmirror.com/apk/apple/apple-music/apple-music-3-6-0-beta-release/apple-music-3-6-0-beta-4-android-apk-download/). You will also need [SAI](https://f-droid.org/pt_BR/packages/com.aefyr.sai.fdroid/) to install it.
|
||||
|
||||
for arm64 install the last version of [Apple Music](https://www.apkmirror.com/apk/apple/apple-music/).
|
||||
|
||||
3. Launch Apple Music and sign in to your account. Subscription required.
|
||||
4. Port forward 10020 TCP: `adb forward tcp:10020 tcp:10020`.
|
||||
5. Start frida server.
|
||||
6. Start the frida agent:
|
||||
|
||||
for x86 `frida -U -l agent.js -f com.apple.android.music`
|
||||
|
||||
for arm64 `frida -U -l agent-arm64.js -f com.apple.android.music`
|
||||
|
||||
|
||||
7. Start downloading some albums: `go run main.go https://music.apple.com/us/album/whenever-you-need-somebody-2022-remaster/1624945511`.
|
||||
8. Start downloading single song: `go run main.go --song https://music.apple.com/us/album/never-gonna-give-you-up-2022-remaster/1624945511?i=1624945512` or `go run main.go https://music.apple.com/us/song/you-move-me-2022-remaster/1624945520`.
|
||||
9. Start downloading select: `go run main.go --select https://music.apple.com/us/album/whenever-you-need-somebody-2022-remaster/1624945511` input numbers separated by spaces.
|
||||
10. Start downloading some playlists: `go run main.go https://music.apple.com/us/playlist/taylor-swift-essentials/pl.3950454ced8c45a3b0cc693c2a7db97b` or `go run main.go https://music.apple.com/us/playlist/hi-res-lossless-24-bit-192khz/pl.u-MDAWvpjt38370N`.
|
||||
11. For dolby atmos: `go run main.go --atmos https://music.apple.com/us/album/1989-taylors-version-deluxe/1713845538`.
|
||||
12. For aac: `go run main.go --aac https://music.apple.com/us/album/1989-taylors-version-deluxe/1713845538`.
|
||||
13. For see quality: `go run main.go --debug https://music.apple.com/us/album/1989-taylors-version-deluxe/1713845538`.
|
||||
1. Make sure the decryption program [wrapper](https://github.com/zhaarey/wrapper) is running
|
||||
2. Start downloading some albums: `go run main.go https://music.apple.com/us/album/whenever-you-need-somebody-2022-remaster/1624945511`.
|
||||
3. Start downloading single song: `go run main.go --song https://music.apple.com/us/album/never-gonna-give-you-up-2022-remaster/1624945511?i=1624945512` or `go run main.go https://music.apple.com/us/song/you-move-me-2022-remaster/1624945520`.
|
||||
4. Start downloading select: `go run main.go --select https://music.apple.com/us/album/whenever-you-need-somebody-2022-remaster/1624945511` input numbers separated by spaces.
|
||||
5. Start downloading some playlists: `go run main.go https://music.apple.com/us/playlist/taylor-swift-essentials/pl.3950454ced8c45a3b0cc693c2a7db97b` or `go run main.go https://music.apple.com/us/playlist/hi-res-lossless-24-bit-192khz/pl.u-MDAWvpjt38370N`.
|
||||
6. For dolby atmos: `go run main.go --atmos https://music.apple.com/us/album/1989-taylors-version-deluxe/1713845538`.
|
||||
7. For aac: `go run main.go --aac https://music.apple.com/us/album/1989-taylors-version-deluxe/1713845538`.
|
||||
8. For see quality: `go run main.go --debug https://music.apple.com/us/album/1989-taylors-version-deluxe/1713845538`.
|
||||
|
||||
[中文教程-详见方法三](https://telegra.ph/Apple-Music-Alac高解析度无损音乐下载教程-04-02-2)
|
||||
|
||||
|
1
go.mod
1
go.mod
@ -94,5 +94,6 @@ require (
|
||||
|
||||
require (
|
||||
github.com/beevik/etree v1.3.0
|
||||
github.com/zhaarey/go-mp4tag v0.0.0-20250210094042-22578afc09bf
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -243,8 +243,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zhaarey/go-mp4tag v0.0.0-20250210082810-2486d83a2076 h1:8Tm1jvhu6pmpW6oiYQ+KiPE7dGwUoDeDs0eD3YpxdOI=
|
||||
github.com/zhaarey/go-mp4tag v0.0.0-20250210082810-2486d83a2076/go.mod h1:cqL6le//aG0AE1/VE1um2m+8dKa8te/WhHWqzrHMDys=
|
||||
github.com/zhaarey/go-mp4tag v0.0.0-20250210094042-22578afc09bf h1:WzZoh9wvukQu2We8dw/bFmLfb5XsC5bGGU/Izhd/UOo=
|
||||
github.com/zhaarey/go-mp4tag v0.0.0-20250210094042-22578afc09bf/go.mod h1:cqL6le//aG0AE1/VE1um2m+8dKa8te/WhHWqzrHMDys=
|
||||
go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
|
||||
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
140
main.go
140
main.go
@ -100,6 +100,16 @@ func checkUrl(url string) (string, string) {
|
||||
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) {
|
||||
pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/song|\/song\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
|
||||
matches := pat.FindAllStringSubmatch(url, -1)
|
||||
@ -390,7 +400,7 @@ func writeCover(sanAlbumFolder, name string, url string) error {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
_ = os.Remove(covPath)
|
||||
}
|
||||
if Config.CoverFormat == "png" {
|
||||
re := regexp.MustCompile(`\{w\}x\{h\}`)
|
||||
@ -454,6 +464,29 @@ func contains(slice []string, item string) bool {
|
||||
func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, track structs.TrackData, albumId, token, storefront, mediaUserToken, sanAlbumFolder, Codec string, counter *structs.Counter) {
|
||||
counter.Total++
|
||||
fmt.Printf("Track %d of %d:\n", trackNum, trackTotal)
|
||||
|
||||
//mv dl dev
|
||||
if track.Type == "music-videos" {
|
||||
if mediaUserToken == "" || len(mediaUserToken) <= 10 {
|
||||
fmt.Println("meida-user-token is not set, skip MV dl")
|
||||
counter.Success++
|
||||
return
|
||||
}
|
||||
if _, err := exec.LookPath("mp4decrypt"); err != nil {
|
||||
fmt.Println("mp4decrypt is not found, skip MV dl")
|
||||
counter.Success++
|
||||
return
|
||||
}
|
||||
err := mvDownloader(track.ID, sanAlbumFolder, token, mediaUserToken)
|
||||
if err !=nil {
|
||||
fmt.Println("\u26A0 Failed to dl MV:", err)
|
||||
counter.Error++
|
||||
return
|
||||
}
|
||||
counter.Success++
|
||||
return
|
||||
}
|
||||
|
||||
manifest, err := getInfoFromAdam(track.ID, token, storefront)
|
||||
if err != nil {
|
||||
fmt.Println("\u26A0 Failed to get manifest:", err)
|
||||
@ -583,7 +616,7 @@ func downloadTrack(trackNum int, trackTotal int, meta *structs.AutoGenerated, tr
|
||||
counter.Error++
|
||||
return
|
||||
}
|
||||
err := runv3.Run(track.ID, trackPath, token, mediaUserToken)
|
||||
_, err := runv3.Run(track.ID, trackPath, token, mediaUserToken, false)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to dl aac-lc:", err)
|
||||
counter.Error++
|
||||
@ -1114,6 +1147,31 @@ func main() {
|
||||
for albumNum, urlRaw := range os.Args {
|
||||
fmt.Printf("Album %d of %d:\n", albumNum+1, albumTotal)
|
||||
var storefront, albumId string
|
||||
//mv dl dev
|
||||
if strings.Contains(urlRaw, "/music-video/") {
|
||||
if debug_mode {
|
||||
continue
|
||||
}
|
||||
counter.Total++
|
||||
if Config.MediaUserToken == "" || len(Config.MediaUserToken) <= 10 {
|
||||
fmt.Println("meida-user-token is not set, skip MV dl")
|
||||
counter.Success++
|
||||
continue
|
||||
}
|
||||
if _, err := exec.LookPath("mp4decrypt"); err != nil {
|
||||
fmt.Println("mp4decrypt is not found, skip MV dl")
|
||||
counter.Success++
|
||||
continue
|
||||
}
|
||||
err := mvDownloader(checkUrlMv(urlRaw), Config.AlacSaveFolder, token, Config.MediaUserToken)
|
||||
if err !=nil {
|
||||
fmt.Println("\u26A0 Failed to dl MV:", err)
|
||||
counter.Error++
|
||||
continue
|
||||
}
|
||||
counter.Success++
|
||||
continue
|
||||
}
|
||||
if strings.Contains(urlRaw, "/song/") {
|
||||
urlRaw, err = getUrlSong(urlRaw, token)
|
||||
dl_song = true
|
||||
@ -1151,6 +1209,84 @@ func main() {
|
||||
counter = structs.Counter{}
|
||||
}
|
||||
}
|
||||
func mvDownloader(adamID string, saveDir string, token string, mediaUserToken string)(error){
|
||||
vidPath := filepath.Join(saveDir, fmt.Sprintf("%s_vid.mp4", adamID))
|
||||
audPath := filepath.Join(saveDir, fmt.Sprintf("%s_aud.mp4", 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)
|
||||
|
||||
videokeyAndUrls, _ := runv3.Run(adamID, videom3u8url, token, mediaUserToken, true)
|
||||
audiokeyAndUrls, _ := runv3.Run(adamID, audiom3u8url, token, mediaUserToken, true)
|
||||
//fmt.Println(videokeyAndUrls)
|
||||
//fmt.Println(audiokeyAndUrls)
|
||||
|
||||
os.MkdirAll(saveDir, os.ModePerm)
|
||||
fmt.Println("MV-VIDEO")
|
||||
_ = runv3.ExtMvData(videokeyAndUrls, vidPath)
|
||||
|
||||
fmt.Println("MV-AUDIO")
|
||||
_ = runv3.ExtMvData(audiokeyAndUrls, audPath)
|
||||
|
||||
muxCmd := exec.Command("MP4Box", "-quiet", "-add", vidPath, "-add", audPath, "-keep-utc", "-new", mvOutPath)
|
||||
fmt.Printf("MV Remuxing...")
|
||||
if err := muxCmd.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) {
|
||||
parsedTTML := etree.NewDocument()
|
||||
|
@ -64,7 +64,7 @@ func (w *Key) GetKey(ctx context.Context, licenseServerURL string, PSSH string,
|
||||
|
||||
for _, key := range keys {
|
||||
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)
|
||||
keybt = key.Value
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/grafov/m3u8"
|
||||
"strings"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type PlaybackLicense struct {
|
||||
@ -106,7 +107,7 @@ func AfterRequest(Response *requests.Response) ([]byte, error) {
|
||||
}
|
||||
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"
|
||||
postData := map[string]string{
|
||||
"salableAdamId": adamId,
|
||||
@ -144,11 +145,14 @@ func getWebplayback(adamId string, authtoken string, mutoken string) (string, st
|
||||
fmt.Println("json err:", err)
|
||||
return "", "", err
|
||||
}
|
||||
if mvmode {
|
||||
return obj.List[0].HlsPlaylistUrl, "", nil
|
||||
}
|
||||
if len(obj.List) > 0 {
|
||||
// 遍历 Assets
|
||||
for i, _ := range obj.List[0].Assets {
|
||||
if obj.List[0].Assets[i].Flavor == "28:ctrp256" {
|
||||
kidBase64, fileurl, err := extractKidBase64(obj.List[0].Assets[i].URL)
|
||||
kidBase64, fileurl, err := extractKidBase64(obj.List[0].Assets[i].URL, false)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -162,6 +166,7 @@ func getWebplayback(adamId string, authtoken string, mutoken string) (string, st
|
||||
type Songlist struct {
|
||||
List []struct {
|
||||
Hlsurl string `json:"hls-key-cert-url"`
|
||||
HlsPlaylistUrl string `json:"hls-playlist-url"`
|
||||
Assets []struct {
|
||||
Flavor string `json:"flavor"`
|
||||
URL string `json:"URL"`
|
||||
@ -170,7 +175,7 @@ type Songlist struct {
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
func extractKidBase64(b string) (string, string, error) {
|
||||
func extractKidBase64(b string, mvmode bool) (string, string, error) {
|
||||
resp, err := http.Get(b)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@ -189,7 +194,7 @@ func extractKidBase64(b string) (string, string, error) {
|
||||
return "", "", err
|
||||
}
|
||||
var kidbase64 string
|
||||
var fileurl string
|
||||
var urlBuilder strings.Builder
|
||||
if listType == m3u8.MEDIA {
|
||||
mediaPlaylist := from.(*m3u8.MediaPlaylist)
|
||||
if mediaPlaylist.Key != nil {
|
||||
@ -197,15 +202,30 @@ func extractKidBase64(b string) (string, string, error) {
|
||||
kidbase64 = split[1]
|
||||
lastSlashIndex := strings.LastIndex(b, "/")
|
||||
// 截取最后一个斜杠之前的部分
|
||||
fileurl = b[:lastSlashIndex] + "/" + mediaPlaylist.Map.URI
|
||||
urlBuilder.WriteString(b[:lastSlashIndex])
|
||||
urlBuilder.WriteString("/")
|
||||
urlBuilder.WriteString(mediaPlaylist.Map.URI)
|
||||
//fileurl = b[:lastSlashIndex] + "/" + mediaPlaylist.Map.URI
|
||||
//fmt.Println("Extracted URI:", mediaPlaylist.Map.URI)
|
||||
if mvmode {
|
||||
for _, segment := range mediaPlaylist.Segments {
|
||||
if segment != nil {
|
||||
//fmt.Println("Extracted URI:", segment.URI)
|
||||
urlBuilder.WriteString(";")
|
||||
urlBuilder.WriteString(b[:lastSlashIndex])
|
||||
urlBuilder.WriteString("/")
|
||||
urlBuilder.WriteString(segment.URI)
|
||||
//fileurl = fileurl + ";" + b[:lastSlashIndex] + "/" + segment.URI
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("No key information found")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Not a media playlist")
|
||||
}
|
||||
return kidbase64, fileurl, nil
|
||||
return kidbase64, urlBuilder.String(), nil
|
||||
}
|
||||
func extsong(b string)(bytes.Buffer){
|
||||
resp, err := http.Get(b)
|
||||
@ -235,11 +255,21 @@ func extsong(b string)(bytes.Buffer){
|
||||
io.Copy(io.MultiWriter(&buffer, bar), resp.Body)
|
||||
return buffer
|
||||
}
|
||||
func Run(adamId string, trackpath string, authtoken string, mutoken string)(error) {
|
||||
|
||||
fileurl, kidBase64, err := getWebplayback(adamId, authtoken, mutoken)
|
||||
if err != nil {
|
||||
return err
|
||||
func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmode bool)(string, error) {
|
||||
var keystr string //for mv key
|
||||
var fileurl string
|
||||
var kidBase64 string
|
||||
var err error
|
||||
if mvmode {
|
||||
kidBase64, fileurl, err = extractKidBase64(trackpath, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
fileurl, kidBase64, err = GetWebplayback(adamId, authtoken, mutoken, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "pssh", kidBase64)
|
||||
@ -248,7 +278,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
||||
//fmt.Println(pssh)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
headers := map[string]interface{}{
|
||||
"authorization": "Bearer " + authtoken,
|
||||
@ -263,10 +293,14 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
||||
AfterRequest: AfterRequest,
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if mvmode {
|
||||
keyAndUrls := "1:" + keystr + ";" + fileurl
|
||||
return keyAndUrls, nil
|
||||
}
|
||||
body := extsong(fileurl)
|
||||
fmt.Print("Downloaded\n")
|
||||
@ -276,7 +310,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
||||
err = DecryptMP4(&body, keybt, &buffer)
|
||||
if err != nil {
|
||||
fmt.Print("Decryption failed\n")
|
||||
return err
|
||||
return "", err
|
||||
} else {
|
||||
fmt.Print("Decrypted\n")
|
||||
}
|
||||
@ -284,34 +318,69 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string)(erro
|
||||
ofh, err := os.Create(trackpath)
|
||||
if err != nil {
|
||||
fmt.Printf("创建文件失败: %v\n", err)
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
defer ofh.Close()
|
||||
|
||||
_, err = ofh.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
fmt.Printf("写入文件失败: %v\n", err)
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func ExtMvData (keyAndUrls string, savePath string)(error) {
|
||||
segments := strings.Split(keyAndUrls, ";")
|
||||
key := segments[0]
|
||||
//fmt.Println(key)
|
||||
urls := segments[1:]
|
||||
tempFile, err := os.CreateTemp("", "enc_mv_data-*.mp4")
|
||||
if err != nil {
|
||||
fmt.Printf("创建文件失败:%v\n", err)
|
||||
return err
|
||||
}
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// 依次下载每个链接并写入文件
|
||||
bar := progressbar.DefaultBytes(
|
||||
-1,
|
||||
"Downloading...",
|
||||
)
|
||||
barWriter := io.MultiWriter(tempFile, bar)
|
||||
for _, url := range urls {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Printf("下载链接 %s 失败:%v\n", url, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 将响应体写入输出文件
|
||||
_, err = io.Copy(barWriter, resp.Body)
|
||||
defer resp.Body.Close() // 注意及时关闭响应体,避免资源泄露
|
||||
if err != nil {
|
||||
fmt.Printf("写入文件失败:%v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
//fmt.Printf("第 %d 个链接 %s 下载并写入完成\n", idx+1, url)
|
||||
}
|
||||
tempFile.Close()
|
||||
fmt.Println("\nDownloaded.")
|
||||
|
||||
cmd1 := exec.Command("mp4decrypt", "--key", key, tempFile.Name(), savePath)
|
||||
outlog, err := cmd1.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("Decrypt failed: %v\n", err)
|
||||
fmt.Printf("Output:\n%s\n", outlog)
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("Decrypted.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 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.
|
||||
// func DecryptMP4Auto(r io.Reader, keys []*Key, w io.Writer) error {
|
||||
// // Extract content key
|
||||
// var key []byte
|
||||
// for _, k := range keys {
|
||||
// if k.Type == wvpb.License_KeyContainer_CONTENT {
|
||||
// key = k.Key
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if key == nil {
|
||||
// return fmt.Errorf("no %s key type found in the provided key set", wvpb.License_KeyContainer_CONTENT)
|
||||
// }
|
||||
// // Execute decryption
|
||||
// return DecryptMP4(r, key, w)
|
||||
// }
|
||||
|
||||
|
||||
// DecryptMP4 decrypts a fragmented MP4 file with keys from widevice license. Supports CENC and CBCS schemes.
|
||||
func DecryptMP4(r io.Reader, key []byte, w io.Writer) error {
|
||||
|
Loading…
x
Reference in New Issue
Block a user