SubTimelineFixerHelperEx.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package sub_timeline_fixer
  2. import (
  3. "errors"
  4. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/ass"
  5. "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/srt"
  6. "github.com/allanpk716/ChineseSubFinder/internal/pkg/ffmpeg_helper"
  7. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  8. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  9. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
  10. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
  11. "github.com/allanpk716/ChineseSubFinder/internal/pkg/vad"
  12. "github.com/allanpk716/ChineseSubFinder/internal/types/subparser"
  13. "github.com/emirpasic/gods/maps/treemap"
  14. "github.com/emirpasic/gods/utils"
  15. "github.com/sirupsen/logrus"
  16. "math"
  17. "os"
  18. )
  19. type SubTimelineFixerHelperEx struct {
  20. log *logrus.Logger
  21. ffmpegHelper *ffmpeg_helper.FFMPEGHelper
  22. subParserHub *sub_parser_hub.SubParserHub
  23. timelineFixPipeLine *sub_timeline_fixer.Pipeline
  24. fixerConfig settings.TimelineFixerSettings
  25. needDownloadFFMPeg bool
  26. }
  27. func NewSubTimelineFixerHelperEx(log *logrus.Logger, fixerConfig settings.TimelineFixerSettings) *SubTimelineFixerHelperEx {
  28. fixerConfig.Check()
  29. return &SubTimelineFixerHelperEx{
  30. log: log,
  31. ffmpegHelper: ffmpeg_helper.NewFFMPEGHelper(log),
  32. subParserHub: sub_parser_hub.NewSubParserHub(log, ass.NewParser(log), srt.NewParser(log)),
  33. timelineFixPipeLine: sub_timeline_fixer.NewPipeline(fixerConfig.MaxOffsetTime),
  34. fixerConfig: fixerConfig,
  35. needDownloadFFMPeg: false,
  36. }
  37. }
  38. // Check 是否安装了 ffmpeg 和 ffprobe
  39. func (s *SubTimelineFixerHelperEx) Check() bool {
  40. version, err := s.ffmpegHelper.Version()
  41. if err != nil {
  42. s.needDownloadFFMPeg = false
  43. s.log.Errorln("Need Install ffmpeg and ffprobe !")
  44. return false
  45. }
  46. s.needDownloadFFMPeg = true
  47. s.log.Infoln(version)
  48. return true
  49. }
  50. func (s SubTimelineFixerHelperEx) Process(videoFileFullPath, srcSubFPath string) error {
  51. if s.needDownloadFFMPeg == false {
  52. s.log.Errorln("Need Install ffmpeg and ffprobe, Can't Do TimeLine Fix")
  53. return nil
  54. }
  55. var infoSrc *subparser.FileInfo
  56. var pipeResultMax sub_timeline_fixer.PipeResult
  57. bProcess := false
  58. bok := false
  59. var ffmpegInfo *ffmpeg_helper.FFMPEGInfo
  60. var err error
  61. // 先尝试获取内置字幕的信息
  62. bok, ffmpegInfo, err = s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Subtitle)
  63. if err != nil {
  64. return err
  65. }
  66. if bok == false {
  67. return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo = false Subtitle -- " + videoFileFullPath)
  68. }
  69. // 这个需要提前考虑,如果只有一个内置的字幕,且这个字幕的大小小于 2kb,那么认为这个字幕是有问题的,就直接切换到 audio 校正
  70. oneSubAndIsError := false
  71. if len(ffmpegInfo.SubtitleInfoList) == 1 {
  72. fi, err := os.Stat(ffmpegInfo.SubtitleInfoList[0].FullPath)
  73. if err != nil {
  74. oneSubAndIsError = true
  75. } else {
  76. if fi.Size() <= 2048 {
  77. oneSubAndIsError = true
  78. }
  79. }
  80. }
  81. // 内置的字幕,这里只列举一种格式出来,其实会有一个字幕的 srt 和 ass 两种格式都导出存在
  82. if ffmpegInfo.SubtitleInfoList == nil || len(ffmpegInfo.SubtitleInfoList) <= 0 || oneSubAndIsError == true {
  83. if ffmpegInfo.AudioInfoList == nil || len(ffmpegInfo.AudioInfoList) == 0 {
  84. return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo Can`t Find SubTitle And Audio To Export -- " + videoFileFullPath)
  85. }
  86. // 如果内置字幕没有,那么就需要尝试获取音频信息
  87. bok, ffmpegInfo, err = s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Audio)
  88. if err != nil {
  89. return err
  90. }
  91. if bok == false {
  92. return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo = false Audio -- " + videoFileFullPath)
  93. }
  94. // 使用音频进行时间轴的校正
  95. if len(ffmpegInfo.AudioInfoList) <= 0 {
  96. s.log.Warnln("Can`t find audio info, skip time fix --", videoFileFullPath)
  97. return nil
  98. }
  99. bProcess, infoSrc, pipeResultMax, err = s.processByAudio(ffmpegInfo.AudioInfoList[0].FullPath, srcSubFPath)
  100. if err != nil {
  101. return err
  102. }
  103. } else {
  104. // 使用内置的字幕进行时间轴的校正,这里需要考虑一个问题,内置的字幕可能是有问题的(先考虑一种,就是字幕的长度不对,是一小段的)
  105. // 那么就可以比较多个内置字幕的大小选择大的去使用
  106. // 如果有多个内置的字幕,还是要判断下的,选体积最大的那个吧
  107. fileSizes := treemap.NewWith(utils.Int64Comparator)
  108. for index, info := range ffmpegInfo.SubtitleInfoList {
  109. fi, err := os.Stat(info.FullPath)
  110. if err != nil {
  111. fileSizes.Put(0, index)
  112. } else {
  113. fileSizes.Put(fi.Size(), index)
  114. }
  115. }
  116. _, index := fileSizes.Max()
  117. baseSubFPath := ffmpegInfo.SubtitleInfoList[index.(int)].FullPath
  118. bProcess, infoSrc, pipeResultMax, err = s.processBySub(baseSubFPath, srcSubFPath)
  119. if err != nil {
  120. return err
  121. }
  122. }
  123. // 开始调整字幕时间轴
  124. if bProcess == false || math.Abs(pipeResultMax.GetOffsetTime()) < s.fixerConfig.MinOffset {
  125. s.log.Infoln("Skip TimeLine Fix -- OffsetTime:", pipeResultMax.GetOffsetTime(), srcSubFPath)
  126. return nil
  127. }
  128. err = s.changeTimeLineAndSave(infoSrc, pipeResultMax, srcSubFPath)
  129. if err != nil {
  130. return err
  131. }
  132. s.log.Infoln("Fix Offset:", pipeResultMax.GetOffsetTime(), srcSubFPath)
  133. s.log.Infoln("BackUp Org SubFile:", pipeResultMax.GetOffsetTime(), srcSubFPath+sub_timeline_fixer.BackUpExt)
  134. return nil
  135. }
  136. func (s SubTimelineFixerHelperEx) processBySub(baseSubFileFPath, srcSubFileFPath string) (bool, *subparser.FileInfo, sub_timeline_fixer.PipeResult, error) {
  137. bFind, infoBase, err := s.subParserHub.DetermineFileTypeFromFile(baseSubFileFPath)
  138. if err != nil {
  139. return false, nil, sub_timeline_fixer.PipeResult{}, err
  140. }
  141. if bFind == false {
  142. s.log.Warnln("processBySub.DetermineFileTypeFromFile sub not match --", baseSubFileFPath)
  143. return false, nil, sub_timeline_fixer.PipeResult{}, nil
  144. }
  145. bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromFile(srcSubFileFPath)
  146. if err != nil {
  147. return false, nil, sub_timeline_fixer.PipeResult{}, err
  148. }
  149. if bFind == false {
  150. s.log.Warnln("processBySub.DetermineFileTypeFromFile sub not match --", srcSubFileFPath)
  151. return false, nil, sub_timeline_fixer.PipeResult{}, nil
  152. }
  153. // ---------------------------------------------------------------------------------------
  154. pipeResult, err := s.timelineFixPipeLine.CalcOffsetTime(infoBase, infoSrc, nil, false)
  155. if err != nil {
  156. return false, nil, sub_timeline_fixer.PipeResult{}, err
  157. }
  158. return true, infoSrc, pipeResult, nil
  159. }
  160. func (s SubTimelineFixerHelperEx) processByAudio(baseAudioFileFPath, srcSubFileFPath string) (bool, *subparser.FileInfo, sub_timeline_fixer.PipeResult, error) {
  161. audioVADInfos, err := vad.GetVADInfoFromAudio(vad.AudioInfo{
  162. FileFullPath: baseAudioFileFPath,
  163. SampleRate: 16000,
  164. BitDepth: 16,
  165. }, true)
  166. if err != nil {
  167. return false, nil, sub_timeline_fixer.PipeResult{}, err
  168. }
  169. bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromFile(srcSubFileFPath)
  170. if err != nil {
  171. return false, nil, sub_timeline_fixer.PipeResult{}, err
  172. }
  173. if bFind == false {
  174. s.log.Warnln("processByAudio.DetermineFileTypeFromFile sub not match --", srcSubFileFPath)
  175. return false, nil, sub_timeline_fixer.PipeResult{}, nil
  176. }
  177. // ---------------------------------------------------------------------------------------
  178. pipeResult, err := s.timelineFixPipeLine.CalcOffsetTime(nil, infoSrc, audioVADInfos, false)
  179. if err != nil {
  180. return false, nil, sub_timeline_fixer.PipeResult{}, err
  181. }
  182. return true, infoSrc, pipeResult, nil
  183. }
  184. func (s SubTimelineFixerHelperEx) changeTimeLineAndSave(infoSrc *subparser.FileInfo, pipeResult sub_timeline_fixer.PipeResult, desSubSaveFPath string) error {
  185. /*
  186. 修复的字幕先存放到缓存目录,然后需要把原有的字幕进行“备份”,改名,然后再替换过来
  187. */
  188. subFileName := desSubSaveFPath + sub_timeline_fixer.TmpExt
  189. if my_util.IsFile(subFileName) == true {
  190. err := os.Remove(subFileName)
  191. if err != nil {
  192. return err
  193. }
  194. }
  195. _, err := s.timelineFixPipeLine.FixSubFileTimeline(infoSrc, pipeResult.ScaledFileInfo, pipeResult.GetOffsetTime(), subFileName)
  196. if err != nil {
  197. return err
  198. }
  199. if my_util.IsFile(desSubSaveFPath+sub_timeline_fixer.BackUpExt) == true {
  200. err = os.Remove(desSubSaveFPath + sub_timeline_fixer.BackUpExt)
  201. if err != nil {
  202. return err
  203. }
  204. }
  205. err = os.Rename(desSubSaveFPath, desSubSaveFPath+sub_timeline_fixer.BackUpExt)
  206. if err != nil {
  207. return err
  208. }
  209. err = os.Rename(subFileName, desSubSaveFPath)
  210. if err != nil {
  211. return err
  212. }
  213. return nil
  214. }