SubTimelineFixerHelperEx.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. package sub_timeline_fixer
  2. import (
  3. "errors"
  4. "math"
  5. "os"
  6. "github.com/allanpk716/ChineseSubFinder/pkg/types/subparser"
  7. "github.com/allanpk716/ChineseSubFinder/pkg/ffmpeg_helper"
  8. "github.com/allanpk716/ChineseSubFinder/pkg/logic/sub_parser/ass"
  9. "github.com/allanpk716/ChineseSubFinder/pkg/logic/sub_parser/srt"
  10. "github.com/allanpk716/ChineseSubFinder/pkg/my_util"
  11. "github.com/allanpk716/ChineseSubFinder/pkg/settings"
  12. "github.com/allanpk716/ChineseSubFinder/pkg/sub_parser_hub"
  13. "github.com/allanpk716/ChineseSubFinder/pkg/sub_timeline_fixer"
  14. "github.com/allanpk716/ChineseSubFinder/pkg/vad"
  15. "github.com/emirpasic/gods/maps/treemap"
  16. "github.com/emirpasic/gods/utils"
  17. "github.com/sirupsen/logrus"
  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.ProcessByAudioFile(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.ProcessBySubFile(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("TimeLine Fix -- Score:", pipeResultMax.Score, srcSubFPath)
  133. s.log.Infoln("Fix Offset:", pipeResultMax.GetOffsetTime(), srcSubFPath)
  134. s.log.Infoln("BackUp Org SubFile:", pipeResultMax.GetOffsetTime(), srcSubFPath+sub_timeline_fixer.BackUpExt)
  135. return nil
  136. }
  137. func (s SubTimelineFixerHelperEx) ProcessBySubFileInfo(infoBase *subparser.FileInfo, infoSrc *subparser.FileInfo) (bool, *subparser.FileInfo, sub_timeline_fixer.PipeResult, error) {
  138. // ---------------------------------------------------------------------------------------
  139. pipeResult, err := s.timelineFixPipeLine.CalcOffsetTime(infoBase, infoSrc, nil, false)
  140. if err != nil {
  141. return false, nil, sub_timeline_fixer.PipeResult{}, err
  142. }
  143. return true, infoSrc, pipeResult, nil
  144. }
  145. func (s SubTimelineFixerHelperEx) ProcessBySubFile(baseSubFileFPath, srcSubFileFPath string) (bool, *subparser.FileInfo, sub_timeline_fixer.PipeResult, error) {
  146. bFind, infoBase, err := s.subParserHub.DetermineFileTypeFromFile(baseSubFileFPath)
  147. if err != nil {
  148. return false, nil, sub_timeline_fixer.PipeResult{}, err
  149. }
  150. if bFind == false {
  151. s.log.Warnln("ProcessBySubFile.DetermineFileTypeFromFile sub not match --", baseSubFileFPath)
  152. return false, nil, sub_timeline_fixer.PipeResult{}, nil
  153. }
  154. bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromFile(srcSubFileFPath)
  155. if err != nil {
  156. return false, nil, sub_timeline_fixer.PipeResult{}, err
  157. }
  158. if bFind == false {
  159. s.log.Warnln("ProcessBySubFile.DetermineFileTypeFromFile sub not match --", srcSubFileFPath)
  160. return false, nil, sub_timeline_fixer.PipeResult{}, nil
  161. }
  162. return s.ProcessBySubFileInfo(infoBase, infoSrc)
  163. }
  164. func (s SubTimelineFixerHelperEx) ProcessByAudioVAD(audioVADInfos []vad.VADInfo, infoSrc *subparser.FileInfo) (bool, *subparser.FileInfo, sub_timeline_fixer.PipeResult, error) {
  165. // ---------------------------------------------------------------------------------------
  166. pipeResult, err := s.timelineFixPipeLine.CalcOffsetTime(nil, infoSrc, audioVADInfos, false)
  167. if err != nil {
  168. return false, nil, sub_timeline_fixer.PipeResult{}, err
  169. }
  170. return true, infoSrc, pipeResult, nil
  171. }
  172. func (s SubTimelineFixerHelperEx) ProcessByAudioFile(baseAudioFileFPath, srcSubFileFPath string) (bool, *subparser.FileInfo, sub_timeline_fixer.PipeResult, error) {
  173. audioVADInfos, err := vad.GetVADInfoFromAudio(vad.AudioInfo{
  174. FileFullPath: baseAudioFileFPath,
  175. SampleRate: 16000,
  176. BitDepth: 16,
  177. }, true)
  178. if err != nil {
  179. return false, nil, sub_timeline_fixer.PipeResult{}, err
  180. }
  181. bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromFile(srcSubFileFPath)
  182. if err != nil {
  183. return false, nil, sub_timeline_fixer.PipeResult{}, err
  184. }
  185. if bFind == false {
  186. s.log.Warnln("ProcessByAudioFile.DetermineFileTypeFromFile sub not match --", srcSubFileFPath)
  187. return false, nil, sub_timeline_fixer.PipeResult{}, nil
  188. }
  189. return s.ProcessByAudioVAD(audioVADInfos, infoSrc)
  190. }
  191. func (s SubTimelineFixerHelperEx) IsVideoCanExportSubtitleAndAudio(videoFileFullPath string) (bool, []vad.VADInfo, *subparser.FileInfo, error) {
  192. // 先尝试获取内置字幕的信息
  193. bok, ffmpegInfo, err := s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.SubtitleAndAudio)
  194. if err != nil {
  195. return false, nil, nil, err
  196. }
  197. if bok == false {
  198. return false, nil, nil, nil
  199. }
  200. // ---------------------------------------------------------------------------------------
  201. // 音频
  202. if len(ffmpegInfo.AudioInfoList) <= 0 {
  203. return false, nil, nil, nil
  204. }
  205. audioVADInfos, err := vad.GetVADInfoFromAudio(vad.AudioInfo{
  206. FileFullPath: ffmpegInfo.AudioInfoList[0].FullPath,
  207. SampleRate: 16000,
  208. BitDepth: 16,
  209. }, true)
  210. if err != nil {
  211. return false, nil, nil, err
  212. }
  213. // ---------------------------------------------------------------------------------------
  214. // 字幕
  215. if len(ffmpegInfo.SubtitleInfoList) <= 0 {
  216. return false, nil, nil, nil
  217. }
  218. // 使用内置的字幕进行时间轴的校正,这里需要考虑一个问题,内置的字幕可能是有问题的(先考虑一种,就是字幕的长度不对,是一小段的)
  219. // 那么就可以比较多个内置字幕的大小选择大的去使用
  220. // 如果有多个内置的字幕,还是要判断下的,选体积最大的那个吧
  221. fileSizes := treemap.NewWith(utils.Int64Comparator)
  222. for index, info := range ffmpegInfo.SubtitleInfoList {
  223. fi, err := os.Stat(info.FullPath)
  224. if err != nil {
  225. fileSizes.Put(0, index)
  226. } else {
  227. fileSizes.Put(fi.Size(), index)
  228. }
  229. }
  230. _, index := fileSizes.Max()
  231. baseSubFPath := ffmpegInfo.SubtitleInfoList[index.(int)].FullPath
  232. bFind, infoBase, err := s.subParserHub.DetermineFileTypeFromFile(baseSubFPath)
  233. if err != nil {
  234. return false, nil, nil, err
  235. }
  236. if bFind == false {
  237. return false, nil, nil, nil
  238. }
  239. // ---------------------------------------------------------------------------------------
  240. return true, audioVADInfos, infoBase, nil
  241. }
  242. func (s SubTimelineFixerHelperEx) IsMatchBySubFile(audioVADInfos []vad.VADInfo, infoBase *subparser.FileInfo, srcSubFileFPath string, minScore float64, offsetRange float64) (bool, float64, float64, float64, float64, error) {
  243. bFind, srcBase, err := s.subParserHub.DetermineFileTypeFromFile(srcSubFileFPath)
  244. if err != nil {
  245. return false, 0, 0, 0, 0, err
  246. }
  247. if bFind == false {
  248. return false, 0, 0, 0, 0, nil
  249. }
  250. // ---------------------------------------------------------------------------------------
  251. // 音频
  252. s.log.Infoln("IsMatchBySubFile:", srcSubFileFPath)
  253. bProcess, _, pipeResultMaxAudio, err := s.ProcessByAudioVAD(audioVADInfos, srcBase)
  254. if err != nil {
  255. return false, 0, 0, 0, 0, err
  256. }
  257. if bProcess == false {
  258. return false, 0, 0, 0, 0, nil
  259. }
  260. // ---------------------------------------------------------------------------------------
  261. // 字幕
  262. bProcess, _, pipeResultMaxSub, err := s.ProcessBySubFileInfo(infoBase, srcBase)
  263. if err != nil {
  264. return false, 0, 0, 0, 0, err
  265. }
  266. if bProcess == false {
  267. return false, 0, 0, 0, 0, nil
  268. }
  269. // ---------------------------------------------------------------------------------------
  270. if pipeResultMaxAudio.Score < minScore || pipeResultMaxSub.Score < minScore {
  271. return false, pipeResultMaxAudio.Score, pipeResultMaxAudio.GetOffsetTime(), pipeResultMaxSub.Score, pipeResultMaxSub.GetOffsetTime(), nil
  272. }
  273. if math.Abs(pipeResultMaxAudio.GetOffsetTime()-pipeResultMaxSub.GetOffsetTime()) > offsetRange {
  274. return false, pipeResultMaxAudio.Score, pipeResultMaxAudio.GetOffsetTime(), pipeResultMaxSub.Score, pipeResultMaxSub.GetOffsetTime(), nil
  275. }
  276. return true, pipeResultMaxAudio.Score, pipeResultMaxAudio.GetOffsetTime(), pipeResultMaxSub.Score, pipeResultMaxSub.GetOffsetTime(), nil
  277. }
  278. func (s SubTimelineFixerHelperEx) changeTimeLineAndSave(infoSrc *subparser.FileInfo, pipeResult sub_timeline_fixer.PipeResult, desSubSaveFPath string) error {
  279. /*
  280. 修复的字幕先存放到缓存目录,然后需要把原有的字幕进行“备份”,改名,然后再替换过来
  281. */
  282. subFileName := desSubSaveFPath + sub_timeline_fixer.TmpExt
  283. if my_util.IsFile(subFileName) == true {
  284. err := os.Remove(subFileName)
  285. if err != nil {
  286. return err
  287. }
  288. }
  289. _, err := s.timelineFixPipeLine.FixSubFileTimeline(infoSrc, pipeResult.ScaledFileInfo, pipeResult.GetOffsetTime(), subFileName)
  290. if err != nil {
  291. return err
  292. }
  293. if my_util.IsFile(desSubSaveFPath+sub_timeline_fixer.BackUpExt) == true {
  294. err = os.Remove(desSubSaveFPath + sub_timeline_fixer.BackUpExt)
  295. if err != nil {
  296. return err
  297. }
  298. }
  299. err = os.Rename(desSubSaveFPath, desSubSaveFPath+sub_timeline_fixer.BackUpExt)
  300. if err != nil {
  301. return err
  302. }
  303. err = os.Rename(subFileName, desSubSaveFPath)
  304. if err != nil {
  305. return err
  306. }
  307. return nil
  308. }