parent
7f7c452559
commit
a83bfbfd61
|
|
@ -1,7 +1,7 @@
|
||||||
## m3u8d 一款m3u8下载工具
|
## m3u8d 一款m3u8下载工具
|
||||||
* 提供windows图形界面(Qt), mac\linux命令行, linux支持arm和386
|
* 提供windows图形界面(Qt), mac\linux命令行, linux支持arm和386
|
||||||
* 使用ffmpeg转换格式为mp4
|
* 使用ffmpeg转换格式为mp4
|
||||||
* windows自带GUI界面的版本下载: [m3u8d-qt_v1.1_windows_amd64.exe](https://github.com/orestonce/m3u8d/releases/download/v1.1/m3u8d-qt_v1.1_windows_amd64.exe):
|
* windows自带GUI界面的版本下载: [m3u8d-qt_v1.2_windows_amd64.exe](https://github.com/orestonce/m3u8d/releases/download/v1.2/m3u8d-qt_v1.2_windows_amd64.exe):
|
||||||

|

|
||||||
* 全部版本下载, 包括windows图形界面/linux命令行/mac命令行: https://github.com/orestonce/m3u8d/releases
|
* 全部版本下载, 包括windows图形界面/linux命令行/mac命令行: https://github.com/orestonce/m3u8d/releases
|
||||||
|
|
||||||
|
|
|
||||||
236
download.go
236
download.go
|
|
@ -1,14 +1,14 @@
|
||||||
package m3u8d
|
package m3u8d
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"context"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/levigross/grequests"
|
|
||||||
"github.com/orestonce/goffmpeg"
|
"github.com/orestonce/goffmpeg"
|
||||||
"github.com/orestonce/gopool"
|
"github.com/orestonce/gopool"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -43,6 +43,7 @@ func GetProgress() int {
|
||||||
type RunDownload_Resp struct {
|
type RunDownload_Resp struct {
|
||||||
ErrMsg string
|
ErrMsg string
|
||||||
IsSkipped bool
|
IsSkipped bool
|
||||||
|
IsCancel bool
|
||||||
SaveFileTo string
|
SaveFileTo string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +56,15 @@ type RunDownload_Req struct {
|
||||||
SkipTsCountFromHead int `json:",omitempty"` // 跳过前面几个ts
|
SkipTsCountFromHead int `json:",omitempty"` // 跳过前面几个ts
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
type downloadEnv struct {
|
||||||
|
cancelFn func()
|
||||||
|
ctx context.Context
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *downloadEnv) RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
||||||
|
|
||||||
if req.HostType == "" {
|
if req.HostType == "" {
|
||||||
req.HostType = "apiv1"
|
req.HostType = "apiv1"
|
||||||
}
|
}
|
||||||
|
|
@ -73,10 +82,23 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
||||||
if req.SkipTsCountFromHead < 0 {
|
if req.SkipTsCountFromHead < 0 {
|
||||||
req.SkipTsCountFromHead = 0
|
req.SkipTsCountFromHead = 0
|
||||||
}
|
}
|
||||||
var err error
|
host, err := getHost(req.M3u8Url, "apiv2")
|
||||||
req.M3u8Url, err = sniffM3u8(req.M3u8Url)
|
if err != nil {
|
||||||
|
resp.ErrMsg = "getHost0: " + err.Error()
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
this.header = http.Header{
|
||||||
|
"User-Agent": []string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"},
|
||||||
|
"Connection": []string{"keep-alive"},
|
||||||
|
"Accept": []string{"*/*"},
|
||||||
|
"Accept-Encoding": []string{"*"},
|
||||||
|
"Accept-Language": []string{"zh-CN,zh;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5"},
|
||||||
|
"Referer": []string{host},
|
||||||
|
}
|
||||||
|
req.M3u8Url, err = this.sniffM3u8(req.M3u8Url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ErrMsg = "sniffM3u8: " + err.Error()
|
resp.ErrMsg = "sniffM3u8: " + err.Error()
|
||||||
|
resp.IsCancel = this.GetIsCancel()
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
id, err := req.getVideoId()
|
id, err := req.getVideoId()
|
||||||
|
|
@ -103,23 +125,6 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
||||||
resp.ErrMsg = "SetupFfmpeg error: " + err.Error()
|
resp.ErrMsg = "SetupFfmpeg error: " + err.Error()
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
host, err := getHost(req.M3u8Url, "apiv2")
|
|
||||||
if err != nil {
|
|
||||||
resp.ErrMsg = "getHost0: " + err.Error()
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
ro := &grequests.RequestOptions{
|
|
||||||
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
|
|
||||||
RequestTimeout: 10 * time.Second, //请求头超时时间
|
|
||||||
Headers: map[string]string{
|
|
||||||
"Connection": "keep-alive",
|
|
||||||
"Accept": "*/*",
|
|
||||||
"Accept-Encoding": "*",
|
|
||||||
"Accept-Language": "zh-CN,zh;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ro.Headers["Referer"] = host
|
|
||||||
ro.InsecureSkipVerify = req.Insecure
|
|
||||||
if !strings.HasPrefix(req.M3u8Url, "http") || req.M3u8Url == "" {
|
if !strings.HasPrefix(req.M3u8Url, "http") || req.M3u8Url == "" {
|
||||||
resp.ErrMsg = "M3u8Url not valid " + strconv.Quote(req.M3u8Url)
|
resp.ErrMsg = "M3u8Url not valid " + strconv.Quote(req.M3u8Url)
|
||||||
return resp
|
return resp
|
||||||
|
|
@ -135,35 +140,38 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
||||||
m3u8Host, err := getHost(req.M3u8Url, req.HostType)
|
m3u8Host, err := getHost(req.M3u8Url, req.HostType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ErrMsg = "getHost1: " + err.Error()
|
resp.ErrMsg = "getHost1: " + err.Error()
|
||||||
|
resp.IsCancel = this.GetIsCancel()
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
// 获取m3u8地址的内容体
|
// 获取m3u8地址的内容体
|
||||||
r, err := grequests.Get(req.M3u8Url, ro)
|
m3u8Body, err := this.doGetRequest(req.M3u8Url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ErrMsg = "getM3u8Body: " + err.Error()
|
resp.ErrMsg = "getM3u8Body: " + err.Error()
|
||||||
|
resp.IsCancel = this.GetIsCancel()
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
m3u8Body := r.String()
|
ts_key, err := this.getM3u8Key(m3u8Host, string(m3u8Body))
|
||||||
ts_key, err := getM3u8Key(ro, m3u8Host, m3u8Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ErrMsg = "getM3u8Key: " + err.Error()
|
resp.ErrMsg = "getM3u8Key: " + err.Error()
|
||||||
|
resp.IsCancel = this.GetIsCancel()
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
ts_list := getTsList(m3u8Host, m3u8Body)
|
tsList := getTsList(m3u8Host, string(m3u8Body))
|
||||||
if len(ts_list) <= req.SkipTsCountFromHead {
|
if len(tsList) <= req.SkipTsCountFromHead {
|
||||||
resp.ErrMsg = "需要下载的文件为空"
|
resp.ErrMsg = "需要下载的文件为空"
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
ts_list = ts_list[req.SkipTsCountFromHead:]
|
tsList = tsList[req.SkipTsCountFromHead:]
|
||||||
// 下载ts
|
// 下载ts
|
||||||
err = downloader(ro, ts_list, downloadDir, ts_key)
|
err = this.downloader(tsList, downloadDir, ts_key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ErrMsg = "下载ts文件错误: " + err.Error()
|
resp.ErrMsg = "下载ts文件错误: " + err.Error()
|
||||||
|
resp.IsCancel = this.GetIsCancel()
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
DrawProgressBar(1)
|
DrawProgressBar(1)
|
||||||
var tsFileList []string
|
var tsFileList []string
|
||||||
for _, one := range ts_list {
|
for _, one := range tsList {
|
||||||
tsFileList = append(tsFileList, filepath.Join(downloadDir, one.Name))
|
tsFileList = append(tsFileList, filepath.Join(downloadDir, one.Name))
|
||||||
}
|
}
|
||||||
var tmpOutputName string
|
var tmpOutputName string
|
||||||
|
|
@ -223,6 +231,40 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gOldEnv *downloadEnv
|
||||||
|
var gOldEnvLocker sync.Mutex
|
||||||
|
|
||||||
|
func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
||||||
|
env := &downloadEnv{
|
||||||
|
client: &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: req.Insecure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
env.ctx, env.cancelFn = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
gOldEnvLocker.Lock()
|
||||||
|
if gOldEnv != nil {
|
||||||
|
gOldEnv.cancelFn()
|
||||||
|
}
|
||||||
|
gOldEnv = env
|
||||||
|
gOldEnvLocker.Unlock()
|
||||||
|
return env.RunDownload(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseOldEnv() {
|
||||||
|
gOldEnvLocker.Lock()
|
||||||
|
defer gOldEnvLocker.Unlock()
|
||||||
|
if gOldEnv != nil {
|
||||||
|
gOldEnv.cancelFn()
|
||||||
|
}
|
||||||
|
gOldEnv = nil
|
||||||
|
}
|
||||||
|
|
||||||
// 获取m3u8地址的host
|
// 获取m3u8地址的host
|
||||||
func getHost(Url, ht string) (host string, err error) {
|
func getHost(Url, ht string) (host string, err error) {
|
||||||
u, err := url.Parse(Url)
|
u, err := url.Parse(Url)
|
||||||
|
|
@ -245,7 +287,7 @@ func GetWd() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取m3u8加密的密钥
|
// 获取m3u8加密的密钥
|
||||||
func getM3u8Key(ro *grequests.RequestOptions, host, html string) (key string, err error) {
|
func (this *downloadEnv) getM3u8Key(host, html string) (key string, err error) {
|
||||||
lines := strings.Split(html, "\n")
|
lines := strings.Split(html, "\n")
|
||||||
key = ""
|
key = ""
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
|
|
@ -256,16 +298,14 @@ func getM3u8Key(ro *grequests.RequestOptions, host, html string) (key string, er
|
||||||
if !strings.Contains(line, "http") {
|
if !strings.Contains(line, "http") {
|
||||||
key_url = fmt.Sprintf("%s/%s", host, key_url)
|
key_url = fmt.Sprintf("%s/%s", host, key_url)
|
||||||
}
|
}
|
||||||
res, err := grequests.Get(key_url, ro)
|
res, err := this.doGetRequest(key_url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if res.StatusCode == 200 {
|
return string(res), nil
|
||||||
key = res.String()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return key, nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTsList(host, body string) (tsList []TsInfo) {
|
func getTsList(host, body string) (tsList []TsInfo) {
|
||||||
|
|
@ -300,33 +340,18 @@ func getTsList(host, body string) (tsList []TsInfo) {
|
||||||
|
|
||||||
// 下载ts文件
|
// 下载ts文件
|
||||||
// @modify: 2020-08-13 修复ts格式SyncByte合并不能播放问题
|
// @modify: 2020-08-13 修复ts格式SyncByte合并不能播放问题
|
||||||
func downloadTsFile(ro *grequests.RequestOptions, ts TsInfo, download_dir, key string) (err error) {
|
func (this *downloadEnv) downloadTsFile(ts TsInfo, download_dir, key string) (err error) {
|
||||||
currPath := fmt.Sprintf("%s/%s", download_dir, ts.Name)
|
currPath := fmt.Sprintf("%s/%s", download_dir, ts.Name)
|
||||||
if isFileExists(currPath) {
|
if isFileExists(currPath) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
res, err := grequests.Get(ts.Url, ro)
|
data, err := this.doGetRequest(ts.Url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !res.Ok {
|
|
||||||
return errors.New("!res.Ok")
|
|
||||||
}
|
|
||||||
// 校验长度是否合法
|
// 校验长度是否合法
|
||||||
var origData []byte
|
var origData []byte
|
||||||
origData = res.Bytes()
|
origData = data
|
||||||
contentLen := 0
|
|
||||||
contentLenStr := res.Header.Get("Content-Length")
|
|
||||||
if contentLenStr != "" {
|
|
||||||
contentLen, _ = strconv.Atoi(contentLenStr)
|
|
||||||
}
|
|
||||||
if len(origData) == 0 || (contentLen > 0 && len(origData) < contentLen) || res.Error != nil {
|
|
||||||
msg := ""
|
|
||||||
if res.Error != nil {
|
|
||||||
msg = res.Error.Error()
|
|
||||||
}
|
|
||||||
return errors.New("[warn] File: " + ts.Name + "res origData invalid or err:" + msg)
|
|
||||||
}
|
|
||||||
// 解密出视频 ts 源文件
|
// 解密出视频 ts 源文件
|
||||||
if key != "" {
|
if key != "" {
|
||||||
//解密 ts 文件,算法:aes 128 cbc pack5
|
//解密 ts 文件,算法:aes 128 cbc pack5
|
||||||
|
|
@ -354,7 +379,14 @@ func downloadTsFile(ro *grequests.RequestOptions, ts TsInfo, download_dir, key s
|
||||||
return os.Rename(tmpPath, currPath)
|
return os.Rename(tmpPath, currPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloader(ro *grequests.RequestOptions, tsList []TsInfo, downloadDir string, key string) (err error) {
|
func (this *downloadEnv) SleepDur(d time.Duration) {
|
||||||
|
select {
|
||||||
|
case <-time.After(d):
|
||||||
|
case <-this.ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *downloadEnv) downloader(tsList []TsInfo, downloadDir string, key string) (err error) {
|
||||||
task := gopool.NewThreadPool(8)
|
task := gopool.NewThreadPool(8)
|
||||||
tsLen := len(tsList)
|
tsLen := len(tsList)
|
||||||
downloadCount := 0
|
downloadCount := 0
|
||||||
|
|
@ -371,10 +403,16 @@ func downloader(ro *grequests.RequestOptions, tsList []TsInfo, downloadDir strin
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
locker.Unlock()
|
locker.Unlock()
|
||||||
lastErr = downloadTsFile(ro, ts, downloadDir, key)
|
if i > 0 {
|
||||||
|
this.SleepDur(time.Second * time.Duration(i))
|
||||||
|
}
|
||||||
|
lastErr = this.downloadTsFile(ts, downloadDir, key)
|
||||||
if lastErr == nil {
|
if lastErr == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if this.GetIsCancel() {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if lastErr != nil {
|
if lastErr != nil {
|
||||||
locker.Lock()
|
locker.Lock()
|
||||||
|
|
@ -428,64 +466,21 @@ func isDirExists(path string) bool {
|
||||||
return err == nil && stat.IsDir()
|
return err == nil && stat.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断文件是否存在
|
|
||||||
func pathExists(path string) bool {
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================== 加解密相关 ==============================
|
// ============================== 加解密相关 ==============================
|
||||||
|
|
||||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
|
||||||
padding := blockSize - len(ciphertext)%blockSize
|
|
||||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
|
||||||
return append(ciphertext, padtext...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PKCS7UnPadding(origData []byte) []byte {
|
func PKCS7UnPadding(origData []byte) []byte {
|
||||||
length := len(origData)
|
length := len(origData)
|
||||||
unpadding := int(origData[length-1])
|
unpadding := int(origData[length-1])
|
||||||
return origData[:(length - unpadding)]
|
return origData[:(length - unpadding)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func AesEncrypt(origData, key []byte, ivs ...[]byte) ([]byte, error) {
|
func AesDecrypt(crypted, key []byte) ([]byte, error) {
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
blockSize := block.BlockSize()
|
blockSize := block.BlockSize()
|
||||||
var iv []byte
|
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||||
if len(ivs) == 0 {
|
|
||||||
iv = key
|
|
||||||
} else {
|
|
||||||
iv = ivs[0]
|
|
||||||
}
|
|
||||||
origData = PKCS7Padding(origData, blockSize)
|
|
||||||
blockMode := cipher.NewCBCEncrypter(block, iv[:blockSize])
|
|
||||||
crypted := make([]byte, len(origData))
|
|
||||||
blockMode.CryptBlocks(crypted, origData)
|
|
||||||
return crypted, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AesDecrypt(crypted, key []byte, ivs ...[]byte) ([]byte, error) {
|
|
||||||
block, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
var iv []byte
|
|
||||||
if len(ivs) == 0 {
|
|
||||||
iv = key
|
|
||||||
} else {
|
|
||||||
iv = ivs[0]
|
|
||||||
}
|
|
||||||
blockMode := cipher.NewCBCDecrypter(block, iv[:blockSize])
|
|
||||||
origData := make([]byte, len(crypted))
|
origData := make([]byte, len(crypted))
|
||||||
blockMode.CryptBlocks(origData, crypted)
|
blockMode.CryptBlocks(origData, crypted)
|
||||||
origData = PKCS7UnPadding(origData)
|
origData = PKCS7UnPadding(origData)
|
||||||
|
|
@ -508,17 +503,11 @@ func getFileSha256(targetFile string) (v string) {
|
||||||
return hex.EncodeToString(tmp[:])
|
return hex.EncodeToString(tmp[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func sniffM3u8(urlS string) (afterUrl string, err error) {
|
func (this *downloadEnv) sniffM3u8(urlS string) (afterUrl string, err error) {
|
||||||
if strings.HasSuffix(strings.ToLower(urlS), ".m3u8") {
|
if strings.HasSuffix(strings.ToLower(urlS), ".m3u8") {
|
||||||
return urlS, nil
|
return urlS, nil
|
||||||
}
|
}
|
||||||
resp, err := http.DefaultClient.Get(urlS)
|
content, err := this.doGetRequest(urlS)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
content, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -528,3 +517,32 @@ func sniffM3u8(urlS string) (afterUrl string, err error) {
|
||||||
}
|
}
|
||||||
return string(groups[0]), nil
|
return string(groups[0]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *downloadEnv) doGetRequest(urlS string) (data []byte, err error) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, urlS, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req = req.WithContext(this.ctx)
|
||||||
|
req.Header = this.header
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *downloadEnv) GetIsCancel() bool {
|
||||||
|
select {
|
||||||
|
case <-this.ctx.Done():
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func BuildCliBinary() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, cfg := range list {
|
for _, cfg := range list {
|
||||||
name := "m3u8d_cli_v1.1_" + cfg.GOOS + "_" + cfg.GOARCH + cfg.Ext
|
name := "m3u8d_cli_v1.2_" + cfg.GOOS + "_" + cfg.GOARCH + cfg.Ext
|
||||||
cmd := exec.Command("go", "build", "-o", filepath.Join(wd, "bin", name))
|
cmd := exec.Command("go", "build", "-o", filepath.Join(wd, "bin", name))
|
||||||
cmd.Dir = filepath.Join(wd, "cmd")
|
cmd.Dir = filepath.Join(wd, "cmd")
|
||||||
cmd.Env = append(os.Environ(), "GOOS="+cfg.GOOS)
|
cmd.Env = append(os.Environ(), "GOOS="+cfg.GOOS)
|
||||||
|
|
@ -62,6 +62,7 @@ func CreateLibForQtUi() {
|
||||||
EnableQtClass_Toast: true,
|
EnableQtClass_Toast: true,
|
||||||
})
|
})
|
||||||
ctx.Generate1(m3u8d.RunDownload)
|
ctx.Generate1(m3u8d.RunDownload)
|
||||||
|
ctx.Generate1(m3u8d.CloseOldEnv)
|
||||||
ctx.Generate1(m3u8d.GetProgress)
|
ctx.Generate1(m3u8d.GetProgress)
|
||||||
ctx.Generate1(m3u8d.GetWd)
|
ctx.Generate1(m3u8d.GetWd)
|
||||||
ctx.MustCreateAmd64LibraryInDir("m3u8d-qt")
|
ctx.MustCreateAmd64LibraryInDir("m3u8d-qt")
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -70,6 +70,7 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern __declspec(dllexport) void Go2cppFn_RunDownload(char* in, int inLen, char** out, int* outLen);
|
extern __declspec(dllexport) void Go2cppFn_RunDownload(char* in, int inLen, char** out, int* outLen);
|
||||||
|
extern __declspec(dllexport) void Go2cppFn_CloseOldEnv(char* in, int inLen, char** out, int* outLen);
|
||||||
extern __declspec(dllexport) void Go2cppFn_GetProgress(char* in, int inLen, char** out, int* outLen);
|
extern __declspec(dllexport) void Go2cppFn_GetProgress(char* in, int inLen, char** out, int* outLen);
|
||||||
extern __declspec(dllexport) void Go2cppFn_GetWd(char* in, int inLen, char** out, int* outLen);
|
extern __declspec(dllexport) void Go2cppFn_GetWd(char* in, int inLen, char** out, int* outLen);
|
||||||
|
|
||||||
|
|
@ -82,53 +83,53 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){
|
||||||
std::string in;
|
std::string in;
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
uint32_t tmp18 = in0.M3u8Url.length();
|
uint32_t tmp19 = in0.M3u8Url.length();
|
||||||
char tmp19[4];
|
char tmp20[4];
|
||||||
tmp19[0] = (uint32_t(tmp18) >> 24) & 0xFF;
|
tmp20[0] = (uint32_t(tmp19) >> 24) & 0xFF;
|
||||||
tmp19[1] = (uint32_t(tmp18) >> 16) & 0xFF;
|
tmp20[1] = (uint32_t(tmp19) >> 16) & 0xFF;
|
||||||
tmp19[2] = (uint32_t(tmp18) >> 8) & 0xFF;
|
tmp20[2] = (uint32_t(tmp19) >> 8) & 0xFF;
|
||||||
tmp19[3] = (uint32_t(tmp18) >> 0) & 0xFF;
|
tmp20[3] = (uint32_t(tmp19) >> 0) & 0xFF;
|
||||||
in.append(tmp19, 4);
|
in.append(tmp20, 4);
|
||||||
in.append(in0.M3u8Url);
|
in.append(in0.M3u8Url);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
uint32_t tmp20 = in0.HostType.length();
|
uint32_t tmp21 = in0.HostType.length();
|
||||||
char tmp21[4];
|
char tmp22[4];
|
||||||
tmp21[0] = (uint32_t(tmp20) >> 24) & 0xFF;
|
tmp22[0] = (uint32_t(tmp21) >> 24) & 0xFF;
|
||||||
tmp21[1] = (uint32_t(tmp20) >> 16) & 0xFF;
|
tmp22[1] = (uint32_t(tmp21) >> 16) & 0xFF;
|
||||||
tmp21[2] = (uint32_t(tmp20) >> 8) & 0xFF;
|
tmp22[2] = (uint32_t(tmp21) >> 8) & 0xFF;
|
||||||
tmp21[3] = (uint32_t(tmp20) >> 0) & 0xFF;
|
tmp22[3] = (uint32_t(tmp21) >> 0) & 0xFF;
|
||||||
in.append(tmp21, 4);
|
in.append(tmp22, 4);
|
||||||
in.append(in0.HostType);
|
in.append(in0.HostType);
|
||||||
}
|
}
|
||||||
in.append((char*)(&in0.Insecure), 1);
|
in.append((char*)(&in0.Insecure), 1);
|
||||||
{
|
{
|
||||||
uint32_t tmp22 = in0.SaveDir.length();
|
uint32_t tmp23 = in0.SaveDir.length();
|
||||||
char tmp23[4];
|
char tmp24[4];
|
||||||
tmp23[0] = (uint32_t(tmp22) >> 24) & 0xFF;
|
tmp24[0] = (uint32_t(tmp23) >> 24) & 0xFF;
|
||||||
tmp23[1] = (uint32_t(tmp22) >> 16) & 0xFF;
|
tmp24[1] = (uint32_t(tmp23) >> 16) & 0xFF;
|
||||||
tmp23[2] = (uint32_t(tmp22) >> 8) & 0xFF;
|
tmp24[2] = (uint32_t(tmp23) >> 8) & 0xFF;
|
||||||
tmp23[3] = (uint32_t(tmp22) >> 0) & 0xFF;
|
tmp24[3] = (uint32_t(tmp23) >> 0) & 0xFF;
|
||||||
in.append(tmp23, 4);
|
in.append(tmp24, 4);
|
||||||
in.append(in0.SaveDir);
|
in.append(in0.SaveDir);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
uint32_t tmp24 = in0.FileName.length();
|
uint32_t tmp25 = in0.FileName.length();
|
||||||
char tmp25[4];
|
char tmp26[4];
|
||||||
tmp25[0] = (uint32_t(tmp24) >> 24) & 0xFF;
|
tmp26[0] = (uint32_t(tmp25) >> 24) & 0xFF;
|
||||||
tmp25[1] = (uint32_t(tmp24) >> 16) & 0xFF;
|
tmp26[1] = (uint32_t(tmp25) >> 16) & 0xFF;
|
||||||
tmp25[2] = (uint32_t(tmp24) >> 8) & 0xFF;
|
tmp26[2] = (uint32_t(tmp25) >> 8) & 0xFF;
|
||||||
tmp25[3] = (uint32_t(tmp24) >> 0) & 0xFF;
|
tmp26[3] = (uint32_t(tmp25) >> 0) & 0xFF;
|
||||||
in.append(tmp25, 4);
|
in.append(tmp26, 4);
|
||||||
in.append(in0.FileName);
|
in.append(in0.FileName);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
char tmp26[4];
|
char tmp27[4];
|
||||||
tmp26[0] = (uint32_t(in0.SkipTsCountFromHead) >> 24) & 0xFF;
|
tmp27[0] = (uint32_t(in0.SkipTsCountFromHead) >> 24) & 0xFF;
|
||||||
tmp26[1] = (uint32_t(in0.SkipTsCountFromHead) >> 16) & 0xFF;
|
tmp27[1] = (uint32_t(in0.SkipTsCountFromHead) >> 16) & 0xFF;
|
||||||
tmp26[2] = (uint32_t(in0.SkipTsCountFromHead) >> 8) & 0xFF;
|
tmp27[2] = (uint32_t(in0.SkipTsCountFromHead) >> 8) & 0xFF;
|
||||||
tmp26[3] = (uint32_t(in0.SkipTsCountFromHead) >> 0) & 0xFF;
|
tmp27[3] = (uint32_t(in0.SkipTsCountFromHead) >> 0) & 0xFF;
|
||||||
in.append(tmp26, 4);
|
in.append(tmp27, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
char *out = NULL;
|
char *out = NULL;
|
||||||
|
|
@ -138,28 +139,30 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){
|
||||||
int outIdx = 0;
|
int outIdx = 0;
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
uint32_t tmp27 = 0;
|
uint32_t tmp28 = 0;
|
||||||
uint32_t tmp28 = uint32_t(uint8_t(out[outIdx+0]) << 24);
|
uint32_t tmp29 = uint32_t(uint8_t(out[outIdx+0]) << 24);
|
||||||
uint32_t tmp29 = uint32_t(uint8_t(out[outIdx+1]) << 16);
|
uint32_t tmp30 = uint32_t(uint8_t(out[outIdx+1]) << 16);
|
||||||
uint32_t tmp30 = uint32_t(uint8_t(out[outIdx+2]) << 8);
|
uint32_t tmp31 = uint32_t(uint8_t(out[outIdx+2]) << 8);
|
||||||
uint32_t tmp31 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
uint32_t tmp32 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
||||||
tmp27 = tmp28 | tmp29 | tmp30 | tmp31;
|
tmp28 = tmp29 | tmp30 | tmp31 | tmp32;
|
||||||
outIdx+=4;
|
outIdx+=4;
|
||||||
retValue.ErrMsg = std::string(out+outIdx, out+outIdx+tmp27);
|
retValue.ErrMsg = std::string(out+outIdx, out+outIdx+tmp28);
|
||||||
outIdx+=tmp27;
|
outIdx+=tmp28;
|
||||||
}
|
}
|
||||||
retValue.IsSkipped = (bool) out[outIdx];
|
retValue.IsSkipped = (bool) out[outIdx];
|
||||||
outIdx++;
|
outIdx++;
|
||||||
|
retValue.IsCancel = (bool) out[outIdx];
|
||||||
|
outIdx++;
|
||||||
{
|
{
|
||||||
uint32_t tmp32 = 0;
|
uint32_t tmp33 = 0;
|
||||||
uint32_t tmp33 = uint32_t(uint8_t(out[outIdx+0]) << 24);
|
uint32_t tmp34 = uint32_t(uint8_t(out[outIdx+0]) << 24);
|
||||||
uint32_t tmp34 = uint32_t(uint8_t(out[outIdx+1]) << 16);
|
uint32_t tmp35 = uint32_t(uint8_t(out[outIdx+1]) << 16);
|
||||||
uint32_t tmp35 = uint32_t(uint8_t(out[outIdx+2]) << 8);
|
uint32_t tmp36 = uint32_t(uint8_t(out[outIdx+2]) << 8);
|
||||||
uint32_t tmp36 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
uint32_t tmp37 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
||||||
tmp32 = tmp33 | tmp34 | tmp35 | tmp36;
|
tmp33 = tmp34 | tmp35 | tmp36 | tmp37;
|
||||||
outIdx+=4;
|
outIdx+=4;
|
||||||
retValue.SaveFileTo = std::string(out+outIdx, out+outIdx+tmp32);
|
retValue.SaveFileTo = std::string(out+outIdx, out+outIdx+tmp33);
|
||||||
outIdx+=tmp32;
|
outIdx+=tmp33;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (out != NULL) {
|
if (out != NULL) {
|
||||||
|
|
@ -168,6 +171,16 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){
|
||||||
return retValue;
|
return retValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CloseOldEnv(){
|
||||||
|
std::string in;
|
||||||
|
char *out = NULL;
|
||||||
|
int outLen = 0;
|
||||||
|
Go2cppFn_CloseOldEnv((char *)in.data(), in.length(), &out, &outLen);
|
||||||
|
if (out != NULL) {
|
||||||
|
free(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int32_t GetProgress(){
|
int32_t GetProgress(){
|
||||||
std::string in;
|
std::string in;
|
||||||
char *out = NULL;
|
char *out = NULL;
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,11 @@ struct RunDownload_Req{
|
||||||
struct RunDownload_Resp{
|
struct RunDownload_Resp{
|
||||||
std::string ErrMsg;
|
std::string ErrMsg;
|
||||||
bool IsSkipped;
|
bool IsSkipped;
|
||||||
|
bool IsCancel;
|
||||||
std::string SaveFileTo;
|
std::string SaveFileTo;
|
||||||
};
|
};
|
||||||
RunDownload_Resp RunDownload(RunDownload_Req in0);
|
RunDownload_Resp RunDownload(RunDownload_Req in0);
|
||||||
|
void CloseOldEnv();
|
||||||
int32_t GetProgress();
|
int32_t GetProgress();
|
||||||
std::string GetWd();
|
std::string GetWd();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
{
|
{
|
||||||
|
CloseOldEnv();
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +36,8 @@ void MainWindow::on_pushButton_RunDownload_clicked()
|
||||||
ui->checkBox_Insecure->setEnabled(false);
|
ui->checkBox_Insecure->setEnabled(false);
|
||||||
ui->progressBar->setValue(0);
|
ui->progressBar->setValue(0);
|
||||||
ui->pushButton_RunDownload->setText("正在下载...");
|
ui->pushButton_RunDownload->setText("正在下载...");
|
||||||
|
ui->pushButton_StopDownload->setEnabled(true);
|
||||||
|
|
||||||
m_syncUi.AddRunFnOn_OtherThread([this](){
|
m_syncUi.AddRunFnOn_OtherThread([this](){
|
||||||
// isFinished被 other thread 和 ui thread共享
|
// isFinished被 other thread 和 ui thread共享
|
||||||
std::shared_ptr<std::atomic_bool> isFinished = std::make_shared<std::atomic_bool>(false);
|
std::shared_ptr<std::atomic_bool> isFinished = std::make_shared<std::atomic_bool>(false);
|
||||||
|
|
@ -72,6 +75,10 @@ void MainWindow::on_pushButton_RunDownload_clicked()
|
||||||
ui->pushButton_RunDownload->setEnabled(true);
|
ui->pushButton_RunDownload->setEnabled(true);
|
||||||
ui->checkBox_Insecure->setEnabled(false);
|
ui->checkBox_Insecure->setEnabled(false);
|
||||||
ui->pushButton_RunDownload->setText("开始下载");
|
ui->pushButton_RunDownload->setText("开始下载");
|
||||||
|
ui->pushButton_StopDownload->setEnabled(false);
|
||||||
|
if (resp.IsCancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!resp.ErrMsg.empty()) {
|
if (!resp.ErrMsg.empty()) {
|
||||||
Toast::Instance()->SetError(QString::fromStdString(resp.ErrMsg));
|
Toast::Instance()->SetError(QString::fromStdString(resp.ErrMsg));
|
||||||
|
|
@ -91,3 +98,8 @@ void MainWindow::on_pushButton_SaveDir_clicked()
|
||||||
QString dir = QFileDialog::getExistingDirectory(this);
|
QString dir = QFileDialog::getExistingDirectory(this);
|
||||||
ui->lineEdit_SaveDir->setText(dir);
|
ui->lineEdit_SaveDir->setText(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::on_pushButton_StopDownload_clicked()
|
||||||
|
{
|
||||||
|
CloseOldEnv();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ private slots:
|
||||||
|
|
||||||
void on_pushButton_SaveDir_clicked();
|
void on_pushButton_SaveDir_clicked();
|
||||||
|
|
||||||
|
void on_pushButton_StopDownload_clicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
RunOnUiThread m_syncUi;
|
RunOnUiThread m_syncUi;
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0">
|
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0,0">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
@ -134,6 +134,16 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_StopDownload">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>停止下载</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
||||||
41
merge.go
41
merge.go
|
|
@ -1,41 +0,0 @@
|
||||||
package m3u8d
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mergeTsFileList_Raw(tsFileList []string, outputTs string) (hash string, err error) {
|
|
||||||
fout, err := os.Create(outputTs)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
hashWriter := sha256.New()
|
|
||||||
multiW := io.MultiWriter(fout, hashWriter)
|
|
||||||
|
|
||||||
for _, tsName := range tsFileList {
|
|
||||||
var fin *os.File
|
|
||||||
fin, err = os.Open(tsName)
|
|
||||||
if err != nil {
|
|
||||||
fout.Close()
|
|
||||||
_ = os.Remove(outputTs)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(multiW, fin)
|
|
||||||
if err != nil {
|
|
||||||
fin.Close()
|
|
||||||
fout.Close()
|
|
||||||
_ = os.Remove(outputTs)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
fin.Close()
|
|
||||||
}
|
|
||||||
err = fout.Close()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
tmp := hashWriter.Sum(nil)
|
|
||||||
return hex.EncodeToString(tmp[:]), nil
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue