Skip to content

Commit 805d72b

Browse files
committed
支持golang代码多实例调用
1 parent 617ad26 commit 805d72b

18 files changed

+1205
-748
lines changed

api.go

Lines changed: 278 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,113 +2,321 @@ package m3u8d
22

33
import (
44
"context"
5+
"crypto/tls"
56
"io/ioutil"
7+
"net/http"
8+
"net/url"
9+
"os"
610
"path/filepath"
7-
"sort"
11+
"strconv"
812
"strings"
9-
"sync"
13+
"sync/atomic"
14+
"time"
1015
)
1116

12-
var gMergeStatus SpeedStatus
13-
var gMergeCancel context.CancelFunc
14-
var gMergeCancelLocker sync.Mutex
17+
func (this *DownloadEnv) StartDownload(req StartDownload_Req) (errMsg string) {
18+
this.status.Locker.Lock()
19+
defer this.status.Locker.Unlock()
1520

16-
type MergeTsDir_Resp struct {
17-
ErrMsg string
18-
IsCancel bool
21+
if this.status.IsRunning {
22+
errMsg = "正在下载"
23+
return errMsg
24+
}
25+
26+
var proxyUrlObj *url.URL
27+
req.SetProxy, proxyUrlObj, errMsg = ParseProxyFormat(req.SetProxy)
28+
if errMsg != "" {
29+
return errMsg
30+
}
31+
this.setupClient(req, proxyUrlObj)
32+
errMsg = this.prepareReqAndHeader(&req)
33+
if errMsg != "" {
34+
return errMsg
35+
}
36+
37+
this.status.clearStatusNoLock()
38+
39+
this.status.progressBarShow = req.ProgressBarShow
40+
this.ctx, this.cancelFn = context.WithCancel(context.Background())
41+
this.status.IsRunning = true
42+
go func() {
43+
this.runDownload(req)
44+
this.status.SetProgressBarTitle("下载进度")
45+
this.status.DrawProgressBar(1, 0)
46+
47+
this.status.Locker.Lock()
48+
this.cancelFn()
49+
this.status.IsRunning = false
50+
this.status.Locker.Unlock()
51+
}()
52+
return ""
1953
}
2054

21-
var gMergeIsRunning bool
22-
var gMergeIsRunningLocker sync.Mutex
55+
func (this *DownloadEnv) GetStatus() (resp GetStatus_Resp) {
56+
var sleepTh int32
2357

24-
func beginMerge() bool {
25-
gMergeIsRunningLocker.Lock()
26-
defer gMergeIsRunningLocker.Unlock()
58+
sleepTh = atomic.LoadInt32(&this.sleepTh)
59+
resp.Percent = this.status.GetPercent()
60+
resp.Title = this.status.GetTitle()
61+
if resp.Title == "" {
62+
resp.Title = "正在下载"
63+
}
64+
{
65+
this.status.Locker.Lock()
66+
resp.IsDownloading = this.status.IsRunning
67+
resp.ErrMsg = this.status.errMsg
68+
resp.IsSkipped = this.status.isSkipped
69+
resp.SaveFileTo = this.status.saveFileTo
70+
this.status.Locker.Unlock()
71+
}
72+
73+
var speed SpeedInfo
74+
if resp.IsDownloading {
75+
speed = this.status.SpeedRecent5sGetAndUpdate()
76+
}
77+
resp.StatusBar = speed.ToString()
78+
if sleepTh > 0 {
79+
if resp.StatusBar != "" {
80+
resp.StatusBar += ", "
81+
}
82+
resp.StatusBar += "有 " + strconv.Itoa(int(sleepTh)) + "个线程正在休眠."
83+
}
2784

28-
if gMergeIsRunning != false {
29-
return false
85+
if resp.ErrMsg != "" {
86+
resp.IsCancel = this.GetIsCancel()
3087
}
31-
gMergeIsRunning = true
32-
return true
88+
return resp
3389
}
3490

35-
func MergeTsDir(InputTsDir string, OutputMp4Name string) (resp MergeTsDir_Resp) {
36-
if !beginMerge() {
37-
return resp
91+
func (this *DownloadEnv) WaitDownloadFinish() GetStatus_Resp {
92+
for {
93+
status := this.GetStatus()
94+
if status.IsDownloading {
95+
time.Sleep(time.Millisecond * 100)
96+
continue
97+
}
98+
return status
3899
}
100+
}
39101

40-
defer func() {
41-
gMergeIsRunningLocker.Lock()
42-
gMergeIsRunning = false
43-
gMergeIsRunningLocker.Unlock()
44-
}()
102+
func (this *DownloadEnv) setErrMsg(errMsg string) {
103+
this.status.Locker.Lock()
104+
this.status.errMsg = errMsg
105+
this.status.Locker.Unlock()
106+
}
45107

46-
fList, err := ioutil.ReadDir(InputTsDir)
108+
func (this *DownloadEnv) setSaveFileTo(to string, isSkipped bool) {
109+
this.status.Locker.Lock()
110+
this.status.saveFileTo = to
111+
this.status.isSkipped = isSkipped
112+
this.status.Locker.Unlock()
113+
}
114+
115+
func (this *DownloadEnv) runDownload(req StartDownload_Req) {
116+
this.status.SetProgressBarTitle("[1/6]嗅探m3u8")
117+
var m3u8Body []byte
118+
var errMsg string
119+
req.M3u8Url, m3u8Body, errMsg = this.sniffM3u8(req.M3u8Url)
120+
if errMsg != "" {
121+
this.setErrMsg("sniffM3u8: " + errMsg)
122+
return
123+
}
124+
videoId, err := req.getVideoId()
47125
if err != nil {
48-
resp.ErrMsg = "读取目录失败 " + err.Error()
126+
this.setErrMsg("getVideoId: " + err.Error())
49127
return
50128
}
51-
var tsFileList []string
52-
for _, f := range fList {
53-
if f.Mode().IsRegular() && strings.HasSuffix(strings.ToLower(f.Name()), ".ts") {
54-
tsFileList = append(tsFileList, filepath.Join(InputTsDir, f.Name()))
129+
130+
if req.SkipCacheCheck == false {
131+
var info *DbVideoInfo
132+
info, err = cacheRead(req.SaveDir, videoId)
133+
if err != nil {
134+
this.setErrMsg("cacheRead: " + err.Error())
135+
return
136+
}
137+
if info != nil {
138+
this.status.SetProgressBarTitle("[2/6]检查是否已下载")
139+
latestNameFullPath, found := info.SearchVideoInDir(req.SaveDir)
140+
if found {
141+
this.setSaveFileTo(latestNameFullPath, true)
142+
return
143+
}
55144
}
56145
}
57-
if len(tsFileList) == 0 {
58-
resp.ErrMsg = "目录下不存在ts文件: " + InputTsDir
146+
if !strings.HasPrefix(req.M3u8Url, "http") || req.M3u8Url == "" {
147+
this.setErrMsg("M3u8Url not valid " + strconv.Quote(req.M3u8Url))
59148
return
60149
}
61-
sort.Strings(tsFileList) // 按照字典顺序排序
62-
if OutputMp4Name == "" {
63-
OutputMp4Name = filepath.Join(InputTsDir, "all.mp4")
64-
} else if !filepath.IsAbs(OutputMp4Name) {
65-
OutputMp4Name = filepath.Join(InputTsDir, OutputMp4Name)
150+
downloadDir := filepath.Join(req.SaveDir, "downloading", videoId)
151+
if !isDirExists(downloadDir) {
152+
err := os.MkdirAll(downloadDir, os.ModePerm)
153+
if err != nil {
154+
this.setErrMsg("os.MkdirAll error: " + err.Error())
155+
return
156+
}
157+
}
158+
159+
downloadingFilePath := filepath.Join(downloadDir, "downloading.txt")
160+
if !isFileExists(downloadingFilePath) {
161+
err = ioutil.WriteFile(downloadingFilePath, []byte(req.M3u8Url), 0666)
162+
if err != nil {
163+
this.setErrMsg("os.WriteUrl error: " + err.Error())
164+
return
165+
}
66166
}
67-
ctx, cancelFn := context.WithCancel(context.Background())
68-
defer cancelFn()
69167

70-
gMergeCancelLocker.Lock()
71-
if gMergeCancel != nil {
72-
gMergeCancel()
168+
beginSeq := parseBeginSeq(m3u8Body)
169+
// 获取m3u8地址的内容体
170+
encInfo, err := this.getEncryptInfo(req.M3u8Url, string(m3u8Body))
171+
if err != nil {
172+
this.setErrMsg("getEncryptInfo: " + err.Error())
173+
return
174+
}
175+
this.status.SetProgressBarTitle("[3/6]获取ts列表")
176+
tsList, errMsg := getTsList(beginSeq, req.M3u8Url, string(m3u8Body))
177+
if errMsg != "" {
178+
this.setErrMsg("获取ts列表错误: " + errMsg)
179+
return
73180
}
74-
gMergeCancel = cancelFn
75-
gMergeCancelLocker.Unlock()
181+
if len(tsList) <= req.SkipTsCountFromHead {
182+
this.setErrMsg("需要下载的文件为空")
183+
return
184+
}
185+
tsList = tsList[req.SkipTsCountFromHead:]
186+
// 下载ts
187+
this.status.SetProgressBarTitle("[4/6]下载ts")
188+
this.status.SpeedResetBytes()
189+
err = this.downloader(tsList, downloadDir, encInfo, req.ThreadCount)
190+
this.status.SpeedResetBytes()
191+
if err != nil {
192+
this.setErrMsg("下载ts文件错误: " + err.Error())
193+
return
194+
}
195+
this.status.DrawProgressBar(1, 1)
196+
var tsFileList []string
197+
for _, one := range tsList {
198+
tsFileList = append(tsFileList, filepath.Join(downloadDir, one.Name))
199+
}
200+
var tmpOutputName string
201+
tmpOutputName = filepath.Join(downloadDir, "all.merge.mp4")
76202

203+
this.status.SetProgressBarTitle("[5/6]合并ts为mp4")
77204
err = MergeTsFileListToSingleMp4(MergeTsFileListToSingleMp4_Req{
78205
TsFileList: tsFileList,
79-
OutputMp4: OutputMp4Name,
80-
Ctx: ctx,
81-
Status: &gMergeStatus,
206+
OutputMp4: tmpOutputName,
207+
Ctx: this.ctx,
208+
Status: &this.status,
82209
})
210+
this.status.SpeedResetBytes()
83211
if err != nil {
84-
resp.ErrMsg = "合并错误: " + err.Error()
85-
resp.IsCancel = isContextCancel(ctx)
86-
return resp
212+
this.setErrMsg("合并错误: " + err.Error())
213+
return
87214
}
88-
return resp
215+
var contentHash string
216+
if req.SkipCacheCheck == false {
217+
this.status.SetProgressBarTitle("[6/6]计算文件hash")
218+
contentHash = getFileSha256(tmpOutputName)
219+
if contentHash == "" {
220+
this.setErrMsg("无法计算摘要信息: " + tmpOutputName)
221+
return
222+
}
223+
}
224+
var name string
225+
for idx := 0; ; idx++ {
226+
idxS := strconv.Itoa(idx)
227+
if len(idxS) < 4 {
228+
idxS = strings.Repeat("0", 4-len(idxS)) + idxS
229+
}
230+
idxS = "_" + idxS
231+
if idx == 0 {
232+
name = filepath.Join(req.SaveDir, req.FileName+".mp4")
233+
} else {
234+
name = filepath.Join(req.SaveDir, req.FileName+idxS+".mp4")
235+
}
236+
if !isFileExists(name) {
237+
this.setSaveFileTo(name, false)
238+
break
239+
}
240+
if idx > 10000 { // 超过1万就不找了
241+
this.setErrMsg("自动寻找文件名失败")
242+
return
243+
}
244+
}
245+
err = os.Rename(tmpOutputName, name)
246+
if err != nil {
247+
this.setErrMsg("重命名失败: " + err.Error())
248+
return
249+
}
250+
if req.SkipCacheCheck == false {
251+
err = cacheWrite(req.SaveDir, videoId, req, name, contentHash)
252+
if err != nil {
253+
this.setErrMsg("cacheWrite: " + err.Error())
254+
return
255+
}
256+
}
257+
if req.SkipRemoveTs == false {
258+
err = os.RemoveAll(downloadDir)
259+
if err != nil {
260+
this.setErrMsg("删除下载目录失败: " + err.Error())
261+
return
262+
}
263+
// 如果downloading目录为空,就删除掉,否则忽略
264+
_ = os.Remove(filepath.Join(req.SaveDir, "downloading"))
265+
}
266+
this.setSaveFileTo(name, false)
267+
return
89268
}
90269

91-
type MergeGetProgressPercent_Resp struct {
92-
Percent int
93-
SpeedText string
94-
IsRunning bool
95-
}
270+
func (this *DownloadEnv) setupClient(req StartDownload_Req, proxyUrlObj *url.URL) {
271+
if this.nowClient == nil {
272+
this.nowClient = &http.Client{}
273+
}
274+
//关闭以前的空闲链接
275+
this.nowClient.CloseIdleConnections()
276+
this.nowClient.Timeout = time.Second * 20
96277

97-
func MergeGetProgressPercent() (resp MergeGetProgressPercent_Resp) {
98-
gMergeIsRunningLocker.Lock()
99-
resp.IsRunning = gMergeIsRunning
100-
gMergeIsRunningLocker.Unlock()
101-
if resp.IsRunning {
102-
resp.Percent = gMergeStatus.GetPercent()
103-
resp.SpeedText = gMergeStatus.SpeedRecent5sGetAndUpdate()
278+
if this.nowClient.Transport == nil {
279+
this.nowClient.Transport = &http.Transport{
280+
TLSClientConfig: &tls.Config{
281+
InsecureSkipVerify: req.Insecure,
282+
},
283+
Proxy: http.ProxyURL(proxyUrlObj),
284+
}
285+
} else {
286+
transport := this.nowClient.Transport.(*http.Transport)
287+
transport.TLSClientConfig.InsecureSkipVerify = req.Insecure
288+
transport.Proxy = http.ProxyURL(proxyUrlObj)
104289
}
105-
return resp
106290
}
107291

108-
func MergeStop() {
109-
gMergeCancelLocker.Lock()
110-
if gMergeCancel != nil {
111-
gMergeCancel()
292+
func (this *DownloadEnv) prepareReqAndHeader(req *StartDownload_Req) (errMsg string) {
293+
if req.SaveDir == "" {
294+
var err error
295+
req.SaveDir, err = os.Getwd()
296+
if err != nil {
297+
return "os.Getwd error: " + err.Error()
298+
}
299+
}
300+
if req.FileName == "" {
301+
req.FileName = GetFileNameFromUrl(req.M3u8Url)
302+
}
303+
if req.SkipTsCountFromHead < 0 {
304+
req.SkipTsCountFromHead = 0
305+
}
306+
host, err := getHost(req.M3u8Url)
307+
if err != nil {
308+
return "getHost0: " + err.Error()
309+
}
310+
this.header = http.Header{
311+
"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"},
312+
"Connection": []string{"keep-alive"},
313+
"Accept": []string{"*/*"},
314+
"Accept-Encoding": []string{"*"},
315+
"Accept-Language": []string{"zh-CN,zh;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5"},
316+
"Referer": []string{host},
317+
}
318+
for key, valueList := range req.HeaderMap {
319+
this.header[key] = valueList
112320
}
113-
gMergeCancelLocker.Unlock()
321+
return ""
114322
}

0 commit comments

Comments
 (0)