2022-05-15 03:05:31 +00:00
|
|
|
|
package m3u8d
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2022-09-29 07:20:20 +00:00
|
|
|
|
"bytes"
|
2022-06-12 14:43:20 +00:00
|
|
|
|
"context"
|
2022-05-15 03:05:31 +00:00
|
|
|
|
"crypto/aes"
|
|
|
|
|
|
"crypto/cipher"
|
|
|
|
|
|
"crypto/sha256"
|
2022-06-12 14:43:20 +00:00
|
|
|
|
"crypto/tls"
|
2023-06-02 12:02:54 +00:00
|
|
|
|
"encoding/binary"
|
2022-05-15 03:05:31 +00:00
|
|
|
|
"encoding/hex"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"io/ioutil"
|
2022-05-21 12:26:17 +00:00
|
|
|
|
"net/http"
|
2022-05-15 03:05:31 +00:00
|
|
|
|
"net/url"
|
|
|
|
|
|
"os"
|
2022-10-04 09:02:24 +00:00
|
|
|
|
"path"
|
2022-05-15 03:05:31 +00:00
|
|
|
|
"path/filepath"
|
2022-05-21 12:26:17 +00:00
|
|
|
|
"regexp"
|
2022-05-15 03:05:31 +00:00
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync"
|
2022-07-20 16:08:58 +00:00
|
|
|
|
"sync/atomic"
|
2022-05-15 03:05:31 +00:00
|
|
|
|
"time"
|
2022-09-29 07:20:20 +00:00
|
|
|
|
|
|
|
|
|
|
"github.com/orestonce/gopool"
|
2022-05-15 03:05:31 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// TsInfo 用于保存 ts 文件的下载地址和文件名
|
|
|
|
|
|
type TsInfo struct {
|
|
|
|
|
|
Name string
|
|
|
|
|
|
Url string
|
2023-06-02 12:02:54 +00:00
|
|
|
|
Seq uint64 // 如果是aes加密并且没有iv, 这个seq需要充当iv
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-19 23:32:53 +00:00
|
|
|
|
type GetProgress_Resp struct {
|
2022-08-03 16:54:39 +00:00
|
|
|
|
Percent int
|
|
|
|
|
|
Title string
|
|
|
|
|
|
StatusBar string
|
2022-06-19 23:32:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-29 07:20:20 +00:00
|
|
|
|
var PNG_SIGN = []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
|
|
|
|
|
|
|
2022-06-19 23:32:53 +00:00
|
|
|
|
func GetProgress() (resp GetProgress_Resp) {
|
2022-07-20 16:08:58 +00:00
|
|
|
|
var sleepTh int32
|
2022-08-03 16:54:39 +00:00
|
|
|
|
var speedV string
|
|
|
|
|
|
|
2022-07-20 16:08:58 +00:00
|
|
|
|
gOldEnvLocker.Lock()
|
|
|
|
|
|
if gOldEnv != nil {
|
|
|
|
|
|
sleepTh = atomic.LoadInt32(&gOldEnv.sleepTh)
|
2022-08-02 23:37:54 +00:00
|
|
|
|
gOldEnv.progressLocker.Lock()
|
|
|
|
|
|
resp.Percent = gOldEnv.progressPercent
|
|
|
|
|
|
resp.Title = gOldEnv.progressBarTitle
|
|
|
|
|
|
gOldEnv.progressLocker.Unlock()
|
|
|
|
|
|
if resp.Title == "" {
|
|
|
|
|
|
resp.Title = "正在下载"
|
|
|
|
|
|
}
|
2022-08-03 16:54:39 +00:00
|
|
|
|
speedV = gOldEnv.speedRecent5sGetAndUpdate()
|
2022-07-20 16:08:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
gOldEnvLocker.Unlock()
|
2022-08-03 16:54:39 +00:00
|
|
|
|
resp.StatusBar = speedV
|
2022-07-20 16:08:58 +00:00
|
|
|
|
if sleepTh > 0 {
|
2022-08-03 16:54:39 +00:00
|
|
|
|
if resp.StatusBar != "" {
|
|
|
|
|
|
resp.StatusBar += ", "
|
|
|
|
|
|
}
|
|
|
|
|
|
resp.StatusBar += "有 " + strconv.Itoa(int(sleepTh)) + "个线程正在休眠."
|
2022-07-20 16:08:58 +00:00
|
|
|
|
}
|
2022-06-19 23:32:53 +00:00
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-02 23:37:54 +00:00
|
|
|
|
func (this *downloadEnv) SetProgressBarTitle(title string) {
|
|
|
|
|
|
this.progressLocker.Lock()
|
|
|
|
|
|
this.progressBarTitle = title
|
|
|
|
|
|
this.progressLocker.Unlock()
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type RunDownload_Resp struct {
|
|
|
|
|
|
ErrMsg string
|
|
|
|
|
|
IsSkipped bool
|
2022-06-12 14:43:20 +00:00
|
|
|
|
IsCancel bool
|
2022-05-15 03:05:31 +00:00
|
|
|
|
SaveFileTo string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type RunDownload_Req struct {
|
2022-06-19 23:32:53 +00:00
|
|
|
|
M3u8Url string
|
|
|
|
|
|
Insecure bool // "是否允许不安全的请求(默认为false)"
|
|
|
|
|
|
SaveDir string // "文件保存路径(默认为当前路径)"
|
|
|
|
|
|
FileName string // 文件名
|
|
|
|
|
|
SkipTsCountFromHead int // 跳过前面几个ts
|
2022-06-20 23:22:53 +00:00
|
|
|
|
SetProxy string
|
2022-06-25 12:16:21 +00:00
|
|
|
|
HeaderMap map[string][]string
|
2022-08-02 23:37:54 +00:00
|
|
|
|
SkipRemoveTs bool
|
2022-08-09 01:25:30 +00:00
|
|
|
|
ProgressBarShow bool
|
2022-12-06 12:20:36 +00:00
|
|
|
|
ThreadCount int
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-12 14:43:20 +00:00
|
|
|
|
type downloadEnv struct {
|
2022-08-02 23:37:54 +00:00
|
|
|
|
cancelFn func()
|
|
|
|
|
|
ctx context.Context
|
2022-08-09 00:05:32 +00:00
|
|
|
|
nowClient *http.Client
|
2022-08-02 23:37:54 +00:00
|
|
|
|
header http.Header
|
|
|
|
|
|
sleepTh int32
|
|
|
|
|
|
progressLocker sync.Mutex
|
|
|
|
|
|
progressBarTitle string
|
|
|
|
|
|
progressPercent int
|
|
|
|
|
|
progressBarShow bool
|
2022-08-03 16:54:39 +00:00
|
|
|
|
speedBytesLocker sync.Mutex
|
|
|
|
|
|
speedBeginTime time.Time
|
|
|
|
|
|
speedBytesMap map[time.Time]int64
|
2022-06-12 14:43:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (this *downloadEnv) RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if req.SaveDir == "" {
|
|
|
|
|
|
var err error
|
|
|
|
|
|
req.SaveDir, err = os.Getwd()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "os.Getwd error: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.FileName == "" {
|
2022-10-06 01:57:34 +00:00
|
|
|
|
req.FileName = GetFileNameFromUrl(req.M3u8Url)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
if req.SkipTsCountFromHead < 0 {
|
|
|
|
|
|
req.SkipTsCountFromHead = 0
|
|
|
|
|
|
}
|
2022-07-23 01:38:04 +00:00
|
|
|
|
host, err := getHost(req.M3u8Url)
|
2022-06-12 14:43:20 +00:00
|
|
|
|
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},
|
|
|
|
|
|
}
|
2022-06-25 12:16:21 +00:00
|
|
|
|
for key, valueList := range req.HeaderMap {
|
|
|
|
|
|
this.header[key] = valueList
|
|
|
|
|
|
}
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.SetProgressBarTitle("[1/6]嗅探m3u8")
|
2022-06-20 13:34:52 +00:00
|
|
|
|
var m3u8Body []byte
|
2022-07-23 01:38:04 +00:00
|
|
|
|
var errMsg string
|
|
|
|
|
|
req.M3u8Url, m3u8Body, errMsg = this.sniffM3u8(req.M3u8Url)
|
|
|
|
|
|
if errMsg != "" {
|
|
|
|
|
|
resp.ErrMsg = "sniffM3u8: " + errMsg
|
2022-06-12 14:43:20 +00:00
|
|
|
|
resp.IsCancel = this.GetIsCancel()
|
2022-05-21 12:26:17 +00:00
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-05-15 03:05:31 +00:00
|
|
|
|
id, err := req.getVideoId()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "getVideoId: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
info, err := cacheRead(req.SaveDir, id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "cacheRead: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
if info != nil {
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.SetProgressBarTitle("[2/6]检查是否已下载")
|
2022-05-15 03:05:31 +00:00
|
|
|
|
latestNameFullPath, found := info.SearchVideoInDir(req.SaveDir)
|
|
|
|
|
|
if found {
|
|
|
|
|
|
resp.IsSkipped = true
|
|
|
|
|
|
resp.SaveFileTo = latestNameFullPath
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if !strings.HasPrefix(req.M3u8Url, "http") || req.M3u8Url == "" {
|
|
|
|
|
|
resp.ErrMsg = "M3u8Url not valid " + strconv.Quote(req.M3u8Url)
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
downloadDir := filepath.Join(req.SaveDir, "downloading", id)
|
|
|
|
|
|
if !isDirExists(downloadDir) {
|
|
|
|
|
|
err = os.MkdirAll(downloadDir, os.ModePerm)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "os.MkdirAll error: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-06-02 12:02:54 +00:00
|
|
|
|
beginSeq := parseBeginSeq(m3u8Body)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
// 获取m3u8地址的内容体
|
2023-05-15 01:26:38 +00:00
|
|
|
|
encInfo, err := this.getEncryptInfo(req.M3u8Url, string(m3u8Body))
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if err != nil {
|
2023-05-15 01:26:38 +00:00
|
|
|
|
resp.ErrMsg = "getEncryptInfo: " + err.Error()
|
2022-06-12 14:43:20 +00:00
|
|
|
|
resp.IsCancel = this.GetIsCancel()
|
2022-05-15 03:05:31 +00:00
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.SetProgressBarTitle("[3/6]获取ts列表")
|
2023-06-02 12:02:54 +00:00
|
|
|
|
tsList, errMsg := getTsList(beginSeq, req.M3u8Url, string(m3u8Body))
|
2022-07-20 16:08:58 +00:00
|
|
|
|
if errMsg != "" {
|
2022-07-23 01:53:12 +00:00
|
|
|
|
resp.ErrMsg = "获取ts列表错误: " + errMsg
|
2022-07-20 16:08:58 +00:00
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-06-12 14:43:20 +00:00
|
|
|
|
if len(tsList) <= req.SkipTsCountFromHead {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
resp.ErrMsg = "需要下载的文件为空"
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-06-12 14:43:20 +00:00
|
|
|
|
tsList = tsList[req.SkipTsCountFromHead:]
|
2022-05-15 03:05:31 +00:00
|
|
|
|
// 下载ts
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.SetProgressBarTitle("[4/6]下载ts")
|
2022-08-03 16:54:39 +00:00
|
|
|
|
this.speedSetBegin()
|
2023-05-15 01:26:38 +00:00
|
|
|
|
err = this.downloader(tsList, downloadDir, encInfo, req.ThreadCount)
|
2022-08-03 16:54:39 +00:00
|
|
|
|
this.speedClearBytes()
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "下载ts文件错误: " + err.Error()
|
2022-06-12 14:43:20 +00:00
|
|
|
|
resp.IsCancel = this.GetIsCancel()
|
2022-05-15 03:05:31 +00:00
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.DrawProgressBar(1, 1)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
var tsFileList []string
|
2022-06-12 14:43:20 +00:00
|
|
|
|
for _, one := range tsList {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
tsFileList = append(tsFileList, filepath.Join(downloadDir, one.Name))
|
|
|
|
|
|
}
|
|
|
|
|
|
var tmpOutputName string
|
|
|
|
|
|
var contentHash string
|
2022-05-29 09:43:00 +00:00
|
|
|
|
tmpOutputName = filepath.Join(downloadDir, "all.merge.mp4")
|
2022-06-19 23:32:53 +00:00
|
|
|
|
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.SetProgressBarTitle("[5/6]合并ts为mp4")
|
2022-08-03 16:54:39 +00:00
|
|
|
|
this.speedSetBegin()
|
2022-06-19 23:32:53 +00:00
|
|
|
|
err = MergeTsFileListToSingleMp4(MergeTsFileListToSingleMp4_Req{
|
|
|
|
|
|
TsFileList: tsFileList,
|
|
|
|
|
|
OutputMp4: tmpOutputName,
|
2022-07-23 03:21:52 +00:00
|
|
|
|
Ctx: this.ctx,
|
2022-05-29 09:43:00 +00:00
|
|
|
|
})
|
2022-08-03 16:54:39 +00:00
|
|
|
|
this.speedClearBytes()
|
2022-05-29 09:43:00 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "合并错误: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.SetProgressBarTitle("[6/6]计算文件hash")
|
2022-05-29 09:43:00 +00:00
|
|
|
|
contentHash = getFileSha256(tmpOutputName)
|
|
|
|
|
|
if contentHash == "" {
|
|
|
|
|
|
resp.ErrMsg = "无法计算摘要信息: " + tmpOutputName
|
|
|
|
|
|
return resp
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
var name string
|
|
|
|
|
|
for idx := 0; ; idx++ {
|
|
|
|
|
|
idxS := strconv.Itoa(idx)
|
|
|
|
|
|
if len(idxS) < 4 {
|
|
|
|
|
|
idxS = strings.Repeat("0", 4-len(idxS)) + idxS
|
|
|
|
|
|
}
|
|
|
|
|
|
idxS = "_" + idxS
|
2022-05-29 09:43:00 +00:00
|
|
|
|
if idx == 0 {
|
|
|
|
|
|
name = filepath.Join(req.SaveDir, req.FileName+".mp4")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
name = filepath.Join(req.SaveDir, req.FileName+idxS+".mp4")
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
if !isFileExists(name) {
|
|
|
|
|
|
resp.SaveFileTo = name
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
if idx > 10000 { // 超过1万就不找了
|
|
|
|
|
|
resp.ErrMsg = "自动寻找文件名失败"
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
err = os.Rename(tmpOutputName, name)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "重命名失败: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
err = cacheWrite(req.SaveDir, id, req, resp.SaveFileTo, contentHash)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "cacheWrite: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-08-02 23:37:54 +00:00
|
|
|
|
if req.SkipRemoveTs == false {
|
|
|
|
|
|
err = os.RemoveAll(downloadDir)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
resp.ErrMsg = "删除下载目录失败: " + err.Error()
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果downloading目录为空,就删除掉,否则忽略
|
|
|
|
|
|
_ = os.Remove(filepath.Join(req.SaveDir, "downloading"))
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-02 12:02:54 +00:00
|
|
|
|
func parseBeginSeq(body []byte) uint64 {
|
|
|
|
|
|
data := M3u8Parse(string(body))
|
|
|
|
|
|
seq := data.GetPart(`#EXT-X-MEDIA-SEQUENCE`).TextFull
|
|
|
|
|
|
u, err := strconv.ParseUint(seq, 10, 64)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
return u
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-12 14:43:20 +00:00
|
|
|
|
var gOldEnv *downloadEnv
|
|
|
|
|
|
var gOldEnvLocker sync.Mutex
|
|
|
|
|
|
|
|
|
|
|
|
func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
2022-08-09 00:05:32 +00:00
|
|
|
|
var proxyUrlObj *url.URL
|
|
|
|
|
|
req.SetProxy, proxyUrlObj, resp.ErrMsg = SetProxyFormat(req.SetProxy)
|
|
|
|
|
|
if resp.ErrMsg != "" {
|
|
|
|
|
|
return resp
|
|
|
|
|
|
}
|
2022-06-12 14:43:20 +00:00
|
|
|
|
env := &downloadEnv{
|
2022-08-09 00:05:32 +00:00
|
|
|
|
nowClient: &http.Client{
|
|
|
|
|
|
Timeout: time.Second * 20,
|
2022-06-12 14:43:20 +00:00
|
|
|
|
Transport: &http.Transport{
|
|
|
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
|
|
InsecureSkipVerify: req.Insecure,
|
|
|
|
|
|
},
|
2022-08-09 00:05:32 +00:00
|
|
|
|
Proxy: http.ProxyURL(proxyUrlObj),
|
2022-06-12 14:43:20 +00:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2022-08-30 00:45:18 +00:00
|
|
|
|
speedBytesMap: map[time.Time]int64{},
|
2022-08-09 01:25:30 +00:00
|
|
|
|
progressBarShow: req.ProgressBarShow,
|
2022-06-12 14:43:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
env.ctx, env.cancelFn = context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
|
|
|
|
gOldEnvLocker.Lock()
|
|
|
|
|
|
if gOldEnv != nil {
|
|
|
|
|
|
gOldEnv.cancelFn()
|
|
|
|
|
|
}
|
|
|
|
|
|
gOldEnv = env
|
|
|
|
|
|
gOldEnvLocker.Unlock()
|
2022-06-20 13:34:52 +00:00
|
|
|
|
resp = env.RunDownload(req)
|
2022-08-02 23:37:54 +00:00
|
|
|
|
env.SetProgressBarTitle("下载进度")
|
|
|
|
|
|
env.DrawProgressBar(1, 0)
|
2022-06-20 13:34:52 +00:00
|
|
|
|
return resp
|
2022-06-12 14:43:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-02 23:37:54 +00:00
|
|
|
|
func getOldEnv() *downloadEnv {
|
|
|
|
|
|
gOldEnvLocker.Lock()
|
|
|
|
|
|
defer gOldEnvLocker.Unlock()
|
|
|
|
|
|
return gOldEnv
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-12 14:43:20 +00:00
|
|
|
|
func CloseOldEnv() {
|
|
|
|
|
|
gOldEnvLocker.Lock()
|
|
|
|
|
|
defer gOldEnvLocker.Unlock()
|
|
|
|
|
|
if gOldEnv != nil {
|
|
|
|
|
|
gOldEnv.cancelFn()
|
|
|
|
|
|
}
|
|
|
|
|
|
gOldEnv = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-15 03:05:31 +00:00
|
|
|
|
// 获取m3u8地址的host
|
2022-07-23 01:38:04 +00:00
|
|
|
|
func getHost(Url string) (host string, err error) {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
u, err := url.Parse(Url)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return u.Scheme + "://" + u.Host, nil
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func GetWd() string {
|
|
|
|
|
|
wd, _ := os.Getwd()
|
|
|
|
|
|
return wd
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-15 01:26:38 +00:00
|
|
|
|
// 获取m3u8加密的密钥, 可能存在iv
|
|
|
|
|
|
func (this *downloadEnv) getEncryptInfo(m3u8Url string, html string) (info *EncryptInfo, err error) {
|
|
|
|
|
|
keyPart := M3u8Parse(html).GetPart("#EXT-X-KEY")
|
|
|
|
|
|
uri := keyPart.KeyValue["URI"]
|
|
|
|
|
|
if uri == "" {
|
|
|
|
|
|
return nil, nil
|
|
|
|
|
|
}
|
2023-06-02 12:02:54 +00:00
|
|
|
|
method := keyPart.KeyValue["METHOD"]
|
|
|
|
|
|
if method == EncryptMethod_NONE {
|
|
|
|
|
|
return nil, nil
|
|
|
|
|
|
}
|
2023-05-15 01:26:38 +00:00
|
|
|
|
keyUrl, errMsg := resolveRefUrl(m3u8Url, uri)
|
|
|
|
|
|
if errMsg != "" {
|
|
|
|
|
|
return nil, errors.New(errMsg)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
2023-05-15 01:26:38 +00:00
|
|
|
|
var res []byte
|
|
|
|
|
|
res, err = this.doGetRequest(keyUrl)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2023-06-02 12:02:54 +00:00
|
|
|
|
if method == EncryptMethod_AES128 && len(res) != 16 { // Aes 128
|
|
|
|
|
|
return nil, errors.New("getEncryptInfo invalid key " + strconv.Quote(string(res)))
|
|
|
|
|
|
}
|
|
|
|
|
|
var iv []byte
|
|
|
|
|
|
ivs := keyPart.KeyValue["IV"]
|
|
|
|
|
|
if ivs != "" {
|
|
|
|
|
|
iv, err = hex.DecodeString(strings.TrimPrefix(ivs, "0x"))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2023-05-15 01:26:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
return &EncryptInfo{
|
2023-06-02 12:02:54 +00:00
|
|
|
|
Method: method,
|
2023-05-15 01:26:38 +00:00
|
|
|
|
Key: res,
|
|
|
|
|
|
Iv: iv,
|
|
|
|
|
|
}, nil
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-23 01:53:12 +00:00
|
|
|
|
func splitLineWithTrimSpace(s string) []string {
|
|
|
|
|
|
tmp := strings.Split(s, "\n")
|
|
|
|
|
|
for idx, str := range tmp {
|
|
|
|
|
|
str = strings.TrimSpace(str)
|
|
|
|
|
|
tmp[idx] = str
|
|
|
|
|
|
}
|
|
|
|
|
|
return tmp
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-02 12:02:54 +00:00
|
|
|
|
func getTsList(beginSeq uint64, m38uUrl string, body string) (tsList []TsInfo, errMsg string) {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
index := 0
|
|
|
|
|
|
|
2022-07-23 01:53:12 +00:00
|
|
|
|
for _, line := range splitLineWithTrimSpace(body) {
|
|
|
|
|
|
line = strings.TrimSpace(line)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if !strings.HasPrefix(line, "#") && line != "" {
|
|
|
|
|
|
index++
|
2022-07-20 16:08:58 +00:00
|
|
|
|
var after string
|
2022-07-23 01:38:04 +00:00
|
|
|
|
after, errMsg = resolveRefUrl(m38uUrl, line)
|
2022-07-20 16:08:58 +00:00
|
|
|
|
if errMsg != "" {
|
|
|
|
|
|
return nil, errMsg
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
2022-07-20 16:08:58 +00:00
|
|
|
|
tsList = append(tsList, TsInfo{
|
2022-07-23 01:38:04 +00:00
|
|
|
|
Name: fmt.Sprintf("%05d.ts", index), // ts视频片段命名规则
|
2022-07-20 16:08:58 +00:00
|
|
|
|
Url: after,
|
2023-06-02 12:02:54 +00:00
|
|
|
|
Seq: beginSeq + uint64(index-1),
|
2022-07-20 16:08:58 +00:00
|
|
|
|
})
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-20 16:08:58 +00:00
|
|
|
|
return tsList, ""
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 下载ts文件
|
|
|
|
|
|
// @modify: 2020-08-13 修复ts格式SyncByte合并不能播放问题
|
2023-05-15 01:26:38 +00:00
|
|
|
|
func (this *downloadEnv) downloadTsFile(ts TsInfo, download_dir string, encInfo *EncryptInfo) (err error) {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
currPath := fmt.Sprintf("%s/%s", download_dir, ts.Name)
|
|
|
|
|
|
if isFileExists(currPath) {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2022-06-12 14:43:20 +00:00
|
|
|
|
data, err := this.doGetRequest(ts.Url)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 校验长度是否合法
|
|
|
|
|
|
var origData []byte
|
2022-06-12 14:43:20 +00:00
|
|
|
|
origData = data
|
2022-05-15 03:05:31 +00:00
|
|
|
|
// 解密出视频 ts 源文件
|
2023-05-15 01:26:38 +00:00
|
|
|
|
if encInfo != nil {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
//解密 ts 文件,算法:aes 128 cbc pack5
|
2023-06-02 12:02:54 +00:00
|
|
|
|
origData, err = AesDecrypt(ts.Seq, origData, encInfo)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-09-29 07:20:20 +00:00
|
|
|
|
|
|
|
|
|
|
// Detect Fake png file
|
|
|
|
|
|
if bytes.HasPrefix(origData, PNG_SIGN) {
|
|
|
|
|
|
origData = origData[8:]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-15 03:05:31 +00:00
|
|
|
|
// https://en.wikipedia.org/wiki/MPEG_transport_stream
|
|
|
|
|
|
// Some TS files do not start with SyncByte 0x47, they can not be played after merging,
|
|
|
|
|
|
// Need to remove the bytes before the SyncByte 0x47(71).
|
|
|
|
|
|
syncByte := uint8(71) //0x47
|
|
|
|
|
|
bLen := len(origData)
|
|
|
|
|
|
for j := 0; j < bLen; j++ {
|
|
|
|
|
|
if origData[j] == syncByte {
|
|
|
|
|
|
origData = origData[j:]
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
tmpPath := currPath + ".tmp"
|
|
|
|
|
|
err = ioutil.WriteFile(tmpPath, origData, 0666)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2022-08-03 16:54:39 +00:00
|
|
|
|
err = os.Rename(tmpPath, currPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
this.speedAddBytes(len(origData))
|
|
|
|
|
|
return nil
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-12 14:43:20 +00:00
|
|
|
|
func (this *downloadEnv) SleepDur(d time.Duration) {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-time.After(d):
|
|
|
|
|
|
case <-this.ctx.Done():
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-05-15 01:26:38 +00:00
|
|
|
|
func (this *downloadEnv) downloader(tsList []TsInfo, downloadDir string, encInfo *EncryptInfo, threadCount int) (err error) {
|
2022-12-06 12:20:36 +00:00
|
|
|
|
if threadCount <= 0 || threadCount > 1000 {
|
|
|
|
|
|
return errors.New("downloadEnv.threadCount invalid: " + strconv.Itoa(threadCount))
|
2022-10-23 23:25:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
task := gopool.NewThreadPool(threadCount)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
tsLen := len(tsList)
|
|
|
|
|
|
downloadCount := 0
|
|
|
|
|
|
var locker sync.Mutex
|
|
|
|
|
|
|
|
|
|
|
|
for _, ts := range tsList {
|
|
|
|
|
|
ts := ts
|
|
|
|
|
|
task.AddJob(func() {
|
|
|
|
|
|
var lastErr error
|
|
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
|
|
locker.Lock()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
locker.Unlock()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
locker.Unlock()
|
2022-06-12 14:43:20 +00:00
|
|
|
|
if i > 0 {
|
2022-07-20 16:08:58 +00:00
|
|
|
|
atomic.AddInt32(&this.sleepTh, 1)
|
2022-06-12 14:43:20 +00:00
|
|
|
|
this.SleepDur(time.Second * time.Duration(i))
|
2022-07-20 16:08:58 +00:00
|
|
|
|
atomic.AddInt32(&this.sleepTh, -1)
|
2022-06-12 14:43:20 +00:00
|
|
|
|
}
|
2023-05-15 01:26:38 +00:00
|
|
|
|
lastErr = this.downloadTsFile(ts, downloadDir, encInfo)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if lastErr == nil {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2022-06-12 14:43:20 +00:00
|
|
|
|
if this.GetIsCancel() {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
if lastErr != nil {
|
|
|
|
|
|
locker.Lock()
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
err = lastErr
|
|
|
|
|
|
}
|
|
|
|
|
|
locker.Unlock()
|
|
|
|
|
|
}
|
|
|
|
|
|
locker.Lock()
|
|
|
|
|
|
downloadCount++
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.DrawProgressBar(tsLen, downloadCount)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
locker.Unlock()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
task.CloseAndWait()
|
|
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 进度条
|
2022-08-02 23:37:54 +00:00
|
|
|
|
func (this *downloadEnv) DrawProgressBar(total int, current int) {
|
2022-06-19 23:32:53 +00:00
|
|
|
|
if total == 0 {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
proportion := float32(current) / float32(total)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.progressLocker.Lock()
|
|
|
|
|
|
this.progressPercent = int(proportion * 100)
|
|
|
|
|
|
title := this.progressBarTitle
|
|
|
|
|
|
if this.progressBarShow {
|
2022-05-15 03:05:31 +00:00
|
|
|
|
width := 50
|
|
|
|
|
|
pos := int(proportion * float32(width))
|
2022-07-23 01:38:04 +00:00
|
|
|
|
fmt.Printf(title+" %s%*s %6.2f%%\r", strings.Repeat("■", pos), width-pos, "", proportion*100)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
2022-08-02 23:37:54 +00:00
|
|
|
|
this.progressLocker.Unlock()
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func isFileExists(path string) bool {
|
|
|
|
|
|
stat, err := os.Stat(path)
|
|
|
|
|
|
return err == nil && stat.Mode().IsRegular()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func isDirExists(path string) bool {
|
|
|
|
|
|
stat, err := os.Stat(path)
|
|
|
|
|
|
return err == nil && stat.IsDir()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================== 加解密相关 ==============================
|
|
|
|
|
|
|
2023-06-02 12:02:54 +00:00
|
|
|
|
func AesDecrypt(seq uint64, crypted []byte, encInfo *EncryptInfo) ([]byte, error) {
|
2023-05-15 01:26:38 +00:00
|
|
|
|
block, err := aes.NewCipher(encInfo.Key)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2023-05-15 01:26:38 +00:00
|
|
|
|
iv := encInfo.Iv
|
|
|
|
|
|
if len(iv) == 0 {
|
2023-06-02 12:02:54 +00:00
|
|
|
|
if encInfo.Method == EncryptMethod_AES128 {
|
|
|
|
|
|
iv = make([]byte, 16)
|
|
|
|
|
|
binary.BigEndian.PutUint64(iv[8:], seq)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return nil, errors.New("AesDecrypt method not support " + strconv.Quote(encInfo.Method))
|
2023-05-15 01:26:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
blockMode := cipher.NewCBCDecrypter(block, iv)
|
2022-05-15 03:05:31 +00:00
|
|
|
|
origData := make([]byte, len(crypted))
|
|
|
|
|
|
blockMode.CryptBlocks(origData, crypted)
|
2023-06-02 12:02:54 +00:00
|
|
|
|
length := len(origData)
|
|
|
|
|
|
unpadding := int(origData[length-1])
|
|
|
|
|
|
if length-unpadding < 0 {
|
|
|
|
|
|
return nil, fmt.Errorf(`invalid length of unpadding %v - %v`, length, unpadding)
|
|
|
|
|
|
}
|
|
|
|
|
|
return origData[:(length - unpadding)], nil
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getFileSha256(targetFile string) (v string) {
|
|
|
|
|
|
fin, err := os.Open(targetFile)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
defer fin.Close()
|
|
|
|
|
|
|
|
|
|
|
|
sha256Obj := sha256.New()
|
|
|
|
|
|
_, err = io.Copy(sha256Obj, fin)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
tmp := sha256Obj.Sum(nil)
|
|
|
|
|
|
return hex.EncodeToString(tmp[:])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-23 01:38:04 +00:00
|
|
|
|
func (this *downloadEnv) sniffM3u8(urlS string) (afterUrl string, content []byte, errMsg string) {
|
2022-06-20 13:34:52 +00:00
|
|
|
|
for idx := 0; idx < 5; idx++ {
|
2022-07-23 01:38:04 +00:00
|
|
|
|
var err error
|
2022-06-20 13:34:52 +00:00
|
|
|
|
content, err = this.doGetRequest(urlS)
|
|
|
|
|
|
if err != nil {
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return "", nil, err.Error()
|
2022-06-20 13:34:52 +00:00
|
|
|
|
}
|
2022-06-28 12:46:18 +00:00
|
|
|
|
if UrlHasSuffix(urlS, ".m3u8") {
|
2022-06-20 13:34:52 +00:00
|
|
|
|
// 看这个是不是嵌套的m3u8
|
|
|
|
|
|
var m3u8Url string
|
|
|
|
|
|
containsTs := false
|
2022-07-23 01:53:12 +00:00
|
|
|
|
for _, line := range splitLineWithTrimSpace(string(content)) {
|
|
|
|
|
|
if strings.HasPrefix(line, "#") {
|
2022-06-28 12:46:18 +00:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if UrlHasSuffix(line, ".m3u8") {
|
2022-07-23 01:53:12 +00:00
|
|
|
|
m3u8Url = line
|
2022-06-20 13:34:52 +00:00
|
|
|
|
break
|
|
|
|
|
|
}
|
2022-06-28 12:46:18 +00:00
|
|
|
|
if UrlHasSuffix(line, ".ts") {
|
2022-06-20 13:34:52 +00:00
|
|
|
|
containsTs = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2022-09-29 07:20:20 +00:00
|
|
|
|
// Support fake png ts
|
|
|
|
|
|
if UrlHasSuffix(line, ".png") {
|
|
|
|
|
|
containsTs = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2022-06-20 13:34:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
if containsTs {
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return urlS, content, ""
|
2022-06-20 13:34:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
if m3u8Url == "" {
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return "", nil, "未发现m3u8资源_1"
|
2022-06-20 13:34:52 +00:00
|
|
|
|
}
|
2022-07-20 16:08:58 +00:00
|
|
|
|
urlS, errMsg = resolveRefUrl(urlS, m3u8Url)
|
|
|
|
|
|
if errMsg != "" {
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return "", nil, errMsg
|
2022-06-20 13:34:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
groups := regexp.MustCompile(`http[s]://[a-zA-Z0-9/\\.%_-]+.m3u8`).FindSubmatch(content)
|
|
|
|
|
|
if len(groups) == 0 {
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return "", nil, "未发现m3u8资源_2"
|
2022-06-20 13:34:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
urlS = string(groups[0])
|
2022-06-12 14:43:20 +00:00
|
|
|
|
}
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return "", nil, "未发现m3u8资源_3"
|
2022-06-12 14:43:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-20 16:08:58 +00:00
|
|
|
|
func resolveRefUrl(baseUrl string, extUrl string) (after string, errMsg string) {
|
|
|
|
|
|
urlObj, err := url.Parse(baseUrl)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err.Error()
|
|
|
|
|
|
}
|
|
|
|
|
|
lineObj, err := url.Parse(extUrl)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err.Error()
|
|
|
|
|
|
}
|
|
|
|
|
|
return urlObj.ResolveReference(lineObj).String(), ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-28 12:46:18 +00:00
|
|
|
|
func UrlHasSuffix(urlS string, suff string) bool {
|
|
|
|
|
|
urlObj, err := url.Parse(urlS)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return strings.HasSuffix(strings.ToLower(urlObj.Path), suff)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-12 14:43:20 +00:00
|
|
|
|
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
|
2022-08-09 00:05:32 +00:00
|
|
|
|
resp, err := this.nowClient.Do(req)
|
2022-06-12 14:43:20 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2022-05-21 12:26:17 +00:00
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
content, err := io.ReadAll(resp.Body)
|
|
|
|
|
|
if err != nil {
|
2022-06-12 14:43:20 +00:00
|
|
|
|
return nil, err
|
2022-05-21 12:26:17 +00:00
|
|
|
|
}
|
2022-07-20 16:08:58 +00:00
|
|
|
|
if resp.StatusCode != 200 {
|
2022-07-23 01:38:04 +00:00
|
|
|
|
return content, errors.New("resp.Status: " + resp.Status + " " + urlS)
|
2022-07-20 16:08:58 +00:00
|
|
|
|
}
|
2022-06-12 14:43:20 +00:00
|
|
|
|
return content, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (this *downloadEnv) GetIsCancel() bool {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-this.ctx.Done():
|
|
|
|
|
|
return true
|
|
|
|
|
|
default:
|
|
|
|
|
|
return false
|
2022-05-21 12:26:17 +00:00
|
|
|
|
}
|
2022-05-15 03:05:31 +00:00
|
|
|
|
}
|
2022-10-04 09:02:24 +00:00
|
|
|
|
|
2022-10-06 01:52:20 +00:00
|
|
|
|
func GetFileNameFromUrl(u string) string {
|
|
|
|
|
|
urlObj, err := url.Parse(u)
|
|
|
|
|
|
if err != nil || urlObj.Scheme == "" {
|
|
|
|
|
|
return "all"
|
2022-10-04 09:02:24 +00:00
|
|
|
|
}
|
2022-10-06 01:52:20 +00:00
|
|
|
|
name := path.Base(urlObj.Path)
|
2022-10-06 01:57:34 +00:00
|
|
|
|
if name == "" {
|
|
|
|
|
|
return "all"
|
|
|
|
|
|
}
|
2022-10-06 01:52:20 +00:00
|
|
|
|
ext := path.Ext(name)
|
|
|
|
|
|
if len(name) > len(ext) {
|
|
|
|
|
|
return strings.TrimSuffix(name, ext)
|
2022-10-04 09:02:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
return name
|
|
|
|
|
|
}
|