downloader.go 18 KB

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