ffmpeg_helper.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. package ffmpeg_helper
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "github.com/allanpk716/ChineseSubFinder/internal/common"
  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/language"
  10. "github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
  11. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  12. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
  13. "github.com/tidwall/gjson"
  14. "os"
  15. "os/exec"
  16. "path/filepath"
  17. "strings"
  18. )
  19. type FFMPEGHelper struct {
  20. SubParserHub *sub_parser_hub.SubParserHub // 字幕内容的解析器
  21. }
  22. func NewFFMPEGHelper() *FFMPEGHelper {
  23. return &FFMPEGHelper{
  24. SubParserHub: sub_parser_hub.NewSubParserHub(ass.NewParser(), srt.NewParser()),
  25. }
  26. }
  27. // Version 获取版本信息,如果不存在 FFMPEG 和 ffprobe 则报错
  28. func (f FFMPEGHelper) Version() (string, error) {
  29. outMsg0, err := f.getVersion("ffmpeg")
  30. if err != nil {
  31. return "", err
  32. }
  33. outMsg1, err := f.getVersion("ffprobe")
  34. if err != nil {
  35. return "", err
  36. }
  37. return outMsg0 + "\r\n" + outMsg1, nil
  38. }
  39. // GetFFMPEGInfo 获取 视频的 FFMPEG 信息,包含音频和字幕
  40. // 优先会导出 中、英、日、韩 类型的,字幕如果没有语言类型,则也导出,然后需要额外的字幕语言的判断去辅助标记(读取文件内容)
  41. func (f *FFMPEGHelper) GetFFMPEGInfo(videoFileFullPath string, exportType ExportType) (bool, *FFMPEGInfo, error) {
  42. const args = "-v error -show_format -show_streams -print_format json"
  43. cmdArgs := strings.Fields(args)
  44. cmdArgs = append(cmdArgs, videoFileFullPath)
  45. cmd := exec.Command("ffprobe", cmdArgs...)
  46. buf := bytes.NewBufferString("")
  47. //指定输出位置
  48. cmd.Stderr = buf
  49. cmd.Stdout = buf
  50. err := cmd.Start()
  51. if err != nil {
  52. return false, nil, err
  53. }
  54. err = cmd.Wait()
  55. if err != nil {
  56. return false, nil, err
  57. }
  58. // 解析得到的字符串反馈
  59. bok, ffMPEGInfo := f.parseJsonString2GetFFProbeInfo(videoFileFullPath, buf.String())
  60. if bok == false {
  61. return false, nil, nil
  62. }
  63. nowCacheFolderPath, err := ffMPEGInfo.GetCacheFolderFPath()
  64. if err != nil {
  65. return false, nil, err
  66. }
  67. // 在函数调用完毕后,判断是否需要清理
  68. defer func() {
  69. if bok == false && ffMPEGInfo != nil {
  70. err := os.RemoveAll(nowCacheFolderPath)
  71. if err != nil {
  72. log_helper.GetLogger().Errorln("GetFFMPEGInfo - RemoveAll", err.Error())
  73. return
  74. }
  75. }
  76. }()
  77. // 查找当前这个视频外置字幕列表
  78. err = ffMPEGInfo.GetExternalSubInfos(f.SubParserHub)
  79. if err != nil {
  80. return false, nil, err
  81. }
  82. // 判断这个视频是否已经导出过内置的字幕和音频文件了
  83. if ffMPEGInfo.IsExported(exportType) == false {
  84. // 说明缓存不存在,需要导出,这里需要注意,如果导出失败了,这个文件夹要清理掉
  85. if my_util.IsDir(nowCacheFolderPath) == true {
  86. // 如果存在则,先清空一个这个文件夹
  87. err = my_util.ClearFolder(nowCacheFolderPath)
  88. if err != nil {
  89. bok = false
  90. return bok, nil, err
  91. }
  92. }
  93. // 开始导出
  94. // 构建导出的命令参数
  95. exportAudioArgs, exportSubArgs := f.getAudioAndSubExportArgs(videoFileFullPath, ffMPEGInfo)
  96. // 执行导出,音频和内置的字幕
  97. execErrorString, err := f.exportAudioAndSubtitles(exportAudioArgs, exportSubArgs, exportType)
  98. if err != nil {
  99. log_helper.GetLogger().Errorln("exportAudioAndSubtitles", execErrorString)
  100. bok = false
  101. return bok, nil, err
  102. }
  103. }
  104. return bok, ffMPEGInfo, nil
  105. }
  106. // GetAudioDurationInfo 获取音频的长度信息
  107. func (f *FFMPEGHelper) GetAudioDurationInfo(audioFileFullPath string) (bool, float64, error) {
  108. const args = "-v error -show_format -show_streams -print_format json -f s16le -ac 1 -ar 16000"
  109. cmdArgs := strings.Fields(args)
  110. cmdArgs = append(cmdArgs, audioFileFullPath)
  111. cmd := exec.Command("ffprobe", cmdArgs...)
  112. buf := bytes.NewBufferString("")
  113. //指定输出位置
  114. cmd.Stderr = buf
  115. cmd.Stdout = buf
  116. err := cmd.Start()
  117. if err != nil {
  118. return false, 0, err
  119. }
  120. err = cmd.Wait()
  121. if err != nil {
  122. return false, 0, err
  123. }
  124. bok, duration := f.parseJsonString2GetAudioInfo(buf.String())
  125. if bok == false {
  126. return false, 0, errors.New("ffprobe get " + audioFileFullPath + " duration error")
  127. }
  128. return true, duration, nil
  129. }
  130. // ExportAudioAndSubArgsByTimeRange 根据输入的时间轴导出音频分段信息 "0:1:27" "28.2"
  131. func (f *FFMPEGHelper) ExportAudioAndSubArgsByTimeRange(audioFullPath, subFullPath string, startTimeString, timeLength string) (string, string, string, error) {
  132. outStartTimeString := strings.ReplaceAll(startTimeString, ":", "-")
  133. outStartTimeString = strings.ReplaceAll(outStartTimeString, ".", "#")
  134. outTimeLength := strings.ReplaceAll(timeLength, ".", "#")
  135. frontName := strings.ReplaceAll(filepath.Base(audioFullPath), filepath.Ext(audioFullPath), "")
  136. outAudioName := frontName + "_" + outStartTimeString + "_" + outTimeLength + filepath.Ext(audioFullPath)
  137. outSubName := frontName + "_" + outStartTimeString + "_" + outTimeLength + common.SubExtSRT
  138. var outAudioFullPath = filepath.Join(filepath.Dir(audioFullPath), outAudioName)
  139. var outSubFullPath = filepath.Join(filepath.Dir(audioFullPath), outSubName)
  140. // 导出音频
  141. if my_util.IsFile(outAudioFullPath) == true {
  142. err := os.Remove(outAudioFullPath)
  143. if err != nil {
  144. return "", "", "", err
  145. }
  146. }
  147. args := f.getAudioExportArgsByTimeRange(audioFullPath, startTimeString, timeLength, outAudioFullPath)
  148. execFFMPEG, err := f.execFFMPEG(args)
  149. if err != nil {
  150. return "", "", execFFMPEG, err
  151. }
  152. // 导出字幕
  153. if my_util.IsFile(outSubFullPath) == true {
  154. err := os.Remove(outSubFullPath)
  155. if err != nil {
  156. return "", "", "", err
  157. }
  158. }
  159. args = f.getSubExportArgsByTimeRange(subFullPath, startTimeString, timeLength, outSubFullPath)
  160. execFFMPEG, err = f.execFFMPEG(args)
  161. if err != nil {
  162. return "", "", execFFMPEG, err
  163. }
  164. return outAudioFullPath, outSubFullPath, "", nil
  165. }
  166. // ExportSubArgsByTimeRange 根据输入的时间轴导出字幕分段信息 "0:1:27" "28.2"
  167. func (f *FFMPEGHelper) ExportSubArgsByTimeRange(subFullPath, outName string, startTimeString, timeLength string) (string, string, error) {
  168. outStartTimeString := strings.ReplaceAll(startTimeString, ":", "-")
  169. outStartTimeString = strings.ReplaceAll(outStartTimeString, ".", "#")
  170. outTimeLength := strings.ReplaceAll(timeLength, ".", "#")
  171. frontName := strings.ReplaceAll(filepath.Base(subFullPath), filepath.Ext(subFullPath), "")
  172. outSubName := frontName + "_" + outStartTimeString + "_" + outTimeLength + "_" + outName + common.SubExtSRT
  173. var outSubFullPath = filepath.Join(filepath.Dir(subFullPath), outSubName)
  174. // 导出字幕
  175. if my_util.IsFile(outSubFullPath) == true {
  176. err := os.Remove(outSubFullPath)
  177. if err != nil {
  178. return "", "", err
  179. }
  180. }
  181. args := f.getSubExportArgsByTimeRange(subFullPath, startTimeString, timeLength, outSubFullPath)
  182. execFFMPEG, err := f.execFFMPEG(args)
  183. if err != nil {
  184. return "", execFFMPEG, err
  185. }
  186. return outSubFullPath, "", nil
  187. }
  188. // parseJsonString2GetFFProbeInfo 使用 ffprobe 获取视频的 stream 信息,从中解析出字幕和音频的索引
  189. func (f *FFMPEGHelper) parseJsonString2GetFFProbeInfo(videoFileFullPath, inputFFProbeString string) (bool, *FFMPEGInfo) {
  190. streamsValue := gjson.Get(inputFFProbeString, "streams.#")
  191. if streamsValue.Exists() == false {
  192. return false, nil
  193. }
  194. ffmpegInfo := NewFFMPEGInfo(videoFileFullPath)
  195. // 进行字幕和音频的缓存,优先当然是导出 中、英、日、韩 相关的字幕和音频
  196. // 但是如果都没得这些的时候,那么也需要导出至少一个字幕或者音频,用于字幕的校正
  197. cacheAudios := make([]AudioInfo, 0)
  198. cacheSubtitleInfos := make([]SubtitleInfo, 0)
  199. for i := 0; i < int(streamsValue.Num); i++ {
  200. oneIndex := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.index", i))
  201. oneCodecName := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.codec_name", i))
  202. oneCodecType := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.codec_type", i))
  203. oneTimeBase := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.time_base", i))
  204. oneStartTime := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.start_time", i))
  205. oneLanguage := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.tags.language", i))
  206. // 任意一个字段不存在则跳过
  207. if oneIndex.Exists() == false {
  208. continue
  209. }
  210. if oneCodecName.Exists() == false {
  211. continue
  212. }
  213. if oneCodecType.Exists() == false {
  214. continue
  215. }
  216. if oneTimeBase.Exists() == false {
  217. continue
  218. }
  219. if oneStartTime.Exists() == false {
  220. continue
  221. }
  222. // 这里需要区分是字幕还是音频
  223. if oneCodecType.String() == codecTypeSub {
  224. // 字幕
  225. // 这里非必须解析到 language 字段,把所有的都导出来,然后通过额外字幕语言判断即可
  226. oneDurationTS := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.duration_ts", i))
  227. oneDuration := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.duration", i))
  228. // 必须存在的
  229. if oneDurationTS.Exists() == false {
  230. continue
  231. }
  232. if oneDuration.Exists() == false {
  233. continue
  234. }
  235. // 非必须存在的
  236. nowLanguageString := ""
  237. if oneLanguage.Exists() == true {
  238. nowLanguageString = oneLanguage.String()
  239. // 只导出 中、英、日、韩
  240. if language.IsSupportISOString(nowLanguageString) == false {
  241. subInfo := NewSubtitleInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
  242. oneTimeBase.String(), oneStartTime.String(),
  243. int(oneDurationTS.Num), oneDuration.String(), nowLanguageString)
  244. // 不符合的也存在下来,万一,符合要求的一个都没得的时候,就需要从里面挑几个出来了
  245. cacheSubtitleInfos = append(cacheSubtitleInfos, *subInfo)
  246. continue
  247. }
  248. }
  249. subInfo := NewSubtitleInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
  250. oneTimeBase.String(), oneStartTime.String(),
  251. int(oneDurationTS.Num), oneDuration.String(), nowLanguageString)
  252. ffmpegInfo.SubtitleInfoList = append(ffmpegInfo.SubtitleInfoList, *subInfo)
  253. } else if oneCodecType.String() == codecTypeAudio {
  254. // 音频
  255. // 这里必要要能够解析到 language 字段
  256. if oneLanguage.Exists() == false {
  257. // 不符合的也存在下来,万一,符合要求的一个都没得的时候,就需要从里面挑几个出来了
  258. audioInfo := NewAudioInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
  259. oneTimeBase.String(), oneStartTime.String(), oneLanguage.String())
  260. cacheAudios = append(cacheAudios, *audioInfo)
  261. continue
  262. }
  263. // 只导出 中、英、日、韩
  264. if language.IsSupportISOString(oneLanguage.String()) == false {
  265. // 不符合的也存在下来,万一,符合要求的一个都没得的时候,就需要从里面挑几个出来了
  266. audioInfo := NewAudioInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
  267. oneTimeBase.String(), oneStartTime.String(), oneLanguage.String())
  268. cacheAudios = append(cacheAudios, *audioInfo)
  269. continue
  270. }
  271. audioInfo := NewAudioInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
  272. oneTimeBase.String(), oneStartTime.String(), oneLanguage.String())
  273. ffmpegInfo.AudioInfoList = append(ffmpegInfo.AudioInfoList, *audioInfo)
  274. } else {
  275. continue
  276. }
  277. }
  278. // 如何没有找到合适的字幕,那么就要把缓存的字幕选一个填充进去
  279. if len(ffmpegInfo.SubtitleInfoList) == 0 {
  280. if len(cacheSubtitleInfos) != 0 {
  281. ffmpegInfo.SubtitleInfoList = append(ffmpegInfo.SubtitleInfoList, cacheSubtitleInfos[0])
  282. }
  283. }
  284. // 如何没有找到合适的音频,那么就要把缓存的音频选一个填充进去
  285. if len(ffmpegInfo.AudioInfoList) == 0 {
  286. if len(cacheAudios) != 0 {
  287. ffmpegInfo.AudioInfoList = append(ffmpegInfo.AudioInfoList, cacheAudios[0])
  288. }
  289. }
  290. return true, ffmpegInfo
  291. }
  292. // parseJsonString2GetAudioInfo 获取 pcm 音频的长度
  293. func (f *FFMPEGHelper) parseJsonString2GetAudioInfo(inputFFProbeString string) (bool, float64) {
  294. durationValue := gjson.Get(inputFFProbeString, "format.duration")
  295. if durationValue.Exists() == false {
  296. return false, 0
  297. }
  298. return true, durationValue.Float()
  299. }
  300. // exportAudioAndSubtitles 导出音频和字幕文件
  301. func (f *FFMPEGHelper) exportAudioAndSubtitles(audioArgs, subArgs []string, exportType ExportType) (string, error) {
  302. // 这里导出依赖的是 ffmpeg 这个程序,需要的是构建导出的语句
  303. if exportType == SubtitleAndAudio {
  304. execErrorString, err := f.execFFMPEG(audioArgs)
  305. if err != nil {
  306. return execErrorString, err
  307. }
  308. execErrorString, err = f.execFFMPEG(subArgs)
  309. if err != nil {
  310. return execErrorString, err
  311. }
  312. } else if exportType == Audio {
  313. execErrorString, err := f.execFFMPEG(audioArgs)
  314. if err != nil {
  315. return execErrorString, err
  316. }
  317. } else if exportType == Subtitle {
  318. execErrorString, err := f.execFFMPEG(subArgs)
  319. if err != nil {
  320. return execErrorString, err
  321. }
  322. } else {
  323. return "", errors.New("FFMPEGHelper ExportType not support")
  324. }
  325. return "", nil
  326. }
  327. // execFFMPEG 执行 ffmpeg 命令
  328. func (f *FFMPEGHelper) execFFMPEG(cmds []string) (string, error) {
  329. cmd := exec.Command("ffmpeg", cmds...)
  330. buf := bytes.NewBufferString("")
  331. //指定输出位置
  332. cmd.Stderr = buf
  333. cmd.Stdout = buf
  334. err := cmd.Start()
  335. if err != nil {
  336. return buf.String(), err
  337. }
  338. err = cmd.Wait()
  339. if err != nil {
  340. return buf.String(), err
  341. }
  342. return "", nil
  343. }
  344. // getAudioAndSubExportArgs 构建从原始视频导出字幕、音频的 ffmpeg 的参数 audioArgs, subArgs
  345. func (f *FFMPEGHelper) getAudioAndSubExportArgs(videoFileFullPath string, ffmpegInfo *FFMPEGInfo) ([]string, []string) {
  346. /*
  347. 导出多个字幕
  348. ffmpeg.exe -i xx.mp4 -vn -an -map 0:7 subs-7.srt -map 0:6 subs-6.srt
  349. 导出音频,从 1m 27s 开始,导出向后的 28 s,转换为 mp3 格式
  350. ffmpeg.exe -i xx.mp4 -vn -map 0:1 -ss 00:1:27 -f mp3 -t 28 audio.mp3
  351. 导出音频,转换为 mp3 格式
  352. ffmpeg.exe -i xx.mp4 -vn -map 0:1 -f mp3 audio.mp3
  353. 导出音频,转换为 16000k 16bit 单通道 采样率的 test.pcm
  354. ffmpeg.exe -i xx.mp4 -vn -map 0:1 -ss 00:1:27 -t 28 -acodec pcm_s16le -f s16le -ac 1 -ar 16000 test.pcm
  355. 截取字幕的时间片段
  356. ffmpeg.exe -i "subs-3.srt" -ss 00:1:27 -t 28 subs-3-cut-from-org.srt
  357. */
  358. var subArgs = make([]string, 0)
  359. var audioArgs = make([]string, 0)
  360. // 基础的输入视频参数
  361. subArgs = append(subArgs, "-i")
  362. audioArgs = append(audioArgs, "-i")
  363. subArgs = append(subArgs, videoFileFullPath)
  364. audioArgs = append(audioArgs, videoFileFullPath)
  365. // 字幕导出的参数构建
  366. subArgs = append(subArgs, "-vn") // 不输出视频流
  367. subArgs = append(subArgs, "-an") // 不输出音频流
  368. nowCacheFolderPath, err := ffmpegInfo.GetCacheFolderFPath()
  369. if err != nil {
  370. log_helper.GetLogger().Errorln("getAudioAndSubExportArgs", videoFileFullPath, err.Error())
  371. return nil, nil
  372. }
  373. for _, subtitleInfo := range ffmpegInfo.SubtitleInfoList {
  374. f.addSubMapArg(&subArgs, subtitleInfo.Index,
  375. filepath.Join(nowCacheFolderPath, subtitleInfo.GetName()+common.SubExtSRT))
  376. f.addSubMapArg(&subArgs, subtitleInfo.Index,
  377. filepath.Join(nowCacheFolderPath, subtitleInfo.GetName()+common.SubExtASS))
  378. }
  379. // 音频导出的参数构建
  380. audioArgs = append(audioArgs, "-vn")
  381. for _, audioInfo := range ffmpegInfo.AudioInfoList {
  382. f.addAudioMapArg(&audioArgs, audioInfo.Index,
  383. filepath.Join(nowCacheFolderPath, audioInfo.GetName()+extPCM))
  384. }
  385. return audioArgs, subArgs
  386. }
  387. // getAudioAndSubExportArgsByTimeRange 导出某个时间范围内的音频和字幕文件文件 startTimeString 00:1:27 timeLeng 向后多少秒
  388. func (f *FFMPEGHelper) getAudioExportArgsByTimeRange(audioFullPath string, startTimeString, timeLeng, outAudioFullPath string) []string {
  389. /*
  390. ffmpeg.exe -ar 16000 -ac 1 -f s16le -i aa.pcm -ss 00:1:27 -t 28 -acodec pcm_s16le -f s16le -ac 1 -ar 16000 bb.pcm
  391. ffmpeg.exe -i aa.srt -ss 00:1:27 -t 28 bb.srt
  392. */
  393. var audioArgs = make([]string, 0)
  394. // 指定读取的音频文件编码格式
  395. audioArgs = append(audioArgs, "-ar")
  396. audioArgs = append(audioArgs, "16000")
  397. audioArgs = append(audioArgs, "-ac")
  398. audioArgs = append(audioArgs, "1")
  399. audioArgs = append(audioArgs, "-f")
  400. audioArgs = append(audioArgs, "s16le")
  401. audioArgs = append(audioArgs, "-i")
  402. audioArgs = append(audioArgs, audioFullPath)
  403. audioArgs = append(audioArgs, "-ss")
  404. audioArgs = append(audioArgs, startTimeString)
  405. audioArgs = append(audioArgs, "-t")
  406. audioArgs = append(audioArgs, timeLeng)
  407. // 指定导出的音频文件编码格式
  408. audioArgs = append(audioArgs, "-acodec")
  409. audioArgs = append(audioArgs, "pcm_s16le")
  410. audioArgs = append(audioArgs, "-f")
  411. audioArgs = append(audioArgs, "s16le")
  412. audioArgs = append(audioArgs, "-ac")
  413. audioArgs = append(audioArgs, "1")
  414. audioArgs = append(audioArgs, "-ar")
  415. audioArgs = append(audioArgs, "16000")
  416. audioArgs = append(audioArgs, outAudioFullPath)
  417. return audioArgs
  418. }
  419. func (f *FFMPEGHelper) getSubExportArgsByTimeRange(subFullPath string, startTimeString, timeLength, outSubFullPath string) []string {
  420. /*
  421. ffmpeg.exe -i aa.srt -ss 00:1:27 -t 28 bb.srt
  422. */
  423. var subArgs = make([]string, 0)
  424. subArgs = append(subArgs, "-i")
  425. subArgs = append(subArgs, subFullPath)
  426. subArgs = append(subArgs, "-ss")
  427. subArgs = append(subArgs, startTimeString)
  428. subArgs = append(subArgs, "-t")
  429. subArgs = append(subArgs, timeLength)
  430. subArgs = append(subArgs, outSubFullPath)
  431. return subArgs
  432. }
  433. // addSubMapArg 构建字幕的导出参数
  434. func (f *FFMPEGHelper) addSubMapArg(subArgs *[]string, index int, subSaveFullPath string) {
  435. *subArgs = append(*subArgs, "-map")
  436. *subArgs = append(*subArgs, fmt.Sprintf("0:%d", index))
  437. *subArgs = append(*subArgs, subSaveFullPath)
  438. }
  439. // addAudioMapArg 构建音频的导出参数
  440. func (f *FFMPEGHelper) addAudioMapArg(subArgs *[]string, index int, audioSaveFullPath string) {
  441. // -acodec pcm_s16le -f s16le -ac 1 -ar 16000
  442. *subArgs = append(*subArgs, "-map")
  443. *subArgs = append(*subArgs, fmt.Sprintf("0:%d", index))
  444. *subArgs = append(*subArgs, "-acodec")
  445. *subArgs = append(*subArgs, "pcm_s16le")
  446. *subArgs = append(*subArgs, "-f")
  447. *subArgs = append(*subArgs, "s16le")
  448. *subArgs = append(*subArgs, "-ac")
  449. *subArgs = append(*subArgs, "1")
  450. *subArgs = append(*subArgs, "-ar")
  451. *subArgs = append(*subArgs, "16000")
  452. *subArgs = append(*subArgs, audioSaveFullPath)
  453. }
  454. func (f FFMPEGHelper) getVersion(exeName string) (string, error) {
  455. const args = "-version"
  456. cmdArgs := strings.Fields(args)
  457. cmd := exec.Command(exeName, cmdArgs...)
  458. buf := bytes.NewBufferString("")
  459. //指定输出位置
  460. cmd.Stderr = buf
  461. cmd.Stdout = buf
  462. err := cmd.Start()
  463. if err != nil {
  464. return "", err
  465. }
  466. err = cmd.Wait()
  467. if err != nil {
  468. return "", err
  469. }
  470. return buf.String(), nil
  471. }
  472. const (
  473. codecTypeSub = "subtitle"
  474. codecTypeAudio = "audio"
  475. extMP3 = ".mp3"
  476. extPCM = ".pcm"
  477. )
  478. type ExportType int
  479. const (
  480. Subtitle ExportType = iota // 导出字幕
  481. Audio // 导出音频
  482. SubtitleAndAudio // 导出字幕和音频
  483. )