diff --git a/main.go b/main.go index 0f2ddee..7e870ca 100644 --- a/main.go +++ b/main.go @@ -22,16 +22,15 @@ import ( "time" "main/utils/runv2" - "main/utils/structs" "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/grafov/m3u8" "github.com/olekukonko/tablewriter" - + "github.com/spf13/pflag" + "github.com/zhaarey/go-mp4tag" + "gopkg.in/yaml.v2" ) var ( @@ -228,7 +227,7 @@ func checkArtist(artistUrl string, token string, relationship string) ([]string, table := tablewriter.NewWriter(os.Stdout) if relationship == "albums" { 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.SetFooter([]string{"", "", "Total", "$146.93"}) @@ -744,8 +743,8 @@ func rip(albumId string, token string, storefront string, mediaUserToken string, } if needCheck { fullM3u8Url, err := checkM3u8(track.ID, "song") - if err == nil && strings.HasSuffix(fullM3u8Url, ".m3u8"){ - m3u8Url = fullM3u8Url + if err == nil && strings.HasSuffix(fullM3u8Url, ".m3u8") { + m3u8Url = fullM3u8Url } else { 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.SetRowLine(false) //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}, tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor}, 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) + if mvm3u8url == "" { + return errors.New("media-user-token may wrong or expired.") + } os.MkdirAll(saveDir, os.ModePerm) //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("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 tagsString := strings.Join(tags, ":") 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(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 } @@ -1785,7 +1789,7 @@ func extractMedia(b string, more_mode bool) (string, string, error) { } resp, err := http.Get(b) if err != nil { - return "", "",err + return "", "", err } defer resp.Body.Close() 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:") var data [][]string 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", - //variant.Codecs, variant.Audio, variant.Bandwidth) + //variant.Codecs, variant.Audio, variant.Bandwidth) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Codec", "Audio", "Bandwidth"}) diff --git a/utils/runv3/runv3.go b/utils/runv3/runv3.go index 3c529b2..50d90ef 100644 --- a/utils/runv3/runv3.go +++ b/utils/runv3/runv3.go @@ -4,25 +4,29 @@ import ( "context" "encoding/base64" "fmt" + "github.com/gospider007/requests" "google.golang.org/protobuf/proto" + //"log/slog" - "os" cdm "main/utils/runv3/cdm" key "main/utils/runv3/key" + "os" + + "bytes" + "errors" + "io" "github.com/Eyevinn/mp4ff/mp4" - "bytes" - "io" - "errors" //"io/ioutil" - "net/http" "encoding/json" - "github.com/grafov/m3u8" - "strings" - "github.com/schollz/progressbar/v3" + "net/http" "os/exec" + "strings" + + "github.com/grafov/m3u8" + "github.com/schollz/progressbar/v3" ) type PlaybackLicense struct { @@ -32,24 +36,24 @@ type PlaybackLicense struct { Status int `json:"status"` } -// func log() { -// f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) -// if err != nil { -// slog.Error("error opening file: %s", err) -// } -// defer func(f *os.File) { -// err := f.Close() -// if err != nil { -// slog.Error("error closing file: %s", err) -// } -// }(f) -// opts := slog.HandlerOptions{ -// AddSource: true, -// Level: slog.LevelDebug, -// } -// logger := slog.New(slog.NewJSONHandler(os.Stdout, &opts)) -// slog.SetDefault(logger) -//} +// func log() { +// f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) +// if err != nil { +// slog.Error("error opening file: %s", err) +// } +// defer func(f *os.File) { +// err := f.Close() +// if err != nil { +// slog.Error("error closing file: %s", err) +// } +// }(f) +// opts := slog.HandlerOptions{ +// AddSource: true, +// Level: slog.LevelDebug, +// } +// logger := slog.New(slog.NewJSONHandler(os.Stdout, &opts)) +// slog.SetDefault(logger) +// } func getPSSH(contentId string, kidBase64 string) (string, error) { kidBytes, err := base64.StdEncoding.DecodeString(kidBase64) if err != nil { @@ -145,10 +149,10 @@ func GetWebplayback(adamId string, authtoken string, mutoken string, mvmode bool fmt.Println("json err:", err) return "", "", err } - if mvmode { - return obj.List[0].HlsPlaylistUrl, "", nil - } if len(obj.List) > 0 { + if mvmode { + return obj.List[0].HlsPlaylistUrl, "", nil + } // 遍历 Assets for i, _ := range obj.List[0].Assets { 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 } + type Songlist 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"` - URL string `json:"URL"` - }`json:"assets"` - }`json:"songList"` + URL string `json:"URL"` + } `json:"assets"` + } `json:"songList"` Status int `json:"status"` } @@ -202,9 +207,9 @@ func extractKidBase64(b string, mvmode bool) (string, string, error) { kidbase64 = split[1] lastSlashIndex := strings.LastIndex(b, "/") // 截取最后一个斜杠之前的部分 - urlBuilder.WriteString(b[:lastSlashIndex]) - urlBuilder.WriteString("/") - urlBuilder.WriteString(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 { @@ -212,9 +217,9 @@ func extractKidBase64(b string, mvmode bool) (string, string, error) { if segment != nil { //fmt.Println("Extracted URI:", segment.URI) urlBuilder.WriteString(";") - urlBuilder.WriteString(b[:lastSlashIndex]) - urlBuilder.WriteString("/") - urlBuilder.WriteString(segment.URI) + urlBuilder.WriteString(b[:lastSlashIndex]) + urlBuilder.WriteString("/") + urlBuilder.WriteString(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 } -func extsong(b string)(bytes.Buffer){ +func extsong(b string) bytes.Buffer { resp, err := http.Get(b) if err != nil { fmt.Printf("下载文件失败: %v\n", err) @@ -255,8 +260,8 @@ 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, mvmode bool)(string, error) { - var keystr string //for mv key +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 @@ -281,7 +286,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo return "", err } headers := map[string]interface{}{ - "authorization": "Bearer " + authtoken, + "authorization": "Bearer " + authtoken, "x-apple-music-user-token": mutoken, } client, _ := requests.NewClient(nil, requests.ClientOption{ @@ -330,7 +335,7 @@ func Run(adamId string, trackpath string, authtoken string, mutoken string, mvmo return "", nil } -func ExtMvData (keyAndUrls string, savePath string)(error) { +func ExtMvData(keyAndUrls string, savePath string) error { segments := strings.Split(keyAndUrls, ";") key := segments[0] //fmt.Println(key) @@ -345,7 +350,7 @@ func ExtMvData (keyAndUrls string, savePath string)(error) { // 依次下载每个链接并写入文件 bar := progressbar.DefaultBytes( - -1, + -1, "Downloading...", ) barWriter := io.MultiWriter(tempFile, bar) @@ -384,7 +389,6 @@ func ExtMvData (keyAndUrls string, savePath string)(error) { return nil } - // 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 { // Initialization