sub_timeline_fixer_helper.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. package sub_timeline_fixer
  2. import (
  3. "fmt"
  4. "github.com/allanpk716/ChineseSubFinder/internal/common"
  5. "github.com/allanpk716/ChineseSubFinder/internal/ifaces"
  6. "github.com/allanpk716/ChineseSubFinder/internal/logic/emby_helper"
  7. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/ass"
  8. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/srt"
  9. "github.com/allanpk716/ChineseSubFinder/internal/pkg"
  10. "github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
  11. formatterEmby "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/emby"
  12. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/normal"
  13. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
  14. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
  15. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
  16. "github.com/allanpk716/ChineseSubFinder/internal/types"
  17. "github.com/allanpk716/ChineseSubFinder/internal/types/emby"
  18. "github.com/allanpk716/ChineseSubFinder/internal/types/sub_timeline_fiexer"
  19. "os"
  20. "path"
  21. "path/filepath"
  22. "strings"
  23. "time"
  24. )
  25. type SubTimelineFixerHelper struct {
  26. embyHelper *emby_helper.EmbyHelper
  27. EmbyConfig emby.EmbyConfig
  28. FixerConfig sub_timeline_fiexer.SubTimelineFixerConfig
  29. subParserHub *sub_parser_hub.SubParserHub
  30. subTimelineFixer *sub_timeline_fixer.SubTimelineFixer
  31. formatter map[string]ifaces.ISubFormatter
  32. threads int
  33. timeOut time.Duration
  34. }
  35. func NewSubTimelineFixerHelper(embyConfig emby.EmbyConfig, subTimelineFixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixerHelper {
  36. sub := SubTimelineFixerHelper{
  37. EmbyConfig: embyConfig,
  38. FixerConfig: subTimelineFixerConfig,
  39. embyHelper: emby_helper.NewEmbyHelper(embyConfig),
  40. subParserHub: sub_parser_hub.NewSubParserHub(ass.NewParser(), srt.NewParser()),
  41. subTimelineFixer: sub_timeline_fixer.NewSubTimelineFixer(subTimelineFixerConfig),
  42. formatter: make(map[string]ifaces.ISubFormatter),
  43. threads: 6,
  44. timeOut: 60 * time.Second,
  45. }
  46. // TODO 如果字幕格式新增了实现,这里也需要添加对应的实例
  47. // 初始化支持的 formatter
  48. // normal
  49. sub.formatter = make(map[string]ifaces.ISubFormatter)
  50. normalM := normal.NewFormatter()
  51. sub.formatter[normalM.GetFormatterName()] = normalM
  52. // emby
  53. embyM := formatterEmby.NewFormatter()
  54. sub.formatter[embyM.GetFormatterName()] = embyM
  55. return &sub
  56. }
  57. func (s SubTimelineFixerHelper) FixRecentlyItemsSubTimeline(movieRootDir, seriesRootDir string) error {
  58. // 首先得开启,不然就直接跳过不执行
  59. if s.EmbyConfig.FixTimeLine == false {
  60. return nil
  61. }
  62. movieList, seriesList, err := s.embyHelper.GetRecentlyAddVideoList(movieRootDir, seriesRootDir)
  63. if err != nil {
  64. return err
  65. }
  66. // 先做电影的字幕校正、然后才是连续剧的
  67. for _, info := range movieList {
  68. // path.Dir 在 Windows 有梗,所以换个方式获取路径
  69. videoRootPath := filepath.Dir(info.VideoFileFullPath)
  70. err = s.fixOneVideoSub(info.VideoInfo.Id, videoRootPath)
  71. if err != nil {
  72. return err
  73. }
  74. }
  75. for _, infos := range seriesList {
  76. for _, info := range infos {
  77. // path.Dir 在 Windows 有梗,所以换个方式获取路径
  78. videoRootPath := filepath.Dir(info.VideoFileFullPath)
  79. err = s.fixOneVideoSub(info.VideoInfo.Id, videoRootPath)
  80. if err != nil {
  81. return err
  82. }
  83. }
  84. }
  85. return nil
  86. }
  87. func (s SubTimelineFixerHelper) fixOneVideoSub(videoId string, videoRootPath string) error {
  88. // internalEngSub 默认第一个是 srt 然后第二个是 ass,就不要去遍历了
  89. found, internalEngSub, exCh_EngSub, err := s.embyHelper.GetInternalEngSubAndExChineseEnglishSub(videoId)
  90. if err != nil {
  91. return err
  92. }
  93. if found == false {
  94. return nil
  95. }
  96. // 从外置双语(中英)字幕中找对对应的内置 srt 字幕进行匹配比较
  97. for _, exSubInfo := range exCh_EngSub {
  98. inSelectSubIndex := 1
  99. if exSubInfo.Ext == common.SubExtSRT {
  100. inSelectSubIndex = 0
  101. }
  102. // 修正过的字幕有标记,将不会再次修复
  103. if strings.Contains(exSubInfo.FileName, sub_timeline_fixer.FixMask) == true {
  104. continue
  105. }
  106. bFound, subFixInfos, err := s.fixSubTimeline(internalEngSub[inSelectSubIndex], exSubInfo)
  107. if err != nil {
  108. return err
  109. }
  110. if bFound == false {
  111. continue
  112. }
  113. // 调试的时候用
  114. if videoRootPath == "" {
  115. continue
  116. }
  117. for _, info := range subFixInfos {
  118. // 写入 fix 后的字幕文件覆盖之前的字幕文件
  119. desFixedSubFullName := path.Join(videoRootPath, info.FileName)
  120. log_helper.GetLogger().Debugln("Sub Timeline fixed:", desFixedSubFullName)
  121. continue
  122. //err = s.saveSubFile(desFixedSubFullName, info.FixContent)
  123. //if err != nil {
  124. // return err
  125. //}
  126. //log_helper.GetLogger().Debugln("Sub Timeline fixed:", desFixedSubFullName)
  127. }
  128. }
  129. return nil
  130. }
  131. func (s SubTimelineFixerHelper) fixSubTimeline(enSubFile emby.SubInfo, ch_enSubFile emby.SubInfo) (bool, []sub_timeline_fixer.SubFixInfo, error) {
  132. bFind, infoBase, err := s.subParserHub.DetermineFileTypeFromBytes(enSubFile.Content, enSubFile.Ext)
  133. if err != nil {
  134. return false, nil, err
  135. }
  136. if bFind == false {
  137. return false, nil, nil
  138. }
  139. infoBase.Name = enSubFile.FileName
  140. /*
  141. 这里发现一个梗,内置的英文字幕导出的时候,有可能需要合并多个 Dialogue,见
  142. internal/pkg/sub_helper/sub_helper.go 中 MergeMultiDialogue4EngSubtitle 的实现
  143. */
  144. sub_helper.MergeMultiDialogue4EngSubtitle(infoBase)
  145. bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromBytes(ch_enSubFile.Content, ch_enSubFile.Ext)
  146. if err != nil {
  147. return false, nil, err
  148. }
  149. if bFind == false {
  150. return false, nil, nil
  151. }
  152. infoSrc.Name = ch_enSubFile.FileName
  153. /*
  154. 这里发现一个梗,内置的英文字幕导出的时候,有可能需要合并多个 Dialogue,见
  155. internal/pkg/sub_helper/sub_helper.go 中 MergeMultiDialogue4EngSubtitle 的实现
  156. */
  157. sub_helper.MergeMultiDialogue4EngSubtitle(infoSrc)
  158. infoBaseNameWithOutExt := strings.Replace(infoBase.Name, path.Ext(infoBase.Name), "", -1)
  159. //infoSrcNameWithOutExt := strings.Replace(infoSrc.Name, path.Ext(infoSrc.Name), "", -1)
  160. // 把原始的文件缓存下来,新建缓存的文件夹
  161. cacheTmpPath := path.Join(tmpSubFixCacheFolder, infoBaseNameWithOutExt)
  162. if pkg.IsDir(cacheTmpPath) == false {
  163. err = os.MkdirAll(cacheTmpPath, os.ModePerm)
  164. if err != nil {
  165. return false, nil, err
  166. }
  167. }
  168. // 写入内置字幕、外置字幕原始文件
  169. err = s.saveSubFile(path.Join(cacheTmpPath, infoBaseNameWithOutExt+".chinese(inside)"+infoBase.Ext), infoBase.Content)
  170. if err != nil {
  171. return false, nil, err
  172. }
  173. err = s.saveSubFile(path.Join(cacheTmpPath, infoSrc.Name), infoSrc.Content)
  174. if err != nil {
  175. return false, nil, err
  176. }
  177. bok, offsetTime, sd, err := s.subTimelineFixer.GetOffsetTime(infoBase, infoSrc, path.Join(cacheTmpPath, infoSrc.Name+"-bar.html"), path.Join(cacheTmpPath, infoSrc.Name+".log"))
  178. if offsetTime != 0 {
  179. log_helper.GetLogger().Debugln(infoSrc.Name, "offset time is", fmt.Sprintf("%f", offsetTime), "s")
  180. }
  181. // 超过 SD 阈值了
  182. if sd > s.FixerConfig.MaxStartTimeDiffSD {
  183. log_helper.GetLogger().Debugln(infoSrc.Name, "Start Time Diff SD, skip", fmt.Sprintf("%f", sd))
  184. return false, nil, nil
  185. } else {
  186. log_helper.GetLogger().Debugln(infoSrc.Name, "Start Time Diff SD", fmt.Sprintf("%f", sd))
  187. }
  188. if err != nil || bok == false {
  189. return false, nil, err
  190. }
  191. // 偏移很小就无视了
  192. if offsetTime < s.FixerConfig.MinOffset && offsetTime > -s.FixerConfig.MinOffset {
  193. log_helper.GetLogger().Debugln(infoSrc.Name, fmt.Sprintf("Min Offset Config is %f, skip ", s.FixerConfig.MinOffset), fmt.Sprintf("now is %f", offsetTime))
  194. return false, nil, nil
  195. }
  196. // 写入校准时间轴后的字幕
  197. var subFixInfos = make([]sub_timeline_fixer.SubFixInfo, 0)
  198. for _, formatter := range s.formatter {
  199. // 符合已知的字幕命名格式,不符合就跳过,都跳过也行,就不做任何操作而已
  200. bMatch, fileNameWithOutExt, subExt, subLang, extraSubName := formatter.IsMatchThisFormat(infoSrc.Name)
  201. if bMatch == false {
  202. continue
  203. }
  204. // 是否包含 default 关键词,暂时无需判断 forced
  205. hasDefault := false
  206. if strings.Contains(strings.ToLower(infoSrc.Name), types.Sub_Ext_Mark_Default) == true {
  207. hasDefault = true
  208. }
  209. // 生成对应字幕命名格式的,字幕命名。这里注意,normal 的时候, extraSubName+"-fix" 是无效的,不会被设置,也就是直接覆盖之前的字幕了。
  210. subNewName, subNewNameDefault, _ := formatter.GenerateMixSubNameBase(fileNameWithOutExt, subExt, subLang, extraSubName+sub_timeline_fixer.FixMask)
  211. desFixSubFileFullPath := ""
  212. if hasDefault == true {
  213. desFixSubFileFullPath = path.Join(cacheTmpPath, subNewNameDefault)
  214. } else {
  215. desFixSubFileFullPath = path.Join(cacheTmpPath, subNewName)
  216. }
  217. fixContent, err := s.subTimelineFixer.FixSubTimeline(infoSrc, offsetTime, desFixSubFileFullPath)
  218. if err != nil {
  219. return false, nil, err
  220. }
  221. subFixInfos = append(subFixInfos, *sub_timeline_fixer.NewSubFixInfo(infoSrc.Name, fixContent))
  222. }
  223. return true, subFixInfos, nil
  224. }
  225. func (s SubTimelineFixerHelper) saveSubFile(desSaveSubFileFullPath string, content string) error {
  226. dstFile, err := os.Create(desSaveSubFileFullPath)
  227. if err != nil {
  228. return err
  229. }
  230. defer func() {
  231. _ = dstFile.Close()
  232. }()
  233. _, err = dstFile.WriteString(content)
  234. if err != nil {
  235. return err
  236. }
  237. return nil
  238. }
  239. const tmpSubFixCacheFolder = "SubFixCache"