This commit is contained in:
zhaarey 2025-02-14 11:30:05 +08:00
parent 42ca991329
commit ac937f8076
2 changed files with 85 additions and 77 deletions

64
main.go
View File

@ -22,16 +22,15 @@ import (
"time" "time"
"main/utils/runv2" "main/utils/runv2"
"main/utils/structs"
"main/utils/runv3" "main/utils/runv3"
"main/utils/structs"
"github.com/spf13/pflag"
"github.com/zhaarey/go-mp4tag"
"gopkg.in/yaml.v2"
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/grafov/m3u8" "github.com/grafov/m3u8"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/spf13/pflag"
"github.com/zhaarey/go-mp4tag"
"gopkg.in/yaml.v2"
) )
var ( var (
@ -228,7 +227,7 @@ func checkArtist(artistUrl string, token string, relationship string) ([]string,
table := tablewriter.NewWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
if relationship == "albums" { if relationship == "albums" {
table.SetHeader([]string{"", "Album Name", "Date", "Album ID"}) table.SetHeader([]string{"", "Album Name", "Date", "Album ID"})
}else if relationship == "music-videos" { } else if relationship == "music-videos" {
table.SetHeader([]string{"", "MV Name", "Date", "MV ID"}) table.SetHeader([]string{"", "MV Name", "Date", "MV ID"})
} }
//table.SetFooter([]string{"", "", "Total", "$146.93"}) //table.SetFooter([]string{"", "", "Total", "$146.93"})
@ -744,8 +743,8 @@ func rip(albumId string, token string, storefront string, mediaUserToken string,
} }
if needCheck { if needCheck {
fullM3u8Url, err := checkM3u8(track.ID, "song") fullM3u8Url, err := checkM3u8(track.ID, "song")
if err == nil && strings.HasSuffix(fullM3u8Url, ".m3u8"){ if err == nil && strings.HasSuffix(fullM3u8Url, ".m3u8") {
m3u8Url = fullM3u8Url m3u8Url = fullM3u8Url
} else { } else {
fmt.Println("Failed to get best quality m3u8 from device m3u8 port, will use m3u8 from Web API") fmt.Println("Failed to get best quality m3u8 from device m3u8 port, will use m3u8 from Web API")
} }
@ -1001,7 +1000,7 @@ func rip(albumId string, token string, storefront string, mediaUserToken string,
//table.SetFooter([]string{"", "", "Footer", "Footer4"}) //table.SetFooter([]string{"", "", "Footer", "Footer4"})
table.SetRowLine(false) table.SetRowLine(false)
//table.SetAutoMergeCells(true) //table.SetAutoMergeCells(true)
table.SetCaption(meta.Data[0].Type == "albums", fmt.Sprintf("Storefront: %s, %d tracks missing", strings.ToUpper(storefront), meta.Data[0].Attributes.TrackCount -trackTotal)) table.SetCaption(meta.Data[0].Type == "albums", fmt.Sprintf("Storefront: %s, %d tracks missing", strings.ToUpper(storefront), meta.Data[0].Attributes.TrackCount-trackTotal))
table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor}, table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor}, tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor}, tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
@ -1369,6 +1368,9 @@ func mvDownloader(adamID string, saveDir string, token string, storefront string
} }
mvm3u8url, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true) mvm3u8url, _, _ := runv3.GetWebplayback(adamID, token, mediaUserToken, true)
if mvm3u8url == "" {
return errors.New("media-user-token may wrong or expired.")
}
os.MkdirAll(saveDir, os.ModePerm) os.MkdirAll(saveDir, os.ModePerm)
//video //video
@ -1428,6 +1430,26 @@ func mvDownloader(adamID string, saveDir string, token string, storefront string
//tags = append(tags, fmt.Sprintf("album_artist=%s", MVInfo.Data[0].Attributes.ArtistName)) //tags = append(tags, fmt.Sprintf("album_artist=%s", MVInfo.Data[0].Attributes.ArtistName))
tags = append(tags, fmt.Sprintf("performer=%s", MVInfo.Data[0].Attributes.ArtistName)) tags = append(tags, fmt.Sprintf("performer=%s", MVInfo.Data[0].Attributes.ArtistName))
} }
// Extract and save thumbnail if enabled
if Config.SaveThumbnailImage {
// Get the highest quality thumbnail URL from the MV info
thumbURL := MVInfo.Data[0].Attributes.Artwork.URL
thumbURL = strings.Replace(thumbURL, "{w}x{h}", Config.CoverSize, 1)
// Generate base name without extension
baseThumbName := forbiddenNames.ReplaceAllString(mvSaveName, "_") + "_thumbnail"
// Download and save thumbnail
err = writeCover(saveDir, baseThumbName, thumbURL)
if err != nil {
fmt.Println("Failed to save MV thumbnail:", err)
} else {
fmt.Println("MV thumbnail saved successfully")
tags = append(tags, fmt.Sprintf("cover=%s/%s.%s", saveDir, baseThumbName, Config.CoverFormat))
}
}
//mux and add tag //mux and add tag
tagsString := strings.Join(tags, ":") tagsString := strings.Join(tags, ":")
muxCmd := exec.Command("MP4Box", "-itags", tagsString, "-quiet", "-add", vidPath, "-add", audPath, "-keep-utc", "-new", mvOutPath) muxCmd := exec.Command("MP4Box", "-itags", tagsString, "-quiet", "-add", vidPath, "-add", audPath, "-keep-utc", "-new", mvOutPath)
@ -1440,24 +1462,6 @@ func mvDownloader(adamID string, saveDir string, token string, storefront string
defer os.Remove(vidPath) defer os.Remove(vidPath)
defer os.Remove(audPath) defer os.Remove(audPath)
// Extract and save thumbnail if enabled
if Config.SaveThumbnailImage {
// Get the highest quality thumbnail URL from the MV info
thumbURL := MVInfo.Data[0].Attributes.Artwork.URL
thumbURL = strings.Replace(thumbURL, "{w}x{h}", Config.CoverSize, 1)
// Generate base name without extension
baseThumbName := forbiddenNames.ReplaceAllString(mvSaveName, "_") + "_thumbnail"
// Download and save thumbnail
err = writeCover(saveDir, baseThumbName, thumbURL)
if err != nil {
fmt.Println("Failed to save MV thumbnail:", err)
} else {
fmt.Println("MV thumbnail saved successfully")
}
}
return nil return nil
} }
@ -1785,7 +1789,7 @@ func extractMedia(b string, more_mode bool) (string, string, error) {
} }
resp, err := http.Get(b) resp, err := http.Get(b)
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 {
@ -1809,9 +1813,9 @@ func extractMedia(b string, more_mode bool) (string, string, error) {
fmt.Println("\nDebug: All Available Variants:") fmt.Println("\nDebug: All Available Variants:")
var data [][]string var data [][]string
for _, variant := range master.Variants { for _, variant := range master.Variants {
data = append(data, []string{variant.Codecs, variant.Audio, fmt.Sprint(variant.Bandwidth)}) data = append(data, []string{variant.Codecs, variant.Audio, fmt.Sprint(variant.Bandwidth)})
//fmt.Printf("Codec: %s, Audio: %s, Bandwidth: %d\n", //fmt.Printf("Codec: %s, Audio: %s, Bandwidth: %d\n",
//variant.Codecs, variant.Audio, variant.Bandwidth) //variant.Codecs, variant.Audio, variant.Bandwidth)
} }
table := tablewriter.NewWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Codec", "Audio", "Bandwidth"}) table.SetHeader([]string{"Codec", "Audio", "Bandwidth"})

View File

@ -4,25 +4,29 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/gospider007/requests" "github.com/gospider007/requests"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
//"log/slog" //"log/slog"
"os"
cdm "main/utils/runv3/cdm" cdm "main/utils/runv3/cdm"
key "main/utils/runv3/key" key "main/utils/runv3/key"
"os"
"bytes"
"errors"
"io"
"github.com/Eyevinn/mp4ff/mp4" "github.com/Eyevinn/mp4ff/mp4"
"bytes"
"io"
"errors"
//"io/ioutil" //"io/ioutil"
"net/http"
"encoding/json" "encoding/json"
"github.com/grafov/m3u8" "net/http"
"strings"
"github.com/schollz/progressbar/v3"
"os/exec" "os/exec"
"strings"
"github.com/grafov/m3u8"
"github.com/schollz/progressbar/v3"
) )
type PlaybackLicense struct { type PlaybackLicense struct {
@ -32,24 +36,24 @@ type PlaybackLicense struct {
Status int `json:"status"` Status int `json:"status"`
} }
// func log() { // func log() {
// f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) // f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
// if err != nil { // if err != nil {
// slog.Error("error opening file: %s", err) // slog.Error("error opening file: %s", err)
// } // }
// defer func(f *os.File) { // defer func(f *os.File) {
// err := f.Close() // err := f.Close()
// if err != nil { // if err != nil {
// slog.Error("error closing file: %s", err) // slog.Error("error closing file: %s", err)
// } // }
// }(f) // }(f)
// opts := slog.HandlerOptions{ // opts := slog.HandlerOptions{
// AddSource: true, // AddSource: true,
// Level: slog.LevelDebug, // Level: slog.LevelDebug,
// } // }
// logger := slog.New(slog.NewJSONHandler(os.Stdout, &opts)) // logger := slog.New(slog.NewJSONHandler(os.Stdout, &opts))
// slog.SetDefault(logger) // slog.SetDefault(logger)
//} // }
func getPSSH(contentId string, kidBase64 string) (string, error) { func getPSSH(contentId string, kidBase64 string) (string, error) {
kidBytes, err := base64.StdEncoding.DecodeString(kidBase64) kidBytes, err := base64.StdEncoding.DecodeString(kidBase64)
if err != nil { if err != nil {
@ -145,10 +149,10 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool
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 {
if mvmode {
return obj.List[0].HlsPlaylistUrl, "", nil
}
// 遍历 Assets // 遍历 Assets
for i, _ := range obj.List[0].Assets { for i, _ := range obj.List[0].Assets {
if obj.List[0].Assets[i].Flavor == "28:ctrp256" { if obj.List[0].Assets[i].Flavor == "28:ctrp256" {
@ -163,15 +167,16 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool
} }
return "", "", nil return "", "", nil
} }
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"` 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"`
}`json:"assets"` } `json:"assets"`
}`json:"songList"` } `json:"songList"`
Status int `json:"status"` Status int `json:"status"`
} }
@ -202,9 +207,9 @@ func extractKidBase64(b string, mvmode bool) (string, string, error) {
kidbase64 = split[1] kidbase64 = split[1]
lastSlashIndex := strings.LastIndex(b, "/") lastSlashIndex := strings.LastIndex(b, "/")
// 截取最后一个斜杠之前的部分 // 截取最后一个斜杠之前的部分
urlBuilder.WriteString(b[:lastSlashIndex]) urlBuilder.WriteString(b[:lastSlashIndex])
urlBuilder.WriteString("/") urlBuilder.WriteString("/")
urlBuilder.WriteString(mediaPlaylist.Map.URI) urlBuilder.WriteString(mediaPlaylist.Map.URI)
//fileurl = b[:lastSlashIndex] + "/" + mediaPlaylist.Map.URI //fileurl = b[:lastSlashIndex] + "/" + mediaPlaylist.Map.URI
//fmt.Println("Extracted URI:", mediaPlaylist.Map.URI) //fmt.Println("Extracted URI:", mediaPlaylist.Map.URI)
if mvmode { if mvmode {
@ -212,9 +217,9 @@ func extractKidBase64(b string, mvmode bool) (string, string, error) {
if segment != nil { if segment != nil {
//fmt.Println("Extracted URI:", segment.URI) //fmt.Println("Extracted URI:", segment.URI)
urlBuilder.WriteString(";") urlBuilder.WriteString(";")
urlBuilder.WriteString(b[:lastSlashIndex]) urlBuilder.WriteString(b[:lastSlashIndex])
urlBuilder.WriteString("/") urlBuilder.WriteString("/")
urlBuilder.WriteString(segment.URI) urlBuilder.WriteString(segment.URI)
//fileurl = fileurl + ";" + b[:lastSlashIndex] + "/" + segment.URI //fileurl = fileurl + ";" + b[:lastSlashIndex] + "/" + segment.URI
} }
} }
@ -227,7 +232,7 @@ func extractKidBase64(b string, mvmode bool) (string, string, error) {
} }
return kidbase64, urlBuilder.String(), nil return kidbase64, urlBuilder.String(), nil
} }
func extsong(b string)(bytes.Buffer){ func extsong(b string) bytes.Buffer {
resp, err := http.Get(b) resp, err := http.Get(b)
if err != nil { if err != nil {
fmt.Printf("下载文件失败: %v\n", err) fmt.Printf("下载文件失败: %v\n", err)
@ -255,8 +260,8 @@ 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, mvmode bool)(string, error) { func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmode bool) (string, error) {
var keystr string //for mv key var keystr string //for mv key
var fileurl string var fileurl string
var kidBase64 string var kidBase64 string
var err error var err error
@ -281,7 +286,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo
return "", err return "", err
} }
headers := map[string]interface{}{ headers := map[string]interface{}{
"authorization": "Bearer " + authtoken, "authorization": "Bearer " + authtoken,
"x-apple-music-user-token": mutoken, "x-apple-music-user-token": mutoken,
} }
client, _ := requests.NewClient(nil, requests.ClientOption{ client, _ := requests.NewClient(nil, requests.ClientOption{
@ -330,7 +335,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo
return "", nil return "", nil
} }
func ExtMvData (keyAndUrls string, savePath string)(error) { func ExtMvData(keyAndUrls string, savePath string) error {
segments := strings.Split(keyAndUrls, ";") segments := strings.Split(keyAndUrls, ";")
key := segments[0] key := segments[0]
//fmt.Println(key) //fmt.Println(key)
@ -345,7 +350,7 @@ func ExtMvData (keyAndUrls string, savePath string)(error) {
// 依次下载每个链接并写入文件 // 依次下载每个链接并写入文件
bar := progressbar.DefaultBytes( bar := progressbar.DefaultBytes(
-1, -1,
"Downloading...", "Downloading...",
) )
barWriter := io.MultiWriter(tempFile, bar) barWriter := io.MultiWriter(tempFile, bar)
@ -384,7 +389,6 @@ func ExtMvData (keyAndUrls string, savePath string)(error) {
return nil return nil
} }
// DecryptMP4 decrypts a fragmented MP4 file with keys from widevice license. Supports CENC and CBCS schemes. // 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 { func DecryptMP4(r io.Reader, key []byte, w io.Writer) error {
// Initialization // Initialization