downloader.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. package main
  2. import (
  3. "github.com/allanpk716/ChineseSubFinder/common"
  4. "github.com/allanpk716/ChineseSubFinder/mark_system"
  5. "github.com/allanpk716/ChineseSubFinder/model"
  6. "github.com/allanpk716/ChineseSubFinder/series_helper"
  7. "github.com/allanpk716/ChineseSubFinder/sub_supplier"
  8. "github.com/allanpk716/ChineseSubFinder/sub_supplier/shooter"
  9. "github.com/allanpk716/ChineseSubFinder/sub_supplier/subhd"
  10. "github.com/allanpk716/ChineseSubFinder/sub_supplier/xunlei"
  11. "github.com/allanpk716/ChineseSubFinder/sub_supplier/zimuku"
  12. "github.com/go-rod/rod/lib/utils"
  13. "github.com/sirupsen/logrus"
  14. "os"
  15. "path"
  16. "path/filepath"
  17. "strings"
  18. )
  19. type Downloader struct {
  20. reqParam common.ReqParam
  21. log *logrus.Logger
  22. topic int // 最多能够下载 Top 几的字幕,每一个网站
  23. mk *mark_system.MarkingSystem // MarkingSystem
  24. }
  25. func NewDownloader(_reqParam ...common.ReqParam) *Downloader {
  26. var downloader Downloader
  27. downloader.log = model.GetLogger()
  28. downloader.topic = common.DownloadSubsPerSite
  29. if len(_reqParam) > 0 {
  30. downloader.reqParam = _reqParam[0]
  31. if downloader.reqParam.Topic > 0 && downloader.reqParam.Topic != downloader.topic {
  32. downloader.topic = downloader.reqParam.Topic
  33. }
  34. }
  35. var sitesSequence = make([]string, 0)
  36. // TODO 这里写固定了抉择字幕的顺序
  37. sitesSequence = append(sitesSequence, common.SubSiteZiMuKu)
  38. sitesSequence = append(sitesSequence, common.SubSiteSubHd)
  39. sitesSequence = append(sitesSequence, common.SubSiteXunLei)
  40. sitesSequence = append(sitesSequence, common.SubSiteShooter)
  41. downloader.mk = mark_system.NewMarkingSystem(sitesSequence)
  42. return &downloader
  43. }
  44. func (d Downloader) DownloadSub4Movie(dir string) error {
  45. defer func() {
  46. // 抉择完毕,需要清理缓存目录
  47. err := model.ClearTmpFolder()
  48. if err != nil {
  49. d.log.Error(err)
  50. }
  51. }()
  52. nowVideoList, err := model.SearchMatchedVideoFile(dir)
  53. if err != nil {
  54. return err
  55. }
  56. // 构建每个字幕站点下载者的实例
  57. var subSupplierHub *sub_supplier.SubSupplierHub
  58. subSupplierHub = sub_supplier.NewSubSupplierHub(shooter.NewSupplier(d.reqParam),
  59. subhd.NewSupplier(d.reqParam),
  60. xunlei.NewSupplier(d.reqParam),
  61. zimuku.NewSupplier(d.reqParam),
  62. )
  63. // TODO 后续再改为每个视频以上的流程都是一个 channel 来做(目前做不了,得重构缓存字幕的方式,不然会出问题),并且需要控制在一个并发量之下(很可能没必要,毕竟要在弱鸡机器上挂机用的)
  64. // 一个视频文件同时多个站点查询,阻塞完毕后,在进行下一个
  65. for i, oneVideoFullPath := range nowVideoList {
  66. // 字幕都下载缓存好了,需要抉择存哪一个,优先选择中文双语的,然后到中文
  67. organizeSubFiles, err := subSupplierHub.DownloadSub4Movie(oneVideoFullPath, i)
  68. if err != nil {
  69. d.log.Errorln("subSupplierHub.DownloadSub4Movie", oneVideoFullPath ,err)
  70. continue
  71. }
  72. d.oneVideoSelectBestSub(oneVideoFullPath, organizeSubFiles)
  73. // -----------------------------------------------------
  74. }
  75. return nil
  76. }
  77. func (d Downloader) DownloadSub4Series(dir string) error {
  78. defer func() {
  79. // 抉择完毕,需要清理缓存目录
  80. err := model.ClearTmpFolder()
  81. if err != nil {
  82. d.log.Error(err)
  83. }
  84. }()
  85. // 构建每个字幕站点下载者的实例
  86. var subSupplierHub *sub_supplier.SubSupplierHub
  87. subSupplierHub = sub_supplier.NewSubSupplierHub(zimuku.NewSupplier(d.reqParam),
  88. //shooter.NewSupplier(d.reqParam),
  89. //subhd.NewSupplier(d.reqParam),
  90. //xunlei.NewSupplier(d.reqParam),
  91. )
  92. // 遍历连续剧总目录下的第一层目录
  93. seriesDirList, err := series_helper.GetSeriesList(dir)
  94. if err != nil {
  95. return err
  96. }
  97. for i, oneSeriesPath := range seriesDirList {
  98. // 这里拿到了这一部连续剧的所有的剧集信息,以及所有下载到的字幕信息
  99. seriesInfo, organizeSubFiles, err := subSupplierHub.DownloadSub4Series(oneSeriesPath, i)
  100. if err != nil {
  101. d.log.Errorln("subSupplierHub.DownloadSub4Series", oneSeriesPath ,err)
  102. return err
  103. }
  104. // 只针对需要下载字幕的视频进行字幕的选择保存
  105. for epsKey, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
  106. // 匹配对应的 Eps 去处理
  107. d.oneVideoSelectBestSub(episodeInfo.FileFullPath, organizeSubFiles[epsKey])
  108. }
  109. // 这里会拿到一份季度字幕的列表比如,S1E0 S2E0 S3E0
  110. d.saveFullSeasonSub(seriesInfo, organizeSubFiles)
  111. }
  112. return nil
  113. }
  114. // oneVideoSelectBestSub 一个视频,选择最佳的一个字幕(也可以保存所有网站第一个最佳字幕)
  115. func (d Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFiles []string) {
  116. var err error
  117. // 得到目标视频文件的根目录
  118. videoRootPath := filepath.Dir(oneVideoFullPath)
  119. // -------------------------------------------------
  120. // 调试缓存,把下载好的字幕写到对应的视频目录下,方便调试
  121. if d.reqParam.DebugMode == true {
  122. err = d.copySubFile2DesFolder(videoRootPath, organizeSubFiles)
  123. if err != nil {
  124. d.log.Errorln("copySubFile2DesFolder", err)
  125. }
  126. }
  127. // -------------------------------------------------
  128. if d.reqParam.SaveMultiSub == false {
  129. // 选择最优的一个字幕
  130. var finalSubFile *common.SubParserFileInfo
  131. finalSubFile = d.mk.SelectOneSubFile(organizeSubFiles)
  132. if finalSubFile == nil {
  133. d.log.Warnln("Found", len(organizeSubFiles), " subtitles but not one fit:", oneVideoFullPath)
  134. return
  135. }
  136. // 找到了,写入文件
  137. err = d.writeSubFile2VideoPath(oneVideoFullPath, *finalSubFile, "")
  138. if err != nil {
  139. d.log.Errorln("SaveMultiSub:", d.reqParam.SaveMultiSub, "writeSubFile2VideoPath:", err)
  140. return
  141. }
  142. } else {
  143. // 每个网站 Top1 的字幕
  144. siteNames, finalSubFiles := d.mk.SelectEachSiteTop1SubFile(organizeSubFiles)
  145. if len(siteNames) < 0 {
  146. d.log.Warnln("SelectEachSiteTop1SubFile found none sub file")
  147. return
  148. }
  149. for i, file := range finalSubFiles {
  150. err = d.writeSubFile2VideoPath(oneVideoFullPath, file, siteNames[i])
  151. if err != nil {
  152. d.log.Errorln("SaveMultiSub:", d.reqParam.SaveMultiSub, "writeSubFile2VideoPath:", err)
  153. return
  154. }
  155. }
  156. }
  157. }
  158. // saveFullSeasonSub 这里就需要单独存储到连续剧每一季的文件夹的特殊文件夹中
  159. func (d Downloader) saveFullSeasonSub(seriesInfo *common.SeriesInfo, organizeSubFiles map[string][]string) {
  160. for _, season := range seriesInfo.SeasonDict {
  161. epsKey := model.GetEpisodeKeyName(season, 0)
  162. subs, ok := organizeSubFiles[epsKey]
  163. if ok == false {
  164. continue
  165. }
  166. for _, sub := range subs {
  167. subFileName := filepath.Base(sub)
  168. newSeasonSubRootPath := path.Join(seriesInfo.DirPath, epsKey)
  169. _ = os.MkdirAll(newSeasonSubRootPath, os.ModePerm)
  170. newSubFullPath := path.Join(newSeasonSubRootPath, subFileName)
  171. err := os.Rename(sub, newSubFullPath)
  172. if err != nil {
  173. d.log.Errorln("saveFullSeasonSub", subFileName, err)
  174. continue
  175. }
  176. }
  177. }
  178. }
  179. // 在前面需要进行语言的筛选、排序,这里仅仅是存储
  180. func (d Downloader) writeSubFile2VideoPath(videoFileFullPath string, finalSubFile common.SubParserFileInfo, extraSubPreName string) error {
  181. videoRootPath := filepath.Dir(videoFileFullPath)
  182. embyLanExtName := model.Lang2EmbyName(finalSubFile.Lang)
  183. // 构建视频文件加 emby 的字幕预研要求名称
  184. videoFileNameWithOutExt := strings.ReplaceAll(filepath.Base(videoFileFullPath),
  185. filepath.Ext(videoFileFullPath), "")
  186. if extraSubPreName != "" {
  187. extraSubPreName = "[" + extraSubPreName +"]"
  188. }
  189. subNewName := videoFileNameWithOutExt + embyLanExtName + extraSubPreName + finalSubFile.Ext
  190. desSubFullPath := path.Join(videoRootPath, subNewName)
  191. // 最后写入字幕
  192. err := utils.OutputFile(desSubFullPath, finalSubFile.Data)
  193. if err != nil {
  194. return err
  195. }
  196. d.log.Infoln("OrgSubName:", finalSubFile.Name)
  197. d.log.Infoln("SubDownAt:", desSubFullPath)
  198. return nil
  199. }
  200. // copySubFile2DesFolder 拷贝字幕文件到目标文件夹
  201. func (d Downloader) copySubFile2DesFolder(desFolder string, subFiles []string) error {
  202. // 需要进行字幕文件的缓存
  203. // 把缓存的文件夹新建出来
  204. desFolderFullPath := path.Join(desFolder, common.SubTmpFolderName)
  205. err := os.MkdirAll(desFolderFullPath, os.ModePerm)
  206. if err != nil {
  207. return err
  208. }
  209. // 复制下载在 tmp 文件夹中的字幕文件到视频文件夹下面
  210. for _, subFile := range subFiles {
  211. newFn := path.Join(desFolderFullPath, filepath.Base(subFile))
  212. _, err = model.CopyFile(newFn, subFile)
  213. if err != nil {
  214. return err
  215. }
  216. }
  217. return nil
  218. }