diff --git a/README.md b/README.md index ba7ebe1..e9a8011 100644 --- a/README.md +++ b/README.md @@ -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): ![](m3u8d-qt/screenshot.png) * 全部版本下载, 包括windows图形界面/linux命令行/mac命令行: https://github.com/orestonce/m3u8d/releases diff --git a/download.go b/download.go index 79bc100..6aad685 100644 --- a/download.go +++ b/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 + } +} diff --git a/export/main.go b/export/main.go index f2d8642..f266093 100644 --- a/export/main.go +++ b/export/main.go @@ -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") diff --git a/m3u8d-qt/m3u8d-impl.a b/m3u8d-qt/m3u8d-impl.a index c89204e..836ad63 100644 Binary files a/m3u8d-qt/m3u8d-impl.a and b/m3u8d-qt/m3u8d-impl.a differ diff --git a/m3u8d-qt/m3u8d.cpp b/m3u8d-qt/m3u8d.cpp index 2e4f5dd..06bf9f2 100644 --- a/m3u8d-qt/m3u8d.cpp +++ b/m3u8d-qt/m3u8d.cpp @@ -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; diff --git a/m3u8d-qt/m3u8d.h b/m3u8d-qt/m3u8d.h index 0500e69..18f687a 100644 --- a/m3u8d-qt/m3u8d.h +++ b/m3u8d-qt/m3u8d.h @@ -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(); diff --git a/m3u8d-qt/mainwindow.cpp b/m3u8d-qt/mainwindow.cpp index 8b3f7d6..2fac900 100644 --- a/m3u8d-qt/mainwindow.cpp +++ b/m3u8d-qt/mainwindow.cpp @@ -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 isFinished = std::make_shared(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(); +} diff --git a/m3u8d-qt/mainwindow.h b/m3u8d-qt/mainwindow.h index 42c3ac3..5ef614e 100644 --- a/m3u8d-qt/mainwindow.h +++ b/m3u8d-qt/mainwindow.h @@ -21,6 +21,8 @@ private slots: void on_pushButton_SaveDir_clicked(); + void on_pushButton_StopDownload_clicked(); + private: Ui::MainWindow *ui; RunOnUiThread m_syncUi; diff --git a/m3u8d-qt/mainwindow.ui b/m3u8d-qt/mainwindow.ui index 193d5a0..a3e77c0 100644 --- a/m3u8d-qt/mainwindow.ui +++ b/m3u8d-qt/mainwindow.ui @@ -112,7 +112,7 @@ - + @@ -134,6 +134,16 @@ + + + + false + + + 停止下载 + + + diff --git a/merge.go b/merge.go deleted file mode 100644 index 9b2c5fa..0000000 --- a/merge.go +++ /dev/null @@ -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 -}