sub_timeline_fixer_helper.go 12 KB

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