downloader.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. package downloader
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/allanpk716/ChineseSubFinder/internal/common"
  6. "github.com/allanpk716/ChineseSubFinder/internal/ifaces"
  7. embyHelper "github.com/allanpk716/ChineseSubFinder/internal/logic/emby_helper"
  8. "github.com/allanpk716/ChineseSubFinder/internal/logic/forced_scan_and_down_sub"
  9. markSystem "github.com/allanpk716/ChineseSubFinder/internal/logic/mark_system"
  10. "github.com/allanpk716/ChineseSubFinder/internal/logic/restore_fix_timeline_bk"
  11. seriesHelper "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_timeline_fixer"
  14. "github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
  15. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  16. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  17. subCommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
  18. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
  19. subTimelineFixerPKG "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
  20. "github.com/allanpk716/ChineseSubFinder/internal/pkg/task_control"
  21. "github.com/allanpk716/ChineseSubFinder/internal/types/emby"
  22. "github.com/allanpk716/ChineseSubFinder/internal/types/series"
  23. "github.com/sirupsen/logrus"
  24. "golang.org/x/net/context"
  25. "path/filepath"
  26. "sync"
  27. )
  28. // Downloader 实例化一次用一次,不要反复的使用,很多临时标志位需要清理。
  29. type Downloader struct {
  30. settings settings.Settings
  31. log *logrus.Logger
  32. subSupplierHub *subSupplier.SubSupplierHub
  33. mk *markSystem.MarkingSystem // MarkingSystem
  34. embyHelper *embyHelper.EmbyHelper
  35. movieFileFullPathList []string // 多个需要搜索字幕的电影文件全路径
  36. seriesSubNeedDlMap map[string][]emby.EmbyMixInfo // 多个需要搜索字幕的连续剧目录
  37. subFormatter ifaces.ISubFormatter // 字幕格式化命名的实现
  38. subNameFormatter subCommon.FormatterName // 从 inSubFormatter 推断出来
  39. needForcedScanAndDownSub bool // 将会强制扫描所有的视频,下载字幕,替换已经存在的字幕,不进行时间段和已存在则跳过的判断。且不会进过 Emby API 的逻辑,智能进行强制去以本程序的方式去扫描。
  40. NeedRestoreFixTimeLineBK bool // 从 csf-bk 文件还原时间轴修复前的字幕文件
  41. subTimelineFixerHelperEx *sub_timeline_fixer.SubTimelineFixerHelperEx // 字幕时间轴校正
  42. taskControl *task_control.TaskControl
  43. canceled bool
  44. canceledLock sync.Mutex
  45. }
  46. func NewDownloader(_supplierHub *subSupplier.SubSupplierHub, inSubFormatter ifaces.ISubFormatter, _settings settings.Settings) (*Downloader, error) {
  47. var downloader Downloader
  48. var err error
  49. downloader.subFormatter = inSubFormatter
  50. downloader.log = log_helper.GetLogger()
  51. // 参入设置信息
  52. downloader.settings = _settings
  53. // 检测是否某些参数超出范围
  54. downloader.settings.Check()
  55. // 初始化 Emby API 接口
  56. if downloader.settings.EmbySettings.Enable == true && downloader.settings.EmbySettings.AddressUrl != "" && downloader.settings.EmbySettings.APIKey != "" {
  57. downloader.embyHelper = embyHelper.NewEmbyHelper(*downloader.settings.EmbySettings)
  58. }
  59. // 这里就不单独弄一个 settings.SubNameFormatter 字段来传递值了,因为 inSubFormatter 就已经知道是什么 formatter 了
  60. downloader.subNameFormatter = subCommon.FormatterName(downloader.subFormatter.GetFormatterFormatterName())
  61. downloader.subSupplierHub = _supplierHub
  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.SubSiteShooter)
  67. sitesSequence = append(sitesSequence, common.SubSiteXunLei)
  68. downloader.mk = markSystem.NewMarkingSystem(sitesSequence, downloader.settings.AdvancedSettings.SubTypePriority)
  69. downloader.movieFileFullPathList = make([]string, 0)
  70. downloader.seriesSubNeedDlMap = make(map[string][]emby.EmbyMixInfo)
  71. // 初始化,字幕校正的实例
  72. downloader.subTimelineFixerHelperEx = sub_timeline_fixer.NewSubTimelineFixerHelperEx(*downloader.settings.TimelineFixerSettings)
  73. if downloader.settings.AdvancedSettings.FixTimeLine == true {
  74. downloader.subTimelineFixerHelperEx.Check()
  75. }
  76. // 初始化任务控制
  77. downloader.taskControl, err = task_control.NewTaskControl(downloader.settings.CommonSettings.Threads, log_helper.GetLogger())
  78. if err != nil {
  79. return nil, err
  80. }
  81. return &downloader, nil
  82. }
  83. // ReadSpeFile 优先级最高。读取特殊文件,启用一些特殊的功能,比如 forced_scan_and_down_sub
  84. func (d *Downloader) ReadSpeFile() error {
  85. // 理论上是一次性的,用了这个文件就应该没了
  86. // 强制的字幕扫描
  87. needProcessForcedScanAndDownSub, err := forced_scan_and_down_sub.CheckSpeFile()
  88. if err != nil {
  89. return err
  90. }
  91. d.needForcedScanAndDownSub = needProcessForcedScanAndDownSub
  92. // 从 csf-bk 文件还原时间轴修复前的字幕文件
  93. needProcessRestoreFixTimelineBK, err := restore_fix_timeline_bk.CheckSpeFile()
  94. if err != nil {
  95. return err
  96. }
  97. d.NeedRestoreFixTimeLineBK = needProcessRestoreFixTimelineBK
  98. d.log.Infoln("NeedRestoreFixTimeLineBK ==", needProcessRestoreFixTimelineBK)
  99. return nil
  100. }
  101. // GetUpdateVideoListFromEmby 这里首先会进行近期影片的获取,然后对这些影片进行刷新,然后在获取字幕列表,最终得到需要字幕获取的 video 列表
  102. func (d *Downloader) GetUpdateVideoListFromEmby() error {
  103. if d.embyHelper == nil {
  104. return nil
  105. }
  106. defer func() {
  107. d.log.Infoln("GetUpdateVideoListFromEmby End")
  108. }()
  109. d.log.Infoln("GetUpdateVideoListFromEmby Start...")
  110. //------------------------------------------------------
  111. // 是否取消执行
  112. nowCancel := false
  113. d.canceledLock.Lock()
  114. nowCancel = d.canceled
  115. d.canceledLock.Unlock()
  116. if nowCancel == true {
  117. d.log.Infoln("GetUpdateVideoListFromEmby Canceled")
  118. return nil
  119. }
  120. var err error
  121. var movieList []emby.EmbyMixInfo
  122. movieList, d.seriesSubNeedDlMap, err = d.embyHelper.GetRecentlyAddVideoList()
  123. if err != nil {
  124. return err
  125. }
  126. // 获取全路径
  127. for _, info := range movieList {
  128. d.movieFileFullPathList = append(d.movieFileFullPathList, info.VideoFileFullPath)
  129. }
  130. // 输出调试信息
  131. d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap Start")
  132. for s := range d.seriesSubNeedDlMap {
  133. d.log.Debugln(s)
  134. }
  135. d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap End")
  136. d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - movieFileFullPathList Start")
  137. for s, value := range d.movieFileFullPathList {
  138. d.log.Debugln(s, value)
  139. }
  140. d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - movieFileFullPathList End")
  141. return nil
  142. }
  143. func (d *Downloader) RefreshEmbySubList() error {
  144. if d.embyHelper == nil {
  145. return nil
  146. }
  147. bRefresh := false
  148. defer func() {
  149. if bRefresh == true {
  150. d.log.Infoln("Refresh Emby Sub List Success")
  151. } else {
  152. d.log.Errorln("Refresh Emby Sub List Error")
  153. }
  154. }()
  155. d.log.Infoln("Refresh Emby Sub List Start...")
  156. //------------------------------------------------------
  157. // 是否取消执行
  158. nowCancel := false
  159. d.canceledLock.Lock()
  160. nowCancel = d.canceled
  161. d.canceledLock.Unlock()
  162. if nowCancel == true {
  163. d.log.Infoln("RefreshEmbySubList Canceled")
  164. return nil
  165. }
  166. bRefresh, err := d.embyHelper.RefreshEmbySubList()
  167. if err != nil {
  168. return err
  169. }
  170. return nil
  171. }
  172. // DownloadSub4Movie 这里对接 Emby 的时候比较方便,只要更新 d.movieFileFullPathList 就行了,不像连续剧那么麻烦
  173. func (d *Downloader) DownloadSub4Movie() error {
  174. defer func() {
  175. // 所有的电影字幕下载完成,抉择完成,需要清理缓存目录
  176. err := my_util.ClearRootTmpFolder()
  177. if err != nil {
  178. d.log.Error("ClearRootTmpFolder", err)
  179. }
  180. d.log.Infoln("Download Movie Sub End...")
  181. }()
  182. var err error
  183. d.log.Infoln("Download Movie Sub Started...")
  184. //------------------------------------------------------
  185. // 是否取消执行
  186. nowCancel := false
  187. d.canceledLock.Lock()
  188. nowCancel = d.canceled
  189. d.canceledLock.Unlock()
  190. if nowCancel == true {
  191. d.log.Infoln("DownloadSub4Movie Canceled")
  192. return nil
  193. }
  194. // -----------------------------------------------------
  195. // 优先判断特殊的操作
  196. if d.needForcedScanAndDownSub == true {
  197. // 全扫描
  198. d.movieFileFullPathList, err = my_util.SearchMatchedVideoFile(dir)
  199. if err != nil {
  200. return err
  201. }
  202. } else {
  203. // 是否是通过 emby_helper api 获取的列表
  204. if d.embyHelper == nil {
  205. // 没有填写 emby_helper api 的信息,那么就走常规的全文件扫描流程
  206. d.movieFileFullPathList, err = my_util.SearchMatchedVideoFile(dir)
  207. if err != nil {
  208. return err
  209. }
  210. } else {
  211. // 进过 emby_helper api 的信息读取
  212. d.log.Infoln("Movie Sub Dl From Emby API...")
  213. if len(d.movieFileFullPathList) < 1 {
  214. d.log.Infoln("Movie Sub Dl From Emby API no movie need Dl sub")
  215. return nil
  216. }
  217. }
  218. }
  219. // -----------------------------------------------------
  220. // 并发控制,设置为 movie 的处理函数
  221. d.taskControl.SetCtxProcessFunc("MoviePool", d.movieDlFunc, common.OneMovieProcessTimeOut)
  222. // -----------------------------------------------------
  223. // 一个视频文件同时多个站点查询,阻塞完毕后,在进行下一个
  224. for i, oneVideoFullPath := range d.movieFileFullPathList {
  225. err = d.taskControl.Invoke(&task_control.TaskData{
  226. Index: i,
  227. DataEx: DownloadInputData{
  228. OneVideoFullPath: oneVideoFullPath,
  229. },
  230. })
  231. if err != nil {
  232. d.log.Errorln("DownloadSub4Movie Invoke Index:", i, "Error", err)
  233. }
  234. }
  235. d.taskControl.Hold()
  236. // 可以得到执行结果的统计信息
  237. successList, noExecuteList, errorList := d.taskControl.GetExecuteInfo()
  238. d.log.Infoln("--------------------------------------")
  239. d.log.Infoln("successList", len(successList))
  240. for i, indexId := range successList {
  241. d.log.Infoln(i, d.movieFileFullPathList[indexId])
  242. }
  243. d.log.Infoln("--------------------------------------")
  244. d.log.Infoln("noExecuteList", len(noExecuteList))
  245. for i, indexId := range noExecuteList {
  246. d.log.Infoln(i, d.movieFileFullPathList[indexId])
  247. }
  248. d.log.Infoln("--------------------------------------")
  249. d.log.Infoln("errorList", len(errorList))
  250. for i, indexId := range errorList {
  251. d.log.Infoln(i, d.movieFileFullPathList[indexId])
  252. }
  253. d.log.Infoln("--------------------------------------")
  254. return nil
  255. }
  256. func (d *Downloader) DownloadSub4Series() error {
  257. var err error
  258. defer func() {
  259. // 所有的连续剧字幕下载完成,抉择完成,需要清理缓存目录
  260. err := my_util.ClearRootTmpFolder()
  261. if err != nil {
  262. d.log.Error("ClearRootTmpFolder", err)
  263. }
  264. d.log.Infoln("Download Series Sub End...")
  265. my_util.CloseChrome()
  266. d.log.Infoln("CloseChrome")
  267. }()
  268. d.log.Infoln("Download Series Sub Started...")
  269. //------------------------------------------------------
  270. // 是否取消执行
  271. nowCancel := false
  272. d.canceledLock.Lock()
  273. nowCancel = d.canceled
  274. d.canceledLock.Unlock()
  275. if nowCancel == true {
  276. d.log.Infoln("DownloadSub4Series Canceled")
  277. return nil
  278. }
  279. // -----------------------------------------------------
  280. // 并发控制,设置为 movie 的处理函数
  281. d.taskControl.SetCtxProcessFunc("SeriesPool", d.seriesDlFunc, common.OneSeriesProcessTimeOut)
  282. // -----------------------------------------------------
  283. // 是否是通过 emby_helper api 获取的列表
  284. var seriesDirList = make([]string, 0)
  285. if d.embyHelper == nil {
  286. // 遍历连续剧总目录下的第一层目录
  287. seriesDirList, err = seriesHelper.GetSeriesList(dir)
  288. if err != nil {
  289. return err
  290. }
  291. for index, seriesDir := range seriesDirList {
  292. d.log.Debugln("embyHelper == nil GetSeriesList", index, seriesDir)
  293. }
  294. } else {
  295. // 这里给出的是连续剧的文件夹名称
  296. d.log.Debugln("embyHelper seriesSubNeedDlMap Count:", len(d.seriesSubNeedDlMap))
  297. for s := range d.seriesSubNeedDlMap {
  298. seriesDirList = append(seriesDirList, s)
  299. d.log.Debugln("embyHelper seriesSubNeedDlMap:", s)
  300. }
  301. }
  302. for i, oneSeriesPath := range seriesDirList {
  303. err = d.taskControl.Invoke(&task_control.TaskData{
  304. Index: i,
  305. DataEx: DownloadInputData{
  306. RootDirPath: dir,
  307. OneSeriesPath: oneSeriesPath,
  308. },
  309. })
  310. if err != nil {
  311. d.log.Errorln("DownloadSub4Movie Invoke Index:", i, "Error", err)
  312. }
  313. }
  314. d.taskControl.Hold()
  315. // 可以得到执行结果的统计信息
  316. successList, noExecuteList, errorList := d.taskControl.GetExecuteInfo()
  317. d.log.Infoln("--------------------------------------")
  318. d.log.Infoln("successList", len(successList))
  319. for i, indexId := range successList {
  320. d.log.Infoln(i, seriesDirList[indexId])
  321. }
  322. d.log.Infoln("--------------------------------------")
  323. d.log.Infoln("noExecuteList", len(noExecuteList))
  324. for i, indexId := range noExecuteList {
  325. d.log.Infoln(i, seriesDirList[indexId])
  326. }
  327. d.log.Infoln("--------------------------------------")
  328. d.log.Infoln("errorList", len(errorList))
  329. for i, indexId := range errorList {
  330. d.log.Infoln(i, seriesDirList[indexId])
  331. }
  332. d.log.Infoln("--------------------------------------")
  333. return nil
  334. }
  335. func (d *Downloader) RestoreFixTimelineBK() error {
  336. defer d.log.Infoln("End Restore Fix Timeline BK")
  337. d.log.Infoln("Start Restore Fix Timeline BK...")
  338. //------------------------------------------------------
  339. // 是否取消执行
  340. nowCancel := false
  341. d.canceledLock.Lock()
  342. nowCancel = d.canceled
  343. d.canceledLock.Unlock()
  344. if nowCancel == true {
  345. d.log.Infoln("RestoreFixTimelineBK Canceled")
  346. return nil
  347. }
  348. _, err := subTimelineFixerPKG.Restore(d.settings.CommonSettings.MoviePaths, d.settings.CommonSettings.SeriesPaths)
  349. if err != nil {
  350. return err
  351. }
  352. return nil
  353. }
  354. func (d *Downloader) Cancel() {
  355. d.canceledLock.Lock()
  356. d.canceled = true
  357. d.canceledLock.Unlock()
  358. d.taskControl.Release()
  359. }
  360. func (d *Downloader) movieDlFunc(ctx context.Context, inData interface{}) error {
  361. taskData := inData.(*task_control.TaskData)
  362. downloadInputData := taskData.DataEx.(DownloadInputData)
  363. // -----------------------------------------------------
  364. // 字幕都下载缓存好了,需要抉择存哪一个,优先选择中文双语的,然后到中文
  365. organizeSubFiles, err := d.subSupplierHub.DownloadSub4Movie(downloadInputData.OneVideoFullPath, taskData.Index, d.needForcedScanAndDownSub)
  366. if err != nil {
  367. d.log.Errorln("subSupplierHub.DownloadSub4Movie", downloadInputData.OneVideoFullPath, err)
  368. return err
  369. }
  370. // 返回的两个值都是 nil 的时候,就是无需下载字幕,那么同样不用输出额外的信息,因为之前会输出跳过的原因
  371. if organizeSubFiles == nil {
  372. return nil
  373. }
  374. // 去搜索了没有发现字幕
  375. if len(organizeSubFiles) < 1 {
  376. d.log.Infoln("no sub found", filepath.Base(downloadInputData.OneVideoFullPath))
  377. return nil
  378. }
  379. d.oneVideoSelectBestSub(downloadInputData.OneVideoFullPath, organizeSubFiles)
  380. // -----------------------------------------------------
  381. return nil
  382. }
  383. func (d *Downloader) seriesDlFunc(ctx context.Context, inData interface{}) error {
  384. var err error
  385. taskData := inData.(*task_control.TaskData)
  386. downloadInputData := taskData.DataEx.(DownloadInputData)
  387. // 这里拿到了这一部连续剧的所有的剧集信息,以及所有下载到的字幕信息
  388. var seriesInfo *series.SeriesInfo
  389. var organizeSubFiles map[string][]string
  390. // 优先判断特殊的操作
  391. if d.needForcedScanAndDownSub == true {
  392. // 全盘扫描
  393. seriesInfo, organizeSubFiles, err = d.subSupplierHub.DownloadSub4Series(downloadInputData.OneSeriesPath, taskData.Index, d.needForcedScanAndDownSub)
  394. if err != nil {
  395. d.log.Errorln("subSupplierHub.DownloadSub4Series", downloadInputData.OneSeriesPath, err)
  396. return err
  397. }
  398. } else {
  399. // 是否是通过 emby_helper api 获取的列表
  400. if d.embyHelper == nil {
  401. // 不适用 emby api
  402. seriesInfo, organizeSubFiles, err = d.subSupplierHub.DownloadSub4Series(downloadInputData.OneSeriesPath, taskData.Index, d.needForcedScanAndDownSub)
  403. if err != nil {
  404. d.log.Errorln("subSupplierHub.DownloadSub4Series", downloadInputData.OneSeriesPath, err)
  405. return err
  406. }
  407. } else {
  408. // 先进性 emby_helper api 的操作,读取需要更新字幕的项目
  409. seriesInfo, organizeSubFiles, err = d.subSupplierHub.DownloadSub4SeriesFromEmby(
  410. filepath.Join(downloadInputData.RootDirPath, downloadInputData.OneSeriesPath),
  411. d.seriesSubNeedDlMap[downloadInputData.OneSeriesPath], taskData.Index)
  412. if err != nil {
  413. d.log.Errorln("subSupplierHub.DownloadSub4Series", downloadInputData.OneSeriesPath, err)
  414. return err
  415. }
  416. }
  417. }
  418. if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
  419. d.log.Infoln("no sub found", filepath.Base(downloadInputData.OneSeriesPath))
  420. return nil
  421. }
  422. // 只针对需要下载字幕的视频进行字幕的选择保存
  423. for epsKey, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
  424. stage := make(chan interface{}, 1)
  425. go func() {
  426. // 匹配对应的 Eps 去处理
  427. d.oneVideoSelectBestSub(episodeInfo.FileFullPath, organizeSubFiles[epsKey])
  428. stage <- 1
  429. }()
  430. select {
  431. case <-ctx.Done():
  432. {
  433. return errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub epsKey: %s", epsKey))
  434. }
  435. case <-stage:
  436. break
  437. }
  438. }
  439. // 这里会拿到一份季度字幕的列表比如,Key 是 S1E0 S2E0 S3E0,value 是新的存储位置
  440. fullSeasonSubDict := d.saveFullSeasonSub(seriesInfo, organizeSubFiles)
  441. // TODO 季度的字幕包,应该优先于零散的字幕吧,暂定就这样了,注意是全部都替换
  442. // 需要与有下载需求的季交叉
  443. for _, episodeInfo := range seriesInfo.EpList {
  444. stage := make(chan interface{}, 1)
  445. _, ok := seriesInfo.NeedDlSeasonDict[episodeInfo.Season]
  446. if ok == false {
  447. continue
  448. }
  449. go func() {
  450. // 匹配对应的 Eps 去处理
  451. seasonEpsKey := my_util.GetEpisodeKeyName(episodeInfo.Season, episodeInfo.Episode)
  452. d.oneVideoSelectBestSub(episodeInfo.FileFullPath, fullSeasonSubDict[seasonEpsKey])
  453. stage <- 1
  454. }()
  455. select {
  456. case <-ctx.Done():
  457. {
  458. return errors.New(fmt.Sprintf("cancel at EpList.oneVideoSelectBestSub episodeInfo.FileFullPath: %s", episodeInfo.FileFullPath))
  459. }
  460. case <-stage:
  461. break
  462. }
  463. }
  464. // 是否清理全季的缓存字幕文件夹
  465. if d.settings.AdvancedSettings.SaveFullSeasonTmpSubtitles == false {
  466. err = sub_helper.DeleteOneSeasonSubCacheFolder(seriesInfo.DirPath)
  467. if err != nil {
  468. return err
  469. }
  470. }
  471. return nil
  472. }