downloader.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. package downloader
  2. import (
  3. "errors"
  4. "fmt"
  5. "path/filepath"
  6. "sync"
  7. "github.com/allanpk716/ChineseSubFinder/internal/dao"
  8. "github.com/allanpk716/ChineseSubFinder/internal/ifaces"
  9. embyHelper "github.com/allanpk716/ChineseSubFinder/internal/logic/emby_helper"
  10. "github.com/allanpk716/ChineseSubFinder/internal/logic/file_downloader"
  11. markSystem "github.com/allanpk716/ChineseSubFinder/internal/logic/mark_system"
  12. "github.com/allanpk716/ChineseSubFinder/internal/logic/pre_download_process"
  13. "github.com/allanpk716/ChineseSubFinder/internal/logic/series_helper"
  14. subSupplier "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier"
  15. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/assrt"
  16. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_timeline_fixer"
  17. "github.com/allanpk716/ChineseSubFinder/internal/models"
  18. "github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
  19. "github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
  20. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
  21. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  22. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  23. subCommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
  24. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
  25. "github.com/allanpk716/ChineseSubFinder/internal/pkg/task_queue"
  26. "github.com/allanpk716/ChineseSubFinder/internal/types/common"
  27. taskQueue2 "github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
  28. "github.com/sirupsen/logrus"
  29. "golang.org/x/net/context"
  30. )
  31. // Downloader 实例化一次用一次,不要反复的使用,很多临时标志位需要清理。
  32. type Downloader struct {
  33. settings *settings.Settings
  34. log *logrus.Logger
  35. fileDownloader *file_downloader.FileDownloader
  36. ctx context.Context
  37. cancel context.CancelFunc
  38. subSupplierHub *subSupplier.SubSupplierHub // 字幕提供源的集合,这个需要定时进行扫描,这些字幕源是否有效,以及下载验证码信息
  39. mk *markSystem.MarkingSystem // MarkingSystem,字幕的评价系统
  40. subFormatter ifaces.ISubFormatter // 字幕格式化命名的实现
  41. subNameFormatter subCommon.FormatterName // 从 inSubFormatter 推断出来
  42. subTimelineFixerHelperEx *sub_timeline_fixer.SubTimelineFixerHelperEx // 字幕时间轴校正
  43. downloaderLock sync.Mutex // 取消执行 task control 的 Lock
  44. downloadQueue *task_queue.TaskQueue // 需要下载的视频的队列
  45. embyHelper *embyHelper.EmbyHelper // Emby 的实例
  46. cacheLocker sync.Mutex
  47. movieInfoMap map[string]MovieInfo // 给 Web 界面使用的,Key: VideoFPath
  48. seasonInfoMap map[string]SeasonInfo // 给 Web 界面使用的,Key: RootDirPath
  49. }
  50. func NewDownloader(inSubFormatter ifaces.ISubFormatter, fileDownloader *file_downloader.FileDownloader, downloadQueue *task_queue.TaskQueue) *Downloader {
  51. var downloader Downloader
  52. downloader.fileDownloader = fileDownloader
  53. downloader.subFormatter = inSubFormatter
  54. downloader.fileDownloader = fileDownloader
  55. downloader.log = fileDownloader.Log
  56. // 参入设置信息
  57. downloader.settings = fileDownloader.Settings
  58. // 检测是否某些参数超出范围
  59. downloader.settings.Check()
  60. // 这里就不单独弄一个 settings.SubNameFormatter 字段来传递值了,因为 inSubFormatter 就已经知道是什么 formatter 了
  61. downloader.subNameFormatter = subCommon.FormatterName(downloader.subFormatter.GetFormatterFormatterName())
  62. var sitesSequence = make([]string, 0)
  63. // TODO 这里写固定了抉择字幕的顺序
  64. sitesSequence = append(sitesSequence, common.SubSiteZiMuKu)
  65. sitesSequence = append(sitesSequence, common.SubSiteSubHd)
  66. sitesSequence = append(sitesSequence, common.SubSiteChineseSubFinder)
  67. sitesSequence = append(sitesSequence, common.SubSiteAssrt)
  68. sitesSequence = append(sitesSequence, common.SubSiteShooter)
  69. sitesSequence = append(sitesSequence, common.SubSiteXunLei)
  70. downloader.mk = markSystem.NewMarkingSystem(downloader.log, sitesSequence, downloader.settings.AdvancedSettings.SubTypePriority)
  71. // 初始化,字幕校正的实例
  72. downloader.subTimelineFixerHelperEx = sub_timeline_fixer.NewSubTimelineFixerHelperEx(downloader.log, *downloader.settings.TimelineFixerSettings)
  73. if downloader.settings.AdvancedSettings.FixTimeLine == true {
  74. downloader.subTimelineFixerHelperEx.Check()
  75. }
  76. // 任务队列
  77. downloader.downloadQueue = downloadQueue
  78. // 单个任务的超时设置
  79. downloader.ctx, downloader.cancel = context.WithCancel(context.Background())
  80. // 用于字幕下载后的刷新
  81. if downloader.settings.EmbySettings.Enable == true {
  82. downloader.embyHelper = embyHelper.NewEmbyHelper(downloader.log, downloader.settings)
  83. }
  84. downloader.movieInfoMap = make(map[string]MovieInfo)
  85. downloader.seasonInfoMap = make(map[string]SeasonInfo)
  86. err := downloader.loadVideoListCache()
  87. if err != nil {
  88. downloader.log.Errorln("loadVideoListCache error:", err)
  89. }
  90. return &downloader
  91. }
  92. // SupplierCheck 检查字幕源是否有效,会影响后续的字幕源是否参与下载
  93. func (d *Downloader) SupplierCheck() {
  94. defer func() {
  95. if p := recover(); p != nil {
  96. d.log.Errorln("Downloader.SupplierCheck() panic")
  97. my_util.PrintPanicStack(d.log)
  98. }
  99. d.downloaderLock.Unlock()
  100. d.log.Infoln("Download.SupplierCheck() End")
  101. }()
  102. d.downloaderLock.Lock()
  103. d.log.Infoln("Download.SupplierCheck() Start ...")
  104. //// 创建一个 chan 用于任务的中断和超时
  105. //done := make(chan interface{}, 1)
  106. //// 接收内部任务的 panic
  107. //panicChan := make(chan interface{}, 1)
  108. //
  109. //go func() {
  110. // defer func() {
  111. // if p := recover(); p != nil {
  112. // panicChan <- p
  113. // }
  114. //
  115. // close(done)
  116. // close(panicChan)
  117. // }()
  118. // 下载前的初始化
  119. d.log.Infoln("PreDownloadProcess.Init().Check().Wait()...")
  120. if d.settings.SpeedDevMode == true {
  121. // 这里是调试使用的,指定了只用一个字幕源
  122. subSupplierHub := subSupplier.NewSubSupplierHub(assrt.NewSupplier(d.fileDownloader))
  123. d.subSupplierHub = subSupplierHub
  124. } else {
  125. preDownloadProcess := pre_download_process.NewPreDownloadProcess(d.fileDownloader)
  126. err := preDownloadProcess.Init().Check().Wait()
  127. if err != nil {
  128. //done <- errors.New(fmt.Sprintf("NewPreDownloadProcess Error: %v", err))
  129. d.log.Errorln(errors.New(fmt.Sprintf("NewPreDownloadProcess Error: %v", err)))
  130. } else {
  131. // 更新 SubSupplierHub 实例
  132. d.subSupplierHub = preDownloadProcess.SubSupplierHub
  133. //done <- nil
  134. }
  135. }
  136. // done <- nil
  137. //}()
  138. //
  139. //select {
  140. //case err := <-done:
  141. // if err != nil {
  142. // d.log.Errorln(err)
  143. // }
  144. // break
  145. //case p := <-panicChan:
  146. // // 遇到内部的 panic,向外抛出
  147. // panic(p)
  148. //case <-d.ctx.Done():
  149. // {
  150. // d.log.Errorln("cancel SupplierCheck")
  151. // return
  152. // }
  153. //}
  154. }
  155. // QueueDownloader 从字幕队列中取一个视频的字幕下载任务出来,并且开始下载
  156. func (d *Downloader) QueueDownloader() {
  157. defer func() {
  158. if p := recover(); p != nil {
  159. d.log.Errorln("Downloader.QueueDownloader() panic")
  160. my_util.PrintPanicStack(d.log)
  161. }
  162. d.downloaderLock.Unlock()
  163. d.log.Debugln("Download.QueueDownloader() End")
  164. }()
  165. d.log.Debugln("Download.QueueDownloader() Try Start ...")
  166. d.downloaderLock.Lock()
  167. d.log.Debugln("Download.QueueDownloader() Start ...")
  168. var downloadCounter int64
  169. downloadCounter = 0
  170. // 移除查过三个月的 Done 任务
  171. d.downloadQueue.BeforeGetOneJob()
  172. // 从队列取数据出来,见《任务生命周期》
  173. bok, oneJob, err := d.downloadQueue.GetOneJob()
  174. if err != nil {
  175. d.log.Errorln("d.downloadQueue.GetOneWaitingJob()", err)
  176. return
  177. }
  178. if bok == false {
  179. d.log.Debugln("Download Queue Is Empty, Skip This Time")
  180. return
  181. }
  182. // --------------------------------------------------
  183. // 判断是否看过
  184. isPlayed := false
  185. if d.embyHelper != nil {
  186. // 在拿出来后,如果是有内部媒体服务器媒体 ID 的,那么就去查询是否已经观看过了
  187. isPlayed, err = d.embyHelper.IsVideoPlayed(oneJob.MediaServerInsideVideoID)
  188. if err != nil {
  189. d.log.Errorln("d.embyHelper.IsVideoPlayed()", oneJob.VideoFPath, err)
  190. return
  191. }
  192. }
  193. // 不管如何,只要是发现数据库中有 HTTP API 提交的信息,就认为是看过
  194. var videoPlayedInfos []models.ThirdPartSetVideoPlayedInfo
  195. dao.GetDb().Where("physical_video_file_full_path = ?", oneJob.VideoFPath).Find(&videoPlayedInfos)
  196. if len(videoPlayedInfos) > 0 {
  197. isPlayed = true
  198. }
  199. // --------------------------------------------------
  200. // 如果已经播放过 且 这个任务的优先级 > 3 ,不是很急的那种,说明是可以设置忽略继续下载的
  201. if isPlayed == true && oneJob.TaskPriority > task_queue.HighTaskPriorityLevel {
  202. // 播放过了,那么就标记 ignore
  203. oneJob.JobStatus = taskQueue2.Ignore
  204. bok, err = d.downloadQueue.Update(oneJob)
  205. if err != nil {
  206. d.log.Errorln("d.downloadQueue.Update()", err)
  207. return
  208. }
  209. if bok == false {
  210. d.log.Errorln("d.downloadQueue.Update() Failed")
  211. return
  212. }
  213. d.log.Infoln("Is Played, Ignore This Job")
  214. return
  215. }
  216. // 取出来后,需要标记为正在下载
  217. oneJob.JobStatus = taskQueue2.Downloading
  218. bok, err = d.downloadQueue.Update(oneJob)
  219. if err != nil {
  220. d.log.Errorln("d.downloadQueue.Update()", err)
  221. return
  222. }
  223. if bok == false {
  224. d.log.Errorln("d.downloadQueue.Update() Failed")
  225. return
  226. }
  227. // ------------------------------------------------------------------------
  228. // 开始标记,这个是单次扫描的开始,要注意格式,在日志的内部解析识别单个日志开头的时候需要特殊的格式
  229. d.log.Infoln("------------------------------------------")
  230. d.log.Infoln(log_helper.OnceSubsScanStart + "#" + oneJob.Id)
  231. // ------------------------------------------------------------------------
  232. defer func() {
  233. d.log.Infoln(log_helper.OnceSubsScanEnd)
  234. d.log.Infoln("------------------------------------------")
  235. }()
  236. downloadCounter++
  237. // 创建一个 chan 用于任务的中断和超时
  238. done := make(chan interface{}, 1)
  239. // 接收内部任务的 panic
  240. panicChan := make(chan interface{}, 1)
  241. go func() {
  242. defer func() {
  243. if p := recover(); p != nil {
  244. panicChan <- p
  245. }
  246. close(done)
  247. close(panicChan)
  248. // 没下载完毕一次,进行一次缓存和 Chrome 的清理
  249. err = my_folder.ClearRootTmpFolder()
  250. if err != nil {
  251. d.log.Error("ClearRootTmpFolder", err)
  252. }
  253. my_util.CloseChrome(d.log)
  254. }()
  255. if oneJob.VideoType == common.Movie {
  256. // 电影
  257. // 具体的下载逻辑 func()
  258. done <- d.movieDlFunc(d.ctx, oneJob, downloadCounter)
  259. } else if oneJob.VideoType == common.Series {
  260. // 连续剧
  261. // 具体的下载逻辑 func()
  262. done <- d.seriesDlFunc(d.ctx, oneJob, downloadCounter)
  263. } else {
  264. d.log.Errorln("oneJob.VideoType not support, oneJob.VideoType = ", oneJob.VideoType)
  265. done <- nil
  266. }
  267. }()
  268. select {
  269. case err := <-done:
  270. // 跳出 select,可以外层继续,不会阻塞在这里
  271. if err != nil {
  272. d.log.Errorln(err)
  273. }
  274. // 刷新视频的缓存结构
  275. d.UpdateInfo(oneJob)
  276. break
  277. case p := <-panicChan:
  278. // 遇到内部的 panic,向外抛出
  279. panic(p)
  280. case <-d.ctx.Done():
  281. {
  282. // 取消这个 context
  283. d.log.Warningln("cancel Downloader.QueueDownloader()")
  284. return
  285. }
  286. }
  287. }
  288. func (d *Downloader) Cancel() {
  289. if d == nil {
  290. return
  291. }
  292. d.cancel()
  293. d.log.Infoln("Downloader.Cancel()")
  294. }
  295. func (d *Downloader) movieDlFunc(ctx context.Context, job taskQueue2.OneJob, downloadIndex int64) error {
  296. nowSubSupplierHub := d.subSupplierHub
  297. if nowSubSupplierHub.Suppliers == nil || len(nowSubSupplierHub.Suppliers) < 1 {
  298. d.log.Infoln("Wait SupplierCheck Update *subSupplierHub, movieDlFunc Skip this time")
  299. return nil
  300. }
  301. // 字幕都下载缓存好了,需要抉择存哪一个,优先选择中文双语的,然后到中文
  302. organizeSubFiles, err := nowSubSupplierHub.DownloadSub4Movie(job.VideoFPath, downloadIndex)
  303. if err != nil {
  304. err = errors.New(fmt.Sprintf("subSupplierHub.DownloadSub4Movie: %v, %v", job.VideoFPath, err))
  305. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  306. return err
  307. }
  308. // 返回的两个值都是 nil 的时候,就是没有下载到字幕
  309. if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
  310. d.log.Infoln(task_queue.ErrNoSubFound.Error(), filepath.Base(job.VideoFPath))
  311. d.downloadQueue.AutoDetectUpdateJobStatus(job, task_queue.ErrNoSubFound)
  312. return nil
  313. }
  314. err = d.oneVideoSelectBestSub(job.VideoFPath, organizeSubFiles)
  315. if err != nil {
  316. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  317. return err
  318. }
  319. d.downloadQueue.AutoDetectUpdateJobStatus(job, nil)
  320. // TODO 刷新字幕,这里是 Emby 的,如果是其他的,需要再对接对应的媒体服务器
  321. if d.settings.EmbySettings.Enable == true && d.embyHelper != nil && job.MediaServerInsideVideoID != "" {
  322. d.log.Infoln("Refresh Emby Subtitle with id:", job.MediaServerInsideVideoID)
  323. err = d.embyHelper.EmbyApi.UpdateVideoSubList(job.MediaServerInsideVideoID)
  324. if err != nil {
  325. d.log.Errorln("UpdateVideoSubList", job.VideoFPath, job.MediaServerInsideVideoID, "Error:", err)
  326. return err
  327. }
  328. } else {
  329. if d.settings.EmbySettings.Enable == false {
  330. d.log.Infoln("UpdateVideoSubList", job.VideoFPath, "Skip, because Emby enable is false")
  331. } else if d.embyHelper == nil {
  332. d.log.Infoln("UpdateVideoSubList", job.VideoFPath, "Skip, because EmbyHelper is nil")
  333. } else if job.MediaServerInsideVideoID == "" {
  334. d.log.Infoln("UpdateVideoSubList", job.VideoFPath, "Skip, because MediaServerInsideVideoID is empty")
  335. }
  336. }
  337. return nil
  338. }
  339. func (d *Downloader) seriesDlFunc(ctx context.Context, job taskQueue2.OneJob, downloadIndex int64) error {
  340. nowSubSupplierHub := d.subSupplierHub
  341. if nowSubSupplierHub == nil || nowSubSupplierHub.Suppliers == nil || len(nowSubSupplierHub.Suppliers) < 1 {
  342. d.log.Infoln("Wait SupplierCheck Update *subSupplierHub, movieDlFunc Skip this time")
  343. return nil
  344. }
  345. var err error
  346. // 设置只有一集需要下载
  347. epsMap := make(map[int][]int, 0)
  348. epsMap[job.Season] = []int{job.Episode}
  349. if job.VideoType == common.Series && (job.SeriesRootDirPath == "" || job.Season <= 0 || job.Episode <= 0) {
  350. // 连续剧的时候需要额外提交信息
  351. epsVideoNfoInfo, err := decode.GetVideoNfoInfo4OneSeriesEpisode(job.VideoFPath)
  352. if err != nil {
  353. return err
  354. }
  355. seriesInfoDirPath := decode.GetSeriesDirRootFPath(job.VideoFPath)
  356. if seriesInfoDirPath == "" {
  357. err = errors.New(fmt.Sprintf("decode.GetSeriesDirRootFPath == Empty, %s", job.VideoFPath))
  358. return err
  359. }
  360. job.Season = epsVideoNfoInfo.Season
  361. job.Episode = epsVideoNfoInfo.Episode
  362. job.SeriesRootDirPath = seriesInfoDirPath
  363. // 如果进来的时候是 Season 和 Eps 是 -1, 那么就需要重新赋值那些集是需要下载的,否则后面会跳过
  364. epsMap = make(map[int][]int, 0)
  365. epsMap[job.Season] = []int{job.Episode}
  366. }
  367. // 这里拿到了这一部连续剧的所有的剧集信息,以及所有下载到的字幕信息
  368. seriesInfo, err := series_helper.ReadSeriesInfoFromDir(
  369. d.log, job.SeriesRootDirPath,
  370. d.settings.AdvancedSettings.TaskQueue.ExpirationTime,
  371. false,
  372. false,
  373. epsMap)
  374. if err != nil {
  375. err = errors.New(fmt.Sprintf("seriesDlFunc.ReadSeriesInfoFromDir, Error: %v", err))
  376. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  377. return err
  378. }
  379. // 下载好的字幕文件
  380. var organizeSubFiles map[string][]string
  381. // 下载的接口是统一的
  382. organizeSubFiles, err = nowSubSupplierHub.DownloadSub4Series(job.SeriesRootDirPath,
  383. seriesInfo,
  384. downloadIndex)
  385. if err != nil {
  386. err = errors.New(fmt.Sprintf("seriesDlFunc.DownloadSub4Series %v S%vE%v %v", filepath.Base(job.SeriesRootDirPath), job.Season, job.Episode, err))
  387. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  388. return err
  389. }
  390. // 是否下载到字幕了
  391. if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
  392. d.log.Infoln(task_queue.ErrNoSubFound.Error(), filepath.Base(job.VideoFPath), job.Season, job.Episode)
  393. d.downloadQueue.AutoDetectUpdateJobStatus(job, task_queue.ErrNoSubFound)
  394. return nil
  395. }
  396. var errSave2Local error
  397. save2LocalSubCount := 0
  398. // 只针对需要下载字幕的视频进行字幕的选择保存
  399. subVideoCount := 0
  400. for epsKey, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
  401. // 创建一个 chan 用于任务的中断和超时
  402. done := make(chan interface{}, 1)
  403. // 接收内部任务的 panic
  404. panicChan := make(chan interface{}, 1)
  405. go func() {
  406. defer func() {
  407. if p := recover(); p != nil {
  408. panicChan <- p
  409. }
  410. close(done)
  411. close(panicChan)
  412. }()
  413. // 匹配对应的 Eps 去处理
  414. done <- d.oneVideoSelectBestSub(episodeInfo.FileFullPath, organizeSubFiles[epsKey])
  415. }()
  416. select {
  417. case errInterface := <-done:
  418. if errInterface != nil {
  419. errSave2Local = errInterface.(error)
  420. d.log.Errorln(errInterface.(error))
  421. } else {
  422. save2LocalSubCount++
  423. }
  424. break
  425. case p := <-panicChan:
  426. // 遇到内部的 panic,向外抛出
  427. d.log.Errorln("seriesDlFunc.oneVideoSelectBestSub panicChan", p)
  428. break
  429. case <-ctx.Done():
  430. {
  431. err = errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub, %v S%dE%d", seriesInfo.Name, episodeInfo.Season, episodeInfo.Episode))
  432. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  433. return err
  434. }
  435. }
  436. subVideoCount++
  437. }
  438. // 这里会拿到一份季度字幕的列表比如,Key 是 S1E0 S2E0 S3E0,value 是新的存储位置
  439. fullSeasonSubDict := d.saveFullSeasonSub(seriesInfo, organizeSubFiles)
  440. // TODO 季度的字幕包,应该优先于零散的字幕吧,暂定就这样了,注意是全部都替换
  441. // 需要与有下载需求的季交叉
  442. for _, episodeInfo := range seriesInfo.EpList {
  443. _, ok := seriesInfo.NeedDlSeasonDict[episodeInfo.Season]
  444. if ok == false {
  445. continue
  446. }
  447. // 创建一个 chan 用于任务的中断和超时
  448. done := make(chan interface{}, 1)
  449. // 接收内部任务的 panic
  450. panicChan := make(chan interface{}, 1)
  451. go func() {
  452. defer func() {
  453. if p := recover(); p != nil {
  454. panicChan <- p
  455. }
  456. close(done)
  457. close(panicChan)
  458. }()
  459. // 匹配对应的 Eps 去处理
  460. seasonEpsKey := my_util.GetEpisodeKeyName(episodeInfo.Season, episodeInfo.Episode)
  461. if fullSeasonSubDict[seasonEpsKey] == nil || len(fullSeasonSubDict[seasonEpsKey]) < 1 {
  462. d.log.Infoln("seriesDlFunc.saveFullSeasonSub, no sub found, Skip", seasonEpsKey)
  463. done <- nil
  464. }
  465. done <- d.oneVideoSelectBestSub(episodeInfo.FileFullPath, fullSeasonSubDict[seasonEpsKey])
  466. }()
  467. select {
  468. case errInterface := <-done:
  469. if errInterface != nil {
  470. errSave2Local = errInterface.(error)
  471. d.log.Errorln(errInterface.(error))
  472. } else {
  473. save2LocalSubCount++
  474. }
  475. break
  476. case p := <-panicChan:
  477. // 遇到内部的 panic,向外抛出
  478. d.log.Errorln("seriesDlFunc.oneVideoSelectBestSub panicChan", p)
  479. break
  480. case <-ctx.Done():
  481. {
  482. err = errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub, %v S%dE%d", seriesInfo.Name, episodeInfo.Season, episodeInfo.Episode))
  483. d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
  484. return err
  485. }
  486. }
  487. }
  488. // 是否清理全季的缓存字幕文件夹
  489. if d.settings.AdvancedSettings.SaveFullSeasonTmpSubtitles == false {
  490. err = sub_helper.DeleteOneSeasonSubCacheFolder(seriesInfo.DirPath)
  491. if err != nil {
  492. d.log.Errorln("seriesDlFunc.DeleteOneSeasonSubCacheFolder", err)
  493. }
  494. }
  495. if save2LocalSubCount < 1 {
  496. // 下载的字幕都没有一个能够写入到本地的,那么就有问题了
  497. d.downloadQueue.AutoDetectUpdateJobStatus(job, errSave2Local)
  498. return errSave2Local
  499. }
  500. // 哪怕有一个写入到本地成功了,也无需对本次任务报错
  501. d.downloadQueue.AutoDetectUpdateJobStatus(job, nil)
  502. // TODO 刷新字幕,这里是 Emby 的,如果是其他的,需要再对接对应的媒体服务器
  503. if d.settings.EmbySettings.Enable == true && d.embyHelper != nil && job.MediaServerInsideVideoID != "" {
  504. err = d.embyHelper.EmbyApi.UpdateVideoSubList(job.MediaServerInsideVideoID)
  505. if err != nil {
  506. d.log.Errorln("UpdateVideoSubList", job.SeriesRootDirPath, job.MediaServerInsideVideoID, job.Season, job.Episode, "Error:", err)
  507. return err
  508. }
  509. }
  510. return nil
  511. }