parent
7f7c452559
commit
a83bfbfd61
|
|
@ -1,7 +1,7 @@
|
|||
## m3u8d 一款m3u8下载工具
|
||||
* 提供windows图形界面(Qt), mac\linux命令行, linux支持arm和386
|
||||
* 使用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
|
||||
|
||||
|
|
|
|||
236
download.go
236
download.go
|
|
@ -1,14 +1,14 @@
|
|||
package m3u8d
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/levigross/grequests"
|
||||
"github.com/orestonce/goffmpeg"
|
||||
"github.com/orestonce/gopool"
|
||||
"io"
|
||||
|
|
@ -43,6 +43,7 @@ func GetProgress() int {
|
|||
type RunDownload_Resp struct {
|
||||
ErrMsg string
|
||||
IsSkipped bool
|
||||
IsCancel bool
|
||||
SaveFileTo string
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +56,15 @@ type RunDownload_Req struct {
|
|||
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 == "" {
|
||||
req.HostType = "apiv1"
|
||||
}
|
||||
|
|
@ -73,10 +82,23 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
|||
if req.SkipTsCountFromHead < 0 {
|
||||
req.SkipTsCountFromHead = 0
|
||||
}
|
||||
var err error
|
||||
req.M3u8Url, err = sniffM3u8(req.M3u8Url)
|
||||
host, err := getHost(req.M3u8Url, "apiv2")
|
||||
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 {
|
||||
resp.ErrMsg = "sniffM3u8: " + err.Error()
|
||||
resp.IsCancel = this.GetIsCancel()
|
||||
return resp
|
||||
}
|
||||
id, err := req.getVideoId()
|
||||
|
|
@ -103,23 +125,6 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
|||
resp.ErrMsg = "SetupFfmpeg error: " + err.Error()
|
||||
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 == "" {
|
||||
resp.ErrMsg = "M3u8Url not valid " + strconv.Quote(req.M3u8Url)
|
||||
return resp
|
||||
|
|
@ -135,35 +140,38 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
|||
m3u8Host, err := getHost(req.M3u8Url, req.HostType)
|
||||
if err != nil {
|
||||
resp.ErrMsg = "getHost1: " + err.Error()
|
||||
resp.IsCancel = this.GetIsCancel()
|
||||
return resp
|
||||
}
|
||||
// 获取m3u8地址的内容体
|
||||
r, err := grequests.Get(req.M3u8Url, ro)
|
||||
m3u8Body, err := this.doGetRequest(req.M3u8Url)
|
||||
if err != nil {
|
||||
resp.ErrMsg = "getM3u8Body: " + err.Error()
|
||||
resp.IsCancel = this.GetIsCancel()
|
||||
return resp
|
||||
}
|
||||
m3u8Body := r.String()
|
||||
ts_key, err := getM3u8Key(ro, m3u8Host, m3u8Body)
|
||||
ts_key, err := this.getM3u8Key(m3u8Host, string(m3u8Body))
|
||||
if err != nil {
|
||||
resp.ErrMsg = "getM3u8Key: " + err.Error()
|
||||
resp.IsCancel = this.GetIsCancel()
|
||||
return resp
|
||||
}
|
||||
ts_list := getTsList(m3u8Host, m3u8Body)
|
||||
if len(ts_list) <= req.SkipTsCountFromHead {
|
||||
tsList := getTsList(m3u8Host, string(m3u8Body))
|
||||
if len(tsList) <= req.SkipTsCountFromHead {
|
||||
resp.ErrMsg = "需要下载的文件为空"
|
||||
return resp
|
||||
}
|
||||
ts_list = ts_list[req.SkipTsCountFromHead:]
|
||||
tsList = tsList[req.SkipTsCountFromHead:]
|
||||
// 下载ts
|
||||
err = downloader(ro, ts_list, downloadDir, ts_key)
|
||||
err = this.downloader(tsList, downloadDir, ts_key)
|
||||
if err != nil {
|
||||
resp.ErrMsg = "下载ts文件错误: " + err.Error()
|
||||
resp.IsCancel = this.GetIsCancel()
|
||||
return resp
|
||||
}
|
||||
DrawProgressBar(1)
|
||||
var tsFileList []string
|
||||
for _, one := range ts_list {
|
||||
for _, one := range tsList {
|
||||
tsFileList = append(tsFileList, filepath.Join(downloadDir, one.Name))
|
||||
}
|
||||
var tmpOutputName string
|
||||
|
|
@ -223,6 +231,40 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_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
|
||||
func getHost(Url, ht string) (host string, err error) {
|
||||
u, err := url.Parse(Url)
|
||||
|
|
@ -245,7 +287,7 @@ func GetWd() string {
|
|||
}
|
||||
|
||||
// 获取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")
|
||||
key = ""
|
||||
for _, line := range lines {
|
||||
|
|
@ -256,16 +298,14 @@ func getM3u8Key(ro *grequests.RequestOptions, host, html string) (key string, er
|
|||
if !strings.Contains(line, "http") {
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
if res.StatusCode == 200 {
|
||||
key = res.String()
|
||||
}
|
||||
return string(res), nil
|
||||
}
|
||||
}
|
||||
return key, nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func getTsList(host, body string) (tsList []TsInfo) {
|
||||
|
|
@ -300,33 +340,18 @@ func getTsList(host, body string) (tsList []TsInfo) {
|
|||
|
||||
// 下载ts文件
|
||||
// @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)
|
||||
if isFileExists(currPath) {
|
||||
return nil
|
||||
}
|
||||
res, err := grequests.Get(ts.Url, ro)
|
||||
data, err := this.doGetRequest(ts.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !res.Ok {
|
||||
return errors.New("!res.Ok")
|
||||
}
|
||||
// 校验长度是否合法
|
||||
var origData []byte
|
||||
origData = res.Bytes()
|
||||
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)
|
||||
}
|
||||
origData = data
|
||||
// 解密出视频 ts 源文件
|
||||
if key != "" {
|
||||
//解密 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)
|
||||
}
|
||||
|
||||
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)
|
||||
tsLen := len(tsList)
|
||||
downloadCount := 0
|
||||
|
|
@ -371,10 +403,16 @@ func downloader(ro *grequests.RequestOptions, tsList []TsInfo, downloadDir strin
|
|||
return
|
||||
}
|
||||
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 {
|
||||
break
|
||||
}
|
||||
if this.GetIsCancel() {
|
||||
break
|
||||
}
|
||||
}
|
||||
if lastErr != nil {
|
||||
locker.Lock()
|
||||
|
|
@ -428,64 +466,21 @@ func isDirExists(path string) bool {
|
|||
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 {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
var iv []byte
|
||||
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])
|
||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||
origData := make([]byte, len(crypted))
|
||||
blockMode.CryptBlocks(origData, crypted)
|
||||
origData = PKCS7UnPadding(origData)
|
||||
|
|
@ -508,17 +503,11 @@ func getFileSha256(targetFile string) (v string) {
|
|||
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") {
|
||||
return urlS, nil
|
||||
}
|
||||
resp, err := http.DefaultClient.Get(urlS)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
content, err := this.doGetRequest(urlS)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -528,3 +517,32 @@ func sniffM3u8(urlS string) (afterUrl string, err error) {
|
|||
}
|
||||
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 {
|
||||
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.Dir = filepath.Join(wd, "cmd")
|
||||
cmd.Env = append(os.Environ(), "GOOS="+cfg.GOOS)
|
||||
|
|
@ -62,6 +62,7 @@ func CreateLibForQtUi() {
|
|||
EnableQtClass_Toast: true,
|
||||
})
|
||||
ctx.Generate1(m3u8d.RunDownload)
|
||||
ctx.Generate1(m3u8d.CloseOldEnv)
|
||||
ctx.Generate1(m3u8d.GetProgress)
|
||||
ctx.Generate1(m3u8d.GetWd)
|
||||
ctx.MustCreateAmd64LibraryInDir("m3u8d-qt")
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -70,6 +70,7 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
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_GetWd(char* in, int inLen, char** out, int* outLen);
|
||||
|
||||
|
|
@ -82,53 +83,53 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){
|
|||
std::string in;
|
||||
{
|
||||
{
|
||||
uint32_t tmp18 = in0.M3u8Url.length();
|
||||
char tmp19[4];
|
||||
tmp19[0] = (uint32_t(tmp18) >> 24) & 0xFF;
|
||||
tmp19[1] = (uint32_t(tmp18) >> 16) & 0xFF;
|
||||
tmp19[2] = (uint32_t(tmp18) >> 8) & 0xFF;
|
||||
tmp19[3] = (uint32_t(tmp18) >> 0) & 0xFF;
|
||||
in.append(tmp19, 4);
|
||||
uint32_t tmp19 = in0.M3u8Url.length();
|
||||
char tmp20[4];
|
||||
tmp20[0] = (uint32_t(tmp19) >> 24) & 0xFF;
|
||||
tmp20[1] = (uint32_t(tmp19) >> 16) & 0xFF;
|
||||
tmp20[2] = (uint32_t(tmp19) >> 8) & 0xFF;
|
||||
tmp20[3] = (uint32_t(tmp19) >> 0) & 0xFF;
|
||||
in.append(tmp20, 4);
|
||||
in.append(in0.M3u8Url);
|
||||
}
|
||||
{
|
||||
uint32_t tmp20 = in0.HostType.length();
|
||||
char tmp21[4];
|
||||
tmp21[0] = (uint32_t(tmp20) >> 24) & 0xFF;
|
||||
tmp21[1] = (uint32_t(tmp20) >> 16) & 0xFF;
|
||||
tmp21[2] = (uint32_t(tmp20) >> 8) & 0xFF;
|
||||
tmp21[3] = (uint32_t(tmp20) >> 0) & 0xFF;
|
||||
in.append(tmp21, 4);
|
||||
uint32_t tmp21 = in0.HostType.length();
|
||||
char tmp22[4];
|
||||
tmp22[0] = (uint32_t(tmp21) >> 24) & 0xFF;
|
||||
tmp22[1] = (uint32_t(tmp21) >> 16) & 0xFF;
|
||||
tmp22[2] = (uint32_t(tmp21) >> 8) & 0xFF;
|
||||
tmp22[3] = (uint32_t(tmp21) >> 0) & 0xFF;
|
||||
in.append(tmp22, 4);
|
||||
in.append(in0.HostType);
|
||||
}
|
||||
in.append((char*)(&in0.Insecure), 1);
|
||||
{
|
||||
uint32_t tmp22 = in0.SaveDir.length();
|
||||
char tmp23[4];
|
||||
tmp23[0] = (uint32_t(tmp22) >> 24) & 0xFF;
|
||||
tmp23[1] = (uint32_t(tmp22) >> 16) & 0xFF;
|
||||
tmp23[2] = (uint32_t(tmp22) >> 8) & 0xFF;
|
||||
tmp23[3] = (uint32_t(tmp22) >> 0) & 0xFF;
|
||||
in.append(tmp23, 4);
|
||||
uint32_t tmp23 = in0.SaveDir.length();
|
||||
char tmp24[4];
|
||||
tmp24[0] = (uint32_t(tmp23) >> 24) & 0xFF;
|
||||
tmp24[1] = (uint32_t(tmp23) >> 16) & 0xFF;
|
||||
tmp24[2] = (uint32_t(tmp23) >> 8) & 0xFF;
|
||||
tmp24[3] = (uint32_t(tmp23) >> 0) & 0xFF;
|
||||
in.append(tmp24, 4);
|
||||
in.append(in0.SaveDir);
|
||||
}
|
||||
{
|
||||
uint32_t tmp24 = in0.FileName.length();
|
||||
char tmp25[4];
|
||||
tmp25[0] = (uint32_t(tmp24) >> 24) & 0xFF;
|
||||
tmp25[1] = (uint32_t(tmp24) >> 16) & 0xFF;
|
||||
tmp25[2] = (uint32_t(tmp24) >> 8) & 0xFF;
|
||||
tmp25[3] = (uint32_t(tmp24) >> 0) & 0xFF;
|
||||
in.append(tmp25, 4);
|
||||
uint32_t tmp25 = in0.FileName.length();
|
||||
char tmp26[4];
|
||||
tmp26[0] = (uint32_t(tmp25) >> 24) & 0xFF;
|
||||
tmp26[1] = (uint32_t(tmp25) >> 16) & 0xFF;
|
||||
tmp26[2] = (uint32_t(tmp25) >> 8) & 0xFF;
|
||||
tmp26[3] = (uint32_t(tmp25) >> 0) & 0xFF;
|
||||
in.append(tmp26, 4);
|
||||
in.append(in0.FileName);
|
||||
}
|
||||
{
|
||||
char tmp26[4];
|
||||
tmp26[0] = (uint32_t(in0.SkipTsCountFromHead) >> 24) & 0xFF;
|
||||
tmp26[1] = (uint32_t(in0.SkipTsCountFromHead) >> 16) & 0xFF;
|
||||
tmp26[2] = (uint32_t(in0.SkipTsCountFromHead) >> 8) & 0xFF;
|
||||
tmp26[3] = (uint32_t(in0.SkipTsCountFromHead) >> 0) & 0xFF;
|
||||
in.append(tmp26, 4);
|
||||
char tmp27[4];
|
||||
tmp27[0] = (uint32_t(in0.SkipTsCountFromHead) >> 24) & 0xFF;
|
||||
tmp27[1] = (uint32_t(in0.SkipTsCountFromHead) >> 16) & 0xFF;
|
||||
tmp27[2] = (uint32_t(in0.SkipTsCountFromHead) >> 8) & 0xFF;
|
||||
tmp27[3] = (uint32_t(in0.SkipTsCountFromHead) >> 0) & 0xFF;
|
||||
in.append(tmp27, 4);
|
||||
}
|
||||
}
|
||||
char *out = NULL;
|
||||
|
|
@ -138,28 +139,30 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){
|
|||
int outIdx = 0;
|
||||
{
|
||||
{
|
||||
uint32_t tmp27 = 0;
|
||||
uint32_t tmp28 = 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+2]) << 8);
|
||||
uint32_t tmp31 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
||||
tmp27 = tmp28 | tmp29 | tmp30 | tmp31;
|
||||
uint32_t tmp28 = 0;
|
||||
uint32_t tmp29 = uint32_t(uint8_t(out[outIdx+0]) << 24);
|
||||
uint32_t tmp30 = uint32_t(uint8_t(out[outIdx+1]) << 16);
|
||||
uint32_t tmp31 = uint32_t(uint8_t(out[outIdx+2]) << 8);
|
||||
uint32_t tmp32 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
||||
tmp28 = tmp29 | tmp30 | tmp31 | tmp32;
|
||||
outIdx+=4;
|
||||
retValue.ErrMsg = std::string(out+outIdx, out+outIdx+tmp27);
|
||||
outIdx+=tmp27;
|
||||
retValue.ErrMsg = std::string(out+outIdx, out+outIdx+tmp28);
|
||||
outIdx+=tmp28;
|
||||
}
|
||||
retValue.IsSkipped = (bool) out[outIdx];
|
||||
outIdx++;
|
||||
retValue.IsCancel = (bool) out[outIdx];
|
||||
outIdx++;
|
||||
{
|
||||
uint32_t tmp32 = 0;
|
||||
uint32_t tmp33 = 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+2]) << 8);
|
||||
uint32_t tmp36 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
||||
tmp32 = tmp33 | tmp34 | tmp35 | tmp36;
|
||||
uint32_t tmp33 = 0;
|
||||
uint32_t tmp34 = uint32_t(uint8_t(out[outIdx+0]) << 24);
|
||||
uint32_t tmp35 = uint32_t(uint8_t(out[outIdx+1]) << 16);
|
||||
uint32_t tmp36 = uint32_t(uint8_t(out[outIdx+2]) << 8);
|
||||
uint32_t tmp37 = uint32_t(uint8_t(out[outIdx+3]) << 0);
|
||||
tmp33 = tmp34 | tmp35 | tmp36 | tmp37;
|
||||
outIdx+=4;
|
||||
retValue.SaveFileTo = std::string(out+outIdx, out+outIdx+tmp32);
|
||||
outIdx+=tmp32;
|
||||
retValue.SaveFileTo = std::string(out+outIdx, out+outIdx+tmp33);
|
||||
outIdx+=tmp33;
|
||||
}
|
||||
}
|
||||
if (out != NULL) {
|
||||
|
|
@ -168,6 +171,16 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){
|
|||
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(){
|
||||
std::string in;
|
||||
char *out = NULL;
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ struct RunDownload_Req{
|
|||
struct RunDownload_Resp{
|
||||
std::string ErrMsg;
|
||||
bool IsSkipped;
|
||||
bool IsCancel;
|
||||
std::string SaveFileTo;
|
||||
};
|
||||
RunDownload_Resp RunDownload(RunDownload_Req in0);
|
||||
void CloseOldEnv();
|
||||
int32_t GetProgress();
|
||||
std::string GetWd();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
CloseOldEnv();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +36,8 @@ void MainWindow::on_pushButton_RunDownload_clicked()
|
|||
ui->checkBox_Insecure->setEnabled(false);
|
||||
ui->progressBar->setValue(0);
|
||||
ui->pushButton_RunDownload->setText("正在下载...");
|
||||
ui->pushButton_StopDownload->setEnabled(true);
|
||||
|
||||
m_syncUi.AddRunFnOn_OtherThread([this](){
|
||||
// isFinished被 other thread 和 ui thread共享
|
||||
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->checkBox_Insecure->setEnabled(false);
|
||||
ui->pushButton_RunDownload->setText("开始下载");
|
||||
ui->pushButton_StopDownload->setEnabled(false);
|
||||
if (resp.IsCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resp.ErrMsg.empty()) {
|
||||
Toast::Instance()->SetError(QString::fromStdString(resp.ErrMsg));
|
||||
|
|
@ -91,3 +98,8 @@ void MainWindow::on_pushButton_SaveDir_clicked()
|
|||
QString dir = QFileDialog::getExistingDirectory(this);
|
||||
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_StopDownload_clicked();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
RunOnUiThread m_syncUi;
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@
|
|||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
|
|
@ -134,6 +134,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_StopDownload">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>停止下载</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</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