seriesHelper.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. package series_helper
  2. import (
  3. "path/filepath"
  4. "strconv"
  5. "time"
  6. "github.com/allanpk716/ChineseSubFinder/pkg/media_info_dealers"
  7. "github.com/allanpk716/ChineseSubFinder/pkg/search"
  8. "github.com/allanpk716/ChineseSubFinder/pkg"
  9. "github.com/allanpk716/ChineseSubFinder/pkg/ifaces"
  10. "github.com/allanpk716/ChineseSubFinder/pkg/types/common"
  11. "github.com/allanpk716/ChineseSubFinder/pkg/types/emby"
  12. "github.com/allanpk716/ChineseSubFinder/pkg/types/series"
  13. "github.com/allanpk716/ChineseSubFinder/pkg/types/supplier"
  14. "github.com/allanpk716/ChineseSubFinder/pkg/logic/sub_parser/ass"
  15. "github.com/allanpk716/ChineseSubFinder/pkg/logic/sub_parser/srt"
  16. "github.com/allanpk716/ChineseSubFinder/internal/models"
  17. "github.com/allanpk716/ChineseSubFinder/pkg/decode"
  18. "github.com/allanpk716/ChineseSubFinder/pkg/imdb_helper"
  19. "github.com/allanpk716/ChineseSubFinder/pkg/sub_helper"
  20. "github.com/allanpk716/ChineseSubFinder/pkg/sub_parser_hub"
  21. "github.com/emirpasic/gods/maps/treemap"
  22. "github.com/jinzhu/now"
  23. "github.com/sirupsen/logrus"
  24. )
  25. func readSeriesInfo(dealers *media_info_dealers.Dealers, seriesDir string, need2AnalyzeSub bool) (*series.SeriesInfo, map[string][]series.SubInfo, error) {
  26. seriesInfo, err := GetSeriesInfoFromDir(dealers, seriesDir)
  27. if err != nil {
  28. return nil, nil, err
  29. }
  30. seriesInfo.NeedDlSeasonDict = make(map[int]int)
  31. seriesInfo.NeedDlEpsKeyList = make(map[string]series.EpisodeInfo)
  32. // 字幕字典 S01E01 - []SubInfo
  33. SubDict := make(map[string][]series.SubInfo)
  34. if need2AnalyzeSub == false {
  35. return seriesInfo, SubDict, nil
  36. }
  37. subParserHub := sub_parser_hub.NewSubParserHub(dealers.Logger, ass.NewParser(dealers.Logger), srt.NewParser(dealers.Logger))
  38. // 先搜索这个目录下,所有符合条件的视频
  39. matchedVideoFile, err := search.MatchedVideoFile(dealers.Logger, seriesDir)
  40. if err != nil {
  41. return nil, nil, err
  42. }
  43. // 然后再从这个视频找到对用匹配的字幕
  44. for _, oneVideoFPath := range matchedVideoFile {
  45. subFiles, err := sub_helper.SearchMatchedSubFileByOneVideo(dealers.Logger, oneVideoFPath)
  46. if err != nil {
  47. return nil, nil, err
  48. }
  49. epsVideoNfoInfo, err := decode.GetVideoNfoInfo4OneSeriesEpisode(oneVideoFPath)
  50. if err != nil {
  51. dealers.Logger.Errorln(err)
  52. continue
  53. }
  54. for _, subFile := range subFiles {
  55. bFind, subParserFileInfo, err := subParserHub.DetermineFileTypeFromFile(subFile)
  56. if err != nil {
  57. dealers.Logger.Errorln("DetermineFileTypeFromFile", subFile, err)
  58. continue
  59. }
  60. if bFind == false {
  61. dealers.Logger.Warnln("DetermineFileTypeFromFile", subFile, "not support SubType")
  62. continue
  63. }
  64. // 判断这个字幕是否包含中文
  65. if subParserHub.IsSubHasChinese(subParserFileInfo) == false {
  66. continue
  67. }
  68. epsKey := pkg.GetEpisodeKeyName(epsVideoNfoInfo.Season, epsVideoNfoInfo.Episode)
  69. oneFileSubInfo := series.SubInfo{
  70. Title: epsVideoNfoInfo.Title,
  71. Season: epsVideoNfoInfo.Season,
  72. Episode: epsVideoNfoInfo.Episode,
  73. Language: subParserFileInfo.Lang,
  74. Dir: filepath.Dir(subFile),
  75. FileFullPath: subFile,
  76. }
  77. _, ok := SubDict[epsKey]
  78. if ok == false {
  79. // 初始化
  80. SubDict[epsKey] = make([]series.SubInfo, 0)
  81. }
  82. SubDict[epsKey] = append(SubDict[epsKey], oneFileSubInfo)
  83. }
  84. }
  85. return seriesInfo, SubDict, nil
  86. }
  87. // ReadSeriesInfoFromDir 读取剧集的信息,只有那些 Eps 需要下载字幕的 NeedDlEpsKeyList
  88. func ReadSeriesInfoFromDir(dealers *media_info_dealers.Dealers,
  89. seriesDir string,
  90. ExpirationTime int,
  91. forcedScanAndDownloadSub bool,
  92. need2AnalyzeSub bool,
  93. epsMap ...map[int][]int) (*series.SeriesInfo, error) {
  94. seriesInfo, SubDict, err := readSeriesInfo(dealers, seriesDir, need2AnalyzeSub)
  95. if err != nil {
  96. return nil, err
  97. }
  98. // 搜索所有的视频
  99. videoFiles, err := search.MatchedVideoFile(dealers.Logger, seriesDir)
  100. if err != nil {
  101. return nil, err
  102. }
  103. // 视频字典 S01E01 - EpisodeInfo
  104. EpisodeDict := make(map[string]series.EpisodeInfo)
  105. for _, videoFile := range videoFiles {
  106. getEpsInfoAndSubDic(dealers.Logger, videoFile, EpisodeDict, SubDict, epsMap...)
  107. }
  108. for _, episodeInfo := range EpisodeDict {
  109. seriesInfo.EpList = append(seriesInfo.EpList, episodeInfo)
  110. seriesInfo.SeasonDict[episodeInfo.Season] = episodeInfo.Season
  111. }
  112. seriesInfo.NeedDlEpsKeyList, seriesInfo.NeedDlSeasonDict = whichSeasonEpsNeedDownloadSub(dealers.Logger, seriesInfo, ExpirationTime, forcedScanAndDownloadSub)
  113. return seriesInfo, nil
  114. }
  115. // ReadSeriesInfoFromEmby 将 Emby API 读取到的数据进行转换到通用的结构中,需要填充那些剧集需要下载,这样要的是一个连续剧的,不是所有的传入(只有那些 Eps 需要下载字幕的 NeedDlEpsKeyList)
  116. func ReadSeriesInfoFromEmby(dealers *media_info_dealers.Dealers, seriesDir string, seriesVideoList []emby.EmbyMixInfo, ExpirationTime int, forcedScanAndDownloadSub bool, need2AnalyzeSub bool) (*series.SeriesInfo, error) {
  117. seriesInfo, SubDict, err := readSeriesInfo(dealers, seriesDir, need2AnalyzeSub)
  118. if err != nil {
  119. return nil, err
  120. }
  121. EpisodeDict := make(map[string]series.EpisodeInfo)
  122. for _, info := range seriesVideoList {
  123. getEpsInfoAndSubDic(dealers.Logger, info.PhysicalVideoFileFullPath, EpisodeDict, SubDict)
  124. }
  125. for _, episodeInfo := range EpisodeDict {
  126. seriesInfo.EpList = append(seriesInfo.EpList, episodeInfo)
  127. seriesInfo.SeasonDict[episodeInfo.Season] = episodeInfo.Season
  128. }
  129. seriesInfo.NeedDlEpsKeyList, seriesInfo.NeedDlSeasonDict = whichSeasonEpsNeedDownloadSub(dealers.Logger, seriesInfo, ExpirationTime, forcedScanAndDownloadSub)
  130. return seriesInfo, nil
  131. }
  132. // SkipChineseSeries 跳过中文连续剧
  133. func SkipChineseSeries(dealers *media_info_dealers.Dealers, seriesRootPath string) (bool, *models.IMDBInfo, error) {
  134. imdbInfo, err := decode.GetVideoNfoInfo4SeriesDir(seriesRootPath)
  135. if err != nil {
  136. return false, nil, err
  137. }
  138. isChineseVideo, t, err := imdb_helper.IsChineseVideo(dealers, imdbInfo)
  139. if err != nil {
  140. return false, nil, err
  141. }
  142. if isChineseVideo == true {
  143. dealers.Logger.Infoln("Skip", filepath.Base(seriesRootPath), "Sub Download, because series is Chinese")
  144. return true, t, nil
  145. } else {
  146. return false, t, nil
  147. }
  148. }
  149. // DownloadSubtitleInAllSiteByOneSeries 一部连续剧,在所有的网站,下载相应的字幕
  150. func DownloadSubtitleInAllSiteByOneSeries(logger *logrus.Logger, Suppliers []ifaces.ISupplier, seriesInfo *series.SeriesInfo, i int64) []supplier.SubInfo {
  151. defer func() {
  152. logger.Infoln(common.QueueName, i, "DlSub End", seriesInfo.DirPath)
  153. logger.Infoln("------------------------------------------")
  154. }()
  155. logger.Infoln(common.QueueName, i, "DlSub Start", seriesInfo.DirPath)
  156. logger.Infoln(common.QueueName, i, "IMDB ID:", seriesInfo.ImdbId, "NeedDownloadSubs:", len(seriesInfo.NeedDlEpsKeyList))
  157. var outSUbInfos = make([]supplier.SubInfo, 0)
  158. if len(seriesInfo.NeedDlEpsKeyList) < 1 {
  159. return outSUbInfos
  160. }
  161. for key := range seriesInfo.NeedDlEpsKeyList {
  162. logger.Infoln(common.QueueName, i, "NeedDownloadEps", "-", key)
  163. }
  164. for _, oneSupplier := range Suppliers {
  165. oneSupplierFunc := func() {
  166. defer func() {
  167. logger.Infoln(common.QueueName, i, oneSupplier.GetSupplierName(), "End")
  168. logger.Infoln("------------------------------------------")
  169. }()
  170. var subInfos []supplier.SubInfo
  171. logger.Infoln("------------------------------------------")
  172. logger.Infoln(common.QueueName, i, oneSupplier.GetSupplierName(), "Start...")
  173. if oneSupplier.OverDailyDownloadLimit() == true {
  174. logger.Infoln(common.QueueName, i, oneSupplier.GetSupplierName(), "Over Daily Download Limit")
  175. return
  176. }
  177. // 一次性把这一部连续剧的所有字幕下载完
  178. subInfos, err := oneSupplier.GetSubListFromFile4Series(seriesInfo)
  179. if err != nil {
  180. logger.Errorln(common.QueueName, i, oneSupplier.GetSupplierName(), "GetSubListFromFile4Series", err)
  181. return
  182. }
  183. // 把后缀名给改好
  184. sub_helper.ChangeVideoExt2SubExt(subInfos)
  185. outSUbInfos = append(outSUbInfos, subInfos...)
  186. }
  187. oneSupplierFunc()
  188. }
  189. return outSUbInfos
  190. }
  191. // GetSeriesListFromDirs 获取这个目录下的所有文件夹名称,默认为一个连续剧的目录的List
  192. func GetSeriesListFromDirs(logger *logrus.Logger, dirs []string) (*treemap.Map, error) {
  193. defer func() {
  194. logger.Infoln("GetSeriesListFromDirs End")
  195. logger.Infoln("------------------------------------------")
  196. }()
  197. logger.Infoln("------------------------------------------")
  198. logger.Infoln("GetSeriesListFromDirs Start...")
  199. var fileFullPathMap = treemap.NewWithStringComparator()
  200. for _, dir := range dirs {
  201. seriesList, err := GetSeriesList(logger, dir)
  202. if err != nil {
  203. return nil, err
  204. }
  205. value, found := fileFullPathMap.Get(dir)
  206. if found == false {
  207. fileFullPathMap.Put(dir, seriesList)
  208. } else {
  209. value = append(value.([]string), seriesList...)
  210. fileFullPathMap.Put(dir, value)
  211. }
  212. }
  213. return fileFullPathMap, nil
  214. }
  215. // GetSeriesList 获取这个目录下的所有文件夹名称,默认为一个连续剧的目录的List
  216. func GetSeriesList(log *logrus.Logger, dir string) ([]string, error) {
  217. // 需要把所有 tvshow.nfo 搜索出来,那么这些文件对应的目录就是目标连续剧的目录
  218. tvNFOs, err := search.TVNfo(log, dir)
  219. if err != nil {
  220. return nil, err
  221. }
  222. var seriesDirList = make([]string, 0)
  223. for _, tvNfo := range tvNFOs {
  224. seriesDirList = append(seriesDirList, filepath.Dir(tvNfo))
  225. }
  226. return seriesDirList, err
  227. }
  228. // whichSeasonEpsNeedDownloadSub 有那些 Eps 需要下载的,按 SxEx 反回 epsKey
  229. func whichSeasonEpsNeedDownloadSub(logger *logrus.Logger, seriesInfo *series.SeriesInfo, ExpirationTime int, forcedScanAndDownloadSub bool) (map[string]series.EpisodeInfo, map[int]int) {
  230. var needDlSubEpsList = make(map[string]series.EpisodeInfo, 0)
  231. var needDlSeasonList = make(map[int]int, 0)
  232. currentTime := time.Now()
  233. // 直接强制所有视频都下载字幕
  234. if forcedScanAndDownloadSub == true {
  235. for _, epsInfo := range seriesInfo.EpList {
  236. // 添加
  237. epsKey := pkg.GetEpisodeKeyName(epsInfo.Season, epsInfo.Episode)
  238. needDlSubEpsList[epsKey] = epsInfo
  239. needDlSeasonList[epsInfo.Season] = epsInfo.Season
  240. }
  241. return needDlSubEpsList, needDlSeasonList
  242. }
  243. for _, epsInfo := range seriesInfo.EpList {
  244. // 如果没有字幕,则加入下载列表
  245. // 如果每一集的播出时间能够读取到,那么就以这个完后推算 3个月
  246. // 如果读取不到 Aired Time 那么,这一集下载后的 ModifyTime 3个月天内,都进行字幕的下载
  247. var err error
  248. var baseTime time.Time
  249. if epsInfo.AiredTime != "" {
  250. baseTime, err = now.Parse(epsInfo.AiredTime)
  251. if err != nil {
  252. logger.Errorln("SeriesInfo parse AiredTime", epsInfo.Title, epsInfo.Season, epsInfo.Episode, err)
  253. baseTime = epsInfo.ModifyTime
  254. }
  255. } else {
  256. baseTime = epsInfo.ModifyTime
  257. }
  258. if len(epsInfo.SubAlreadyDownloadedList) < 1 || baseTime.AddDate(0, 0, ExpirationTime).After(currentTime) == true {
  259. // 添加
  260. epsKey := pkg.GetEpisodeKeyName(epsInfo.Season, epsInfo.Episode)
  261. needDlSubEpsList[epsKey] = epsInfo
  262. needDlSeasonList[epsInfo.Season] = epsInfo.Season
  263. } else {
  264. if len(epsInfo.SubAlreadyDownloadedList) > 0 {
  265. logger.Infoln("Skip because find sub file and downloaded or aired over 3 months,", epsInfo.Title, epsInfo.Season, epsInfo.Episode)
  266. } else if baseTime.AddDate(0, 0, ExpirationTime).After(currentTime) == false {
  267. logger.Infoln("Skip because 3 months pass,", epsInfo.Title, epsInfo.Season, epsInfo.Episode)
  268. }
  269. }
  270. }
  271. return needDlSubEpsList, needDlSeasonList
  272. }
  273. func GetSeriesInfoFromDir(dealers *media_info_dealers.Dealers, seriesDir string) (*series.SeriesInfo, error) {
  274. seriesInfo := series.SeriesInfo{}
  275. // 只考虑 IMDB 去查询,文件名目前发现可能会跟电影重复,导致很麻烦,本来也有前置要求要削刮器处理的
  276. videoInfo, err := decode.GetVideoNfoInfo4SeriesDir(seriesDir)
  277. if err != nil {
  278. return nil, err
  279. }
  280. imdbInfo, err := imdb_helper.GetIMDBInfoFromVideoNfoInfo(dealers, videoInfo)
  281. if err != nil {
  282. return nil, err
  283. }
  284. // 使用 IMDB ID 得到通用的剧集名称
  285. // 以 IMDB 的信息为准
  286. if imdbInfo != nil {
  287. if imdbInfo.Name != "" {
  288. seriesInfo.Name = imdbInfo.Name
  289. } else if videoInfo.Title != "" {
  290. seriesInfo.Name = videoInfo.Title
  291. } else {
  292. seriesInfo.Name = filepath.Base(seriesDir)
  293. }
  294. seriesInfo.ImdbId = imdbInfo.IMDBID
  295. seriesInfo.Year = imdbInfo.Year
  296. } else {
  297. if videoInfo.Title != "" {
  298. seriesInfo.Name = videoInfo.Title
  299. } else {
  300. seriesInfo.Name = filepath.Base(seriesDir)
  301. }
  302. seriesInfo.ImdbId = videoInfo.ImdbId
  303. iYear, err := strconv.Atoi(videoInfo.Year)
  304. if err != nil {
  305. // 不是必须的
  306. seriesInfo.Year = 0
  307. dealers.Logger.Warnln("ReadSeriesInfoFromDir.GetVideoNfoInfo4SeriesDir.strconv.Atoi", seriesDir, err)
  308. } else {
  309. seriesInfo.Year = iYear
  310. }
  311. }
  312. seriesInfo.ReleaseDate = videoInfo.ReleaseDate
  313. seriesInfo.DirPath = seriesDir
  314. seriesInfo.EpList = make([]series.EpisodeInfo, 0)
  315. seriesInfo.SeasonDict = make(map[int]int)
  316. return &seriesInfo, nil
  317. }
  318. func getEpsInfoAndSubDic(logger *logrus.Logger,
  319. videoFile string,
  320. EpisodeDict map[string]series.EpisodeInfo,
  321. SubDict map[string][]series.SubInfo,
  322. epsMap ...map[int][]int) {
  323. // 正常来说,一集只有一个格式的视频,也就是 S01E01 只有一个,如果有多个则会只保存第一个
  324. episodeInfo, modifyTime, err := decode.GetVideoInfoFromFileFullPath(videoFile, false)
  325. if err != nil {
  326. logger.Errorln("model.GetVideoInfoFromFileFullPath", videoFile, err)
  327. return
  328. }
  329. if len(epsMap) > 0 {
  330. // 如果这个视频不在需要下载的 Eps 列表中,那么就跳过后续的逻辑
  331. epsList, ok := epsMap[0][episodeInfo.Season]
  332. if ok == false {
  333. return
  334. }
  335. found := false
  336. for _, oneEpsID := range epsList {
  337. if oneEpsID == episodeInfo.Episode {
  338. // 在需要下载的 Eps 列表中
  339. found = true
  340. break
  341. }
  342. }
  343. if found == false {
  344. return
  345. }
  346. }
  347. epsKey := pkg.GetEpisodeKeyName(episodeInfo.Season, episodeInfo.Episode)
  348. _, ok := EpisodeDict[epsKey]
  349. if ok == false {
  350. // 初始化
  351. oneFileEpInfo := series.EpisodeInfo{
  352. Title: episodeInfo.Title,
  353. Season: episodeInfo.Season,
  354. Episode: episodeInfo.Episode,
  355. Dir: filepath.Dir(videoFile),
  356. FileFullPath: videoFile,
  357. ModifyTime: modifyTime,
  358. AiredTime: episodeInfo.ReleaseDate,
  359. }
  360. // 需要匹配同级目录下的字幕
  361. oneFileEpInfo.SubAlreadyDownloadedList = make([]series.SubInfo, 0)
  362. for _, subInfo := range SubDict[epsKey] {
  363. if subInfo.Dir == oneFileEpInfo.Dir {
  364. oneFileEpInfo.SubAlreadyDownloadedList = append(oneFileEpInfo.SubAlreadyDownloadedList, subInfo)
  365. }
  366. }
  367. EpisodeDict[epsKey] = oneFileEpInfo
  368. } else {
  369. // 存在则跳过
  370. return
  371. }
  372. return
  373. }