downloader.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. package downloader
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/allanpk716/ChineseSubFinder/internal/ifaces"
  6. markSystem "github.com/allanpk716/ChineseSubFinder/internal/logic/mark_system"
  7. "github.com/allanpk716/ChineseSubFinder/internal/logic/series_helper"
  8. subSupplier "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier"
  9. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/xunlei"
  10. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_timeline_fixer"
  11. "github.com/allanpk716/ChineseSubFinder/internal/logic/task_queue"
  12. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
  13. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  14. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  15. subCommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
  16. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
  17. "github.com/allanpk716/ChineseSubFinder/internal/types/common"
  18. taskQueue2 "github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
  19. "github.com/sirupsen/logrus"
  20. "golang.org/x/net/context"
  21. "path/filepath"
  22. "sync"
  23. "time"
  24. )
  25. // Downloader 实例化一次用一次,不要反复的使用,很多临时标志位需要清理。
  26. type Downloader struct {
  27. settings *settings.Settings
  28. log *logrus.Logger
  29. ctx context.Context
  30. cancel context.CancelFunc
  31. subSupplierHub *subSupplier.SubSupplierHub // 字幕提供源的集合,这个需要定时进行扫描,这些字幕源是否有效,以及下载验证码信息
  32. mk *markSystem.MarkingSystem // MarkingSystem,字幕的评价系统
  33. subFormatter ifaces.ISubFormatter // 字幕格式化命名的实现
  34. subNameFormatter subCommon.FormatterName // 从 inSubFormatter 推断出来
  35. subTimelineFixerHelperEx *sub_timeline_fixer.SubTimelineFixerHelperEx // 字幕时间轴校正
  36. downloaderLock sync.Mutex // 取消执行 task control 的 Lock
  37. downloadQueue *task_queue.TaskQueue // 需要下载的视频的队列
  38. }
  39. func NewDownloader(inSubFormatter ifaces.ISubFormatter, _settings *settings.Settings, log *logrus.Logger, downloadQueue *task_queue.TaskQueue) *Downloader {
  40. var downloader Downloader
  41. downloader.subFormatter = inSubFormatter
  42. downloader.log = log
  43. // 参入设置信息
  44. downloader.settings = _settings
  45. // 检测是否某些参数超出范围
  46. downloader.settings.Check()
  47. // 这里就不单独弄一个 settings.SubNameFormatter 字段来传递值了,因为 inSubFormatter 就已经知道是什么 formatter 了
  48. downloader.subNameFormatter = subCommon.FormatterName(downloader.subFormatter.GetFormatterFormatterName())
  49. var sitesSequence = make([]string, 0)
  50. // TODO 这里写固定了抉择字幕的顺序
  51. sitesSequence = append(sitesSequence, common.SubSiteZiMuKu)
  52. sitesSequence = append(sitesSequence, common.SubSiteSubHd)
  53. sitesSequence = append(sitesSequence, common.SubSiteShooter)
  54. sitesSequence = append(sitesSequence, common.SubSiteXunLei)
  55. downloader.mk = markSystem.NewMarkingSystem(sitesSequence, downloader.settings.AdvancedSettings.SubTypePriority)
  56. // 初始化,字幕校正的实例
  57. downloader.subTimelineFixerHelperEx = sub_timeline_fixer.NewSubTimelineFixerHelperEx(*downloader.settings.TimelineFixerSettings)
  58. if downloader.settings.AdvancedSettings.FixTimeLine == true {
  59. downloader.subTimelineFixerHelperEx.Check()
  60. }
  61. // 任务队列
  62. downloader.downloadQueue = downloadQueue
  63. // 单个任务的超时设置
  64. downloader.ctx, downloader.cancel = context.WithTimeout(context.Background(), time.Duration(downloader.settings.AdvancedSettings.TaskQueue.OneJobTimeOut)*time.Second)
  65. return &downloader
  66. }
  67. func (d *Downloader) SupplierCheck() {
  68. defer func() {
  69. if p := recover(); p != nil {
  70. d.log.Errorln("Downloader.SupplierCheck() panic")
  71. }
  72. d.downloaderLock.Unlock()
  73. d.log.Infoln("Download.SupplierCheck() End")
  74. }()
  75. d.downloaderLock.Lock()
  76. d.log.Infoln("Download.SupplierCheck() Start ...")
  77. // 创建一个 chan 用于任务的中断和超时
  78. done := make(chan interface{}, 1)
  79. // 接收内部任务的 panic
  80. panicChan := make(chan interface{}, 1)
  81. go func() {
  82. if p := recover(); p != nil {
  83. panicChan <- p
  84. }
  85. // 下载前的初始化
  86. //d.log.Infoln("PreDownloadProcess.Init().Check().Wait()...")
  87. //preDownloadProcess := pre_download_process.NewPreDownloadProcess(d.log, d.settings)
  88. //err := preDownloadProcess.Init().Check().Wait()
  89. //if err != nil {
  90. // done <- errors.New(fmt.Sprintf("NewPreDownloadProcess Error: %v", err))
  91. //} else {
  92. // // 更新 SubSupplierHub 实例
  93. // d.downloaderLock.Lock()
  94. // d.subSupplierHub = preDownloadProcess.SubSupplierHub
  95. // d.downloaderLock.Unlock()
  96. //
  97. // done <- nil
  98. //}
  99. subSupplierHub := subSupplier.NewSubSupplierHub(
  100. d.settings,
  101. d.log,
  102. xunlei.NewSupplier(d.settings, d.log),
  103. )
  104. d.subSupplierHub = subSupplierHub
  105. done <- nil
  106. }()
  107. select {
  108. case err := <-done:
  109. if err != nil {
  110. d.log.Errorln(err)
  111. }
  112. break
  113. case p := <-panicChan:
  114. // 遇到内部的 panic,向外抛出
  115. panic(p)
  116. case <-d.ctx.Done():
  117. {
  118. d.log.Errorln("cancel SupplierCheck")
  119. return
  120. }
  121. }
  122. }
  123. func (d *Downloader) QueueDownloader() {
  124. defer func() {
  125. if p := recover(); p != nil {
  126. d.log.Errorln("Downloader.QueueDownloader() panic")
  127. }
  128. d.downloaderLock.Unlock()
  129. d.log.Infoln("Download.QueueDownloader() End")
  130. }()
  131. d.downloaderLock.Lock()
  132. d.log.Infoln("Download.QueueDownloader() Start ...")
  133. var downloadCounter int64
  134. downloadCounter = 0
  135. // 从队列取数据出来
  136. bok, oneJob, err := d.downloadQueue.GetOneWaitingJob()
  137. if err != nil {
  138. d.log.Errorln("d.downloadQueue.GetOneWaitingJob()", err)
  139. return
  140. }
  141. if bok == false {
  142. d.log.Infoln("Download Queue Is Empty, Skip This Time")
  143. return
  144. }
  145. downloadCounter++
  146. // 创建一个 chan 用于任务的中断和超时
  147. done := make(chan interface{}, 1)
  148. // 接收内部任务的 panic
  149. panicChan := make(chan interface{}, 1)
  150. go func() {
  151. defer func() {
  152. if p := recover(); p != nil {
  153. panicChan <- p
  154. }
  155. // 没下载完毕一次,进行一次缓存和 Chrome 的清理
  156. err = my_folder.ClearRootTmpFolder()
  157. if err != nil {
  158. d.log.Error("ClearRootTmpFolder", err)
  159. }
  160. my_util.CloseChrome(d.log)
  161. }()
  162. if oneJob.VideoType == common.Movie {
  163. // 电影
  164. // 具体的下载逻辑 func()
  165. done <- d.movieDlFunc(d.ctx, oneJob, downloadCounter)
  166. } else if oneJob.VideoType == common.Series {
  167. // 连续剧
  168. // 具体的下载逻辑 func()
  169. done <- d.seriesDlFunc(d.ctx, oneJob, downloadCounter)
  170. } else {
  171. d.log.Errorln("oneJob.VideoType not support, oneJob.VideoType = ", oneJob.VideoType)
  172. done <- nil
  173. }
  174. }()
  175. select {
  176. case err := <-done:
  177. // 跳出 select,可以外层继续,不会阻塞在这里
  178. if err != nil {
  179. d.log.Errorln(err)
  180. }
  181. break
  182. case p := <-panicChan:
  183. // 遇到内部的 panic,向外抛出
  184. panic(p)
  185. case <-d.ctx.Done():
  186. {
  187. // 取消这个 context
  188. d.log.Warningln("cancel Downloader.QueueDownloader()")
  189. return
  190. }
  191. }
  192. }
  193. func (d *Downloader) Cancel() {
  194. if d == nil {
  195. return
  196. }
  197. d.cancel()
  198. d.log.Infoln("Downloader.Cancel()")
  199. }
  200. func (d *Downloader) movieDlFunc(ctx context.Context, job taskQueue2.OneJob, downloadIndex int64) error {
  201. nowSubSupplierHub := d.subSupplierHub
  202. if nowSubSupplierHub.Suppliers == nil || len(nowSubSupplierHub.Suppliers) < 1 {
  203. d.log.Infoln("Wait SupplierCheck Update *subSupplierHub, movieDlFunc Skip this time")
  204. return nil
  205. }
  206. // 字幕都下载缓存好了,需要抉择存哪一个,优先选择中文双语的,然后到中文
  207. organizeSubFiles, err := nowSubSupplierHub.DownloadSub4Movie(job.VideoFPath, downloadIndex)
  208. if err != nil {
  209. err = errors.New(fmt.Sprintf("subSupplierHub.DownloadSub4Movie: %v, %v", job.VideoFPath, err))
  210. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  211. return err
  212. }
  213. // 返回的两个值都是 nil 的时候,就是没有下载到字幕
  214. if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
  215. d.log.Infoln(task_queue.ErrNotSubFound.Error(), filepath.Base(job.VideoFPath))
  216. d.downloadQueue.AutoDetectUpdateJobStatus(job, task_queue.ErrNotSubFound)
  217. return nil
  218. }
  219. err = d.oneVideoSelectBestSub(job.VideoFPath, organizeSubFiles)
  220. if err != nil {
  221. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  222. return err
  223. }
  224. d.downloadQueue.AutoDetectUpdateJobStatus(job, nil)
  225. return nil
  226. }
  227. func (d *Downloader) seriesDlFunc(ctx context.Context, job taskQueue2.OneJob, downloadIndex int64) error {
  228. nowSubSupplierHub := d.subSupplierHub
  229. if nowSubSupplierHub.Suppliers == nil || len(nowSubSupplierHub.Suppliers) < 1 {
  230. d.log.Infoln("Wait SupplierCheck Update *subSupplierHub, movieDlFunc Skip this time")
  231. return nil
  232. }
  233. var err error
  234. // 这里拿到了这一部连续剧的所有的剧集信息,以及所有下载到的字幕信息
  235. seriesInfo, err := series_helper.ReadSeriesInfoFromDir(job.SeriesRootDirPath, false, d.settings.AdvancedSettings.ProxySettings)
  236. if err != nil {
  237. err = errors.New(fmt.Sprintf("seriesDlFunc.ReadSeriesInfoFromDir, Error: %v", err))
  238. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  239. return err
  240. }
  241. // 设置只有一集需要下载
  242. epsMap := make(map[int]int, 0)
  243. epsMap[job.Season] = job.Episode
  244. series_helper.SetTheSpecifiedEps2Download(seriesInfo, epsMap)
  245. // 下载好的字幕文件
  246. var organizeSubFiles map[string][]string
  247. // 下载的接口是统一的
  248. organizeSubFiles, err = nowSubSupplierHub.DownloadSub4Series(job.SeriesRootDirPath,
  249. seriesInfo,
  250. downloadIndex)
  251. if err != nil {
  252. err = errors.New(fmt.Sprintf("seriesDlFunc.DownloadSub4Series %v S%vE%v %v", filepath.Base(job.SeriesRootDirPath), job.Season, job.Episode, err))
  253. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  254. return err
  255. }
  256. // 是否下载到字幕了
  257. if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
  258. d.log.Infoln(task_queue.ErrNotSubFound.Error(), filepath.Base(job.VideoFPath), job.Season, job.Episode)
  259. d.downloadQueue.AutoDetectUpdateJobStatus(job, task_queue.ErrNotSubFound)
  260. return nil
  261. }
  262. var errSave2Local error
  263. save2LocalSubCount := 0
  264. // 只针对需要下载字幕的视频进行字幕的选择保存
  265. subVideoCount := 0
  266. for epsKey, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
  267. // 创建一个 chan 用于任务的中断和超时
  268. done := make(chan interface{}, 1)
  269. // 接收内部任务的 panic
  270. panicChan := make(chan interface{}, 1)
  271. go func() {
  272. if p := recover(); p != nil {
  273. panicChan <- p
  274. }
  275. // 匹配对应的 Eps 去处理
  276. done <- d.oneVideoSelectBestSub(episodeInfo.FileFullPath, organizeSubFiles[epsKey])
  277. }()
  278. select {
  279. case errInterface := <-done:
  280. if errInterface != nil {
  281. errSave2Local = errInterface.(error)
  282. d.log.Errorln(errInterface.(error))
  283. } else {
  284. save2LocalSubCount++
  285. }
  286. break
  287. case p := <-panicChan:
  288. // 遇到内部的 panic,向外抛出
  289. panic(p)
  290. case <-ctx.Done():
  291. {
  292. err = errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub, %v S%dE%d", seriesInfo.Name, episodeInfo.Season, episodeInfo.Episode))
  293. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  294. return err
  295. }
  296. }
  297. subVideoCount++
  298. }
  299. // 这里会拿到一份季度字幕的列表比如,Key 是 S1E0 S2E0 S3E0,value 是新的存储位置
  300. fullSeasonSubDict := d.saveFullSeasonSub(seriesInfo, organizeSubFiles)
  301. // TODO 季度的字幕包,应该优先于零散的字幕吧,暂定就这样了,注意是全部都替换
  302. // 需要与有下载需求的季交叉
  303. for _, episodeInfo := range seriesInfo.EpList {
  304. // 创建一个 chan 用于任务的中断和超时
  305. done := make(chan interface{}, 1)
  306. // 接收内部任务的 panic
  307. panicChan := make(chan interface{}, 1)
  308. _, ok := seriesInfo.NeedDlSeasonDict[episodeInfo.Season]
  309. if ok == false {
  310. continue
  311. }
  312. go func() {
  313. if p := recover(); p != nil {
  314. panicChan <- p
  315. }
  316. // 匹配对应的 Eps 去处理
  317. seasonEpsKey := my_util.GetEpisodeKeyName(episodeInfo.Season, episodeInfo.Episode)
  318. done <- d.oneVideoSelectBestSub(episodeInfo.FileFullPath, fullSeasonSubDict[seasonEpsKey])
  319. }()
  320. select {
  321. case errInterface := <-done:
  322. if errInterface != nil {
  323. errSave2Local = errInterface.(error)
  324. d.log.Errorln(errInterface.(error))
  325. } else {
  326. save2LocalSubCount++
  327. }
  328. break
  329. case p := <-panicChan:
  330. // 遇到内部的 panic,向外抛出
  331. panic(p)
  332. case <-ctx.Done():
  333. {
  334. err = errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub, %v S%dE%d", seriesInfo.Name, episodeInfo.Season, episodeInfo.Episode))
  335. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  336. return err
  337. }
  338. }
  339. }
  340. // 是否清理全季的缓存字幕文件夹
  341. if d.settings.AdvancedSettings.SaveFullSeasonTmpSubtitles == false {
  342. err = sub_helper.DeleteOneSeasonSubCacheFolder(seriesInfo.DirPath)
  343. if err != nil {
  344. d.log.Errorln("seriesDlFunc.DeleteOneSeasonSubCacheFolder", err)
  345. }
  346. }
  347. if save2LocalSubCount < 1 {
  348. // 下载的字幕都没有一个能够写入到本地的,那么就有问题了
  349. d.downloadQueue.AutoDetectUpdateJobStatus(job, errSave2Local)
  350. return errSave2Local
  351. }
  352. // 哪怕有一个写入到本地成功了,也无需对本次任务报错
  353. d.downloadQueue.AutoDetectUpdateJobStatus(job, nil)
  354. return nil
  355. }