SubTimelineFixerHelperEx.go 8.6 KB

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