| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675 | 
							- package ffmpeg_helper
 
- import (
 
- 	"bytes"
 
- 	"errors"
 
- 	"fmt"
 
- 	"github.com/allanpk716/ChineseSubFinder/internal/common"
 
- 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/ass"
 
- 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/srt"
 
- 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/language"
 
- 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 
- 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 
- 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
 
- 	"github.com/tidwall/gjson"
 
- 	"os"
 
- 	"os/exec"
 
- 	"path/filepath"
 
- 	"strings"
 
- )
 
- type FFMPEGHelper struct {
 
- 	SubParserHub *sub_parser_hub.SubParserHub // 字幕内容的解析器
 
- }
 
- func NewFFMPEGHelper() *FFMPEGHelper {
 
- 	return &FFMPEGHelper{
 
- 		SubParserHub: sub_parser_hub.NewSubParserHub(ass.NewParser(), srt.NewParser()),
 
- 	}
 
- }
 
- // Version 获取版本信息,如果不存在 FFMPEG 和 ffprobe 则报错
 
- func (f FFMPEGHelper) Version() (string, error) {
 
- 	outMsg0, err := f.getVersion("ffmpeg")
 
- 	if err != nil {
 
- 		return "", err
 
- 	}
 
- 	outMsg1, err := f.getVersion("ffprobe")
 
- 	if err != nil {
 
- 		return "", err
 
- 	}
 
- 	return outMsg0 + "\r\n" + outMsg1, nil
 
- }
 
- // GetFFMPEGInfo 获取 视频的 FFMPEG 信息,包含音频和字幕
 
- // 优先会导出 中、英、日、韩 类型的,字幕如果没有语言类型,则也导出,然后需要额外的字幕语言的判断去辅助标记(读取文件内容)
 
- func (f *FFMPEGHelper) GetFFMPEGInfo(videoFileFullPath string, exportType ExportType) (bool, *FFMPEGInfo, error) {
 
- 	const args = "-v error -show_format -show_streams -print_format json"
 
- 	cmdArgs := strings.Fields(args)
 
- 	cmdArgs = append(cmdArgs, videoFileFullPath)
 
- 	cmd := exec.Command("ffprobe", cmdArgs...)
 
- 	buf := bytes.NewBufferString("")
 
- 	//指定输出位置
 
- 	cmd.Stderr = buf
 
- 	cmd.Stdout = buf
 
- 	err := cmd.Start()
 
- 	if err != nil {
 
- 		return false, nil, err
 
- 	}
 
- 	err = cmd.Wait()
 
- 	if err != nil {
 
- 		return false, nil, err
 
- 	}
 
- 	// 解析得到的字符串反馈
 
- 	bok, ffMPEGInfo := f.parseJsonString2GetFFProbeInfo(videoFileFullPath, buf.String())
 
- 	if bok == false {
 
- 		return false, nil, nil
 
- 	}
 
- 	nowCacheFolderPath, err := ffMPEGInfo.GetCacheFolderFPath()
 
- 	if err != nil {
 
- 		return false, nil, err
 
- 	}
 
- 	// 在函数调用完毕后,判断是否需要清理
 
- 	defer func() {
 
- 		if bok == false && ffMPEGInfo != nil {
 
- 			err := os.RemoveAll(nowCacheFolderPath)
 
- 			if err != nil {
 
- 				log_helper.GetLogger().Errorln("GetFFMPEGInfo - RemoveAll", err.Error())
 
- 				return
 
- 			}
 
- 		}
 
- 	}()
 
- 	// 查找当前这个视频外置字幕列表
 
- 	err = ffMPEGInfo.GetExternalSubInfos(f.SubParserHub)
 
- 	if err != nil {
 
- 		return false, nil, err
 
- 	}
 
- 	// 判断这个视频是否已经导出过内置的字幕和音频文件了
 
- 	if ffMPEGInfo.IsExported(exportType) == false {
 
- 		// 说明缓存不存在,需要导出,这里需要注意,如果导出失败了,这个文件夹要清理掉
 
- 		if my_util.IsDir(nowCacheFolderPath) == true {
 
- 			// 如果存在则,先清空一个这个文件夹
 
- 			err = my_util.ClearFolder(nowCacheFolderPath)
 
- 			if err != nil {
 
- 				bok = false
 
- 				return bok, nil, err
 
- 			}
 
- 		}
 
- 		// 开始导出
 
- 		// 构建导出的命令参数
 
- 		exportAudioArgs, exportSubArgs := f.getAudioAndSubExportArgs(videoFileFullPath, ffMPEGInfo)
 
- 		// 上面导出的信息,可能是 nil 参数,那么就直接把导出的 List 信息给置为 nil,让后续有依据可以跳出,不继续执行
 
- 		if exportType == Subtitle {
 
- 			if exportSubArgs == nil {
 
- 				ffMPEGInfo.SubtitleInfoList = nil
 
- 				return true, ffMPEGInfo, nil
 
- 			}
 
- 		} else if exportType == Audio {
 
- 			if exportAudioArgs == nil {
 
- 				ffMPEGInfo.AudioInfoList = nil
 
- 				return true, ffMPEGInfo, nil
 
- 			}
 
- 		} else if exportType == SubtitleAndAudio {
 
- 			if exportAudioArgs == nil || exportSubArgs == nil {
 
- 				if exportAudioArgs == nil {
 
- 					ffMPEGInfo.AudioInfoList = nil
 
- 				}
 
- 				if exportSubArgs == nil {
 
- 					ffMPEGInfo.SubtitleInfoList = nil
 
- 				}
 
- 				return true, ffMPEGInfo, nil
 
- 			}
 
- 		} else {
 
- 			log_helper.GetLogger().Errorln("GetFFMPEGInfo.getAudioAndSubExportArgs Not Support ExportType")
 
- 			return false, nil, nil
 
- 		}
 
- 		// 上面的操作为了就是确保后续的导出不会出问题
 
- 		// 执行导出,音频和内置的字幕
 
- 		execErrorString, err := f.exportAudioAndSubtitles(exportAudioArgs, exportSubArgs, exportType)
 
- 		if err != nil {
 
- 			log_helper.GetLogger().Errorln("exportAudioAndSubtitles", execErrorString)
 
- 			bok = false
 
- 			return bok, nil, err
 
- 		}
 
- 		// 导出后,需要把现在导出的文件的路径给复制给 ffMPEGInfo 中
 
- 		// 音频是否导出了
 
- 		ffMPEGInfo.isAudioExported(nowCacheFolderPath)
 
- 		// 字幕都要导出了
 
- 		ffMPEGInfo.isSubExported(nowCacheFolderPath)
 
- 		// 创建 exportedMakeFileName 这个文件
 
- 		// 成功,那么就需要生成这个 exportedMakeFileName 文件
 
- 		err = ffMPEGInfo.CreateExportedMask()
 
- 		if err != nil {
 
- 			return false, nil, err
 
- 		}
 
- 	}
 
- 	return bok, ffMPEGInfo, nil
 
- }
 
- // GetAudioDurationInfo 获取音频的长度信息
 
- func (f *FFMPEGHelper) GetAudioDurationInfo(audioFileFullPath string) (bool, float64, error) {
 
- 	const args = "-v error -show_format -show_streams -print_format json -f s16le -ac 1 -ar 16000"
 
- 	cmdArgs := strings.Fields(args)
 
- 	cmdArgs = append(cmdArgs, audioFileFullPath)
 
- 	cmd := exec.Command("ffprobe", cmdArgs...)
 
- 	buf := bytes.NewBufferString("")
 
- 	//指定输出位置
 
- 	cmd.Stderr = buf
 
- 	cmd.Stdout = buf
 
- 	err := cmd.Start()
 
- 	if err != nil {
 
- 		return false, 0, err
 
- 	}
 
- 	err = cmd.Wait()
 
- 	if err != nil {
 
- 		return false, 0, err
 
- 	}
 
- 	bok, duration := f.parseJsonString2GetAudioInfo(buf.String())
 
- 	if bok == false {
 
- 		return false, 0, errors.New("ffprobe get " + audioFileFullPath + " duration error")
 
- 	}
 
- 	return true, duration, nil
 
- }
 
- // ExportAudioAndSubArgsByTimeRange 根据输入的时间轴导出音频分段信息 "0:1:27" "28.2"
 
- func (f *FFMPEGHelper) ExportAudioAndSubArgsByTimeRange(audioFullPath, subFullPath string, startTimeString, timeLength string) (string, string, string, error) {
 
- 	outStartTimeString := strings.ReplaceAll(startTimeString, ":", "-")
 
- 	outStartTimeString = strings.ReplaceAll(outStartTimeString, ".", "#")
 
- 	outTimeLength := strings.ReplaceAll(timeLength, ".", "#")
 
- 	frontName := strings.ReplaceAll(filepath.Base(audioFullPath), filepath.Ext(audioFullPath), "")
 
- 	outAudioName := frontName + "_" + outStartTimeString + "_" + outTimeLength + filepath.Ext(audioFullPath)
 
- 	outSubName := frontName + "_" + outStartTimeString + "_" + outTimeLength + common.SubExtSRT
 
- 	var outAudioFullPath = filepath.Join(filepath.Dir(audioFullPath), outAudioName)
 
- 	var outSubFullPath = filepath.Join(filepath.Dir(audioFullPath), outSubName)
 
- 	// 导出音频
 
- 	if my_util.IsFile(outAudioFullPath) == true {
 
- 		err := os.Remove(outAudioFullPath)
 
- 		if err != nil {
 
- 			return "", "", "", err
 
- 		}
 
- 	}
 
- 	args := f.getAudioExportArgsByTimeRange(audioFullPath, startTimeString, timeLength, outAudioFullPath)
 
- 	execFFMPEG, err := f.execFFMPEG(args)
 
- 	if err != nil {
 
- 		return "", "", execFFMPEG, err
 
- 	}
 
- 	// 导出字幕
 
- 	if my_util.IsFile(outSubFullPath) == true {
 
- 		err := os.Remove(outSubFullPath)
 
- 		if err != nil {
 
- 			return "", "", "", err
 
- 		}
 
- 	}
 
- 	args = f.getSubExportArgsByTimeRange(subFullPath, startTimeString, timeLength, outSubFullPath)
 
- 	execFFMPEG, err = f.execFFMPEG(args)
 
- 	if err != nil {
 
- 		return "", "", execFFMPEG, err
 
- 	}
 
- 	return outAudioFullPath, outSubFullPath, "", nil
 
- }
 
- // ExportSubArgsByTimeRange 根据输入的时间轴导出字幕分段信息 "0:1:27" "28.2"
 
- func (f *FFMPEGHelper) ExportSubArgsByTimeRange(subFullPath, outName string, startTimeString, timeLength string) (string, string, error) {
 
- 	outStartTimeString := strings.ReplaceAll(startTimeString, ":", "-")
 
- 	outStartTimeString = strings.ReplaceAll(outStartTimeString, ".", "#")
 
- 	outTimeLength := strings.ReplaceAll(timeLength, ".", "#")
 
- 	frontName := strings.ReplaceAll(filepath.Base(subFullPath), filepath.Ext(subFullPath), "")
 
- 	outSubName := frontName + "_" + outStartTimeString + "_" + outTimeLength + "_" + outName + common.SubExtSRT
 
- 	var outSubFullPath = filepath.Join(filepath.Dir(subFullPath), outSubName)
 
- 	// 导出字幕
 
- 	if my_util.IsFile(outSubFullPath) == true {
 
- 		err := os.Remove(outSubFullPath)
 
- 		if err != nil {
 
- 			return "", "", err
 
- 		}
 
- 	}
 
- 	args := f.getSubExportArgsByTimeRange(subFullPath, startTimeString, timeLength, outSubFullPath)
 
- 	execFFMPEG, err := f.execFFMPEG(args)
 
- 	if err != nil {
 
- 		return "", execFFMPEG, err
 
- 	}
 
- 	return outSubFullPath, "", nil
 
- }
 
- // parseJsonString2GetFFProbeInfo 使用 ffprobe 获取视频的 stream 信息,从中解析出字幕和音频的索引
 
- func (f *FFMPEGHelper) parseJsonString2GetFFProbeInfo(videoFileFullPath, inputFFProbeString string) (bool, *FFMPEGInfo) {
 
- 	streamsValue := gjson.Get(inputFFProbeString, "streams.#")
 
- 	if streamsValue.Exists() == false {
 
- 		return false, nil
 
- 	}
 
- 	ffmpegInfo := NewFFMPEGInfo(videoFileFullPath)
 
- 	// 进行字幕和音频的缓存,优先当然是导出 中、英、日、韩 相关的字幕和音频
 
- 	// 但是如果都没得这些的时候,那么也需要导出至少一个字幕或者音频,用于字幕的校正
 
- 	cacheAudios := make([]AudioInfo, 0)
 
- 	cacheSubtitleInfos := make([]SubtitleInfo, 0)
 
- 	for i := 0; i < int(streamsValue.Num); i++ {
 
- 		oneIndex := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.index", i))
 
- 		oneCodecName := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.codec_name", i))
 
- 		oneCodecType := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.codec_type", i))
 
- 		oneTimeBase := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.time_base", i))
 
- 		oneStartTime := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.start_time", i))
 
- 		oneLanguage := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.tags.language", i))
 
- 		// 任意一个字段不存在则跳过
 
- 		if oneIndex.Exists() == false {
 
- 			continue
 
- 		}
 
- 		if oneCodecName.Exists() == false {
 
- 			continue
 
- 		}
 
- 		if oneCodecType.Exists() == false {
 
- 			continue
 
- 		}
 
- 		if oneTimeBase.Exists() == false {
 
- 			continue
 
- 		}
 
- 		if oneStartTime.Exists() == false {
 
- 			continue
 
- 		}
 
- 		// 这里需要区分是字幕还是音频
 
- 		if oneCodecType.String() == codecTypeSub {
 
- 			// 字幕
 
- 			// 只解析 subrip 类型的,不支持 hdmv_pgs_subtitle 的字幕导出
 
- 			if f.isSupportSubCodecName(oneCodecName.String()) == false {
 
- 				continue
 
- 			}
 
- 			// 这里非必须解析到 language 字段,把所有的都导出来,然后通过额外字幕语言判断即可
 
- 			oneDurationTS := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.duration_ts", i))
 
- 			oneDuration := gjson.Get(inputFFProbeString, fmt.Sprintf("streams.%d.duration", i))
 
- 			// 必须存在的
 
- 			if oneDurationTS.Exists() == false {
 
- 				continue
 
- 			}
 
- 			if oneDuration.Exists() == false {
 
- 				continue
 
- 			}
 
- 			// 非必须存在的
 
- 			nowLanguageString := ""
 
- 			if oneLanguage.Exists() == true {
 
- 				nowLanguageString = oneLanguage.String()
 
- 				// 只导出 中、英、日、韩
 
- 				if language.IsSupportISOString(nowLanguageString) == false {
 
- 					subInfo := NewSubtitleInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
 
- 						oneTimeBase.String(), oneStartTime.String(),
 
- 						int(oneDurationTS.Num), oneDuration.String(), nowLanguageString)
 
- 					// 不符合的也存在下来,万一,符合要求的一个都没得的时候,就需要从里面挑几个出来了
 
- 					cacheSubtitleInfos = append(cacheSubtitleInfos, *subInfo)
 
- 					continue
 
- 				}
 
- 			}
 
- 			subInfo := NewSubtitleInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
 
- 				oneTimeBase.String(), oneStartTime.String(),
 
- 				int(oneDurationTS.Num), oneDuration.String(), nowLanguageString)
 
- 			ffmpegInfo.SubtitleInfoList = append(ffmpegInfo.SubtitleInfoList, *subInfo)
 
- 		} else if oneCodecType.String() == codecTypeAudio {
 
- 			// 音频
 
- 			// 这里必要要能够解析到 language 字段
 
- 			if oneLanguage.Exists() == false {
 
- 				// 不符合的也存在下来,万一,符合要求的一个都没得的时候,就需要从里面挑几个出来了
 
- 				audioInfo := NewAudioInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
 
- 					oneTimeBase.String(), oneStartTime.String(), oneLanguage.String())
 
- 				cacheAudios = append(cacheAudios, *audioInfo)
 
- 				continue
 
- 			}
 
- 			// 只导出 中、英、日、韩
 
- 			if language.IsSupportISOString(oneLanguage.String()) == false {
 
- 				// 不符合的也存在下来,万一,符合要求的一个都没得的时候,就需要从里面挑几个出来了
 
- 				audioInfo := NewAudioInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
 
- 					oneTimeBase.String(), oneStartTime.String(), oneLanguage.String())
 
- 				cacheAudios = append(cacheAudios, *audioInfo)
 
- 				continue
 
- 			}
 
- 			audioInfo := NewAudioInfo(int(oneIndex.Num), oneCodecName.String(), oneCodecType.String(),
 
- 				oneTimeBase.String(), oneStartTime.String(), oneLanguage.String())
 
- 			ffmpegInfo.AudioInfoList = append(ffmpegInfo.AudioInfoList, *audioInfo)
 
- 		} else {
 
- 			continue
 
- 		}
 
- 	}
 
- 	// 如何没有找到合适的字幕,那么就要把缓存的字幕选一个填充进去
 
- 	if len(ffmpegInfo.SubtitleInfoList) == 0 {
 
- 		if len(cacheSubtitleInfos) != 0 {
 
- 			ffmpegInfo.SubtitleInfoList = append(ffmpegInfo.SubtitleInfoList, cacheSubtitleInfos[0])
 
- 		}
 
- 	}
 
- 	// 如何没有找到合适的音频,那么就要把缓存的音频选一个填充进去
 
- 	if len(ffmpegInfo.AudioInfoList) == 0 {
 
- 		if len(cacheAudios) != 0 {
 
- 			ffmpegInfo.AudioInfoList = append(ffmpegInfo.AudioInfoList, cacheAudios[0])
 
- 		}
 
- 	} else {
 
- 		// 音频只需要导出一个就行了,取第一个
 
- 		newAudioList := make([]AudioInfo, 0)
 
- 		newAudioList = append(newAudioList, ffmpegInfo.AudioInfoList[0])
 
- 		ffmpegInfo.AudioInfoList = newAudioList
 
- 	}
 
- 	return true, ffmpegInfo
 
- }
 
- // parseJsonString2GetAudioInfo 获取 pcm 音频的长度
 
- func (f *FFMPEGHelper) parseJsonString2GetAudioInfo(inputFFProbeString string) (bool, float64) {
 
- 	durationValue := gjson.Get(inputFFProbeString, "format.duration")
 
- 	if durationValue.Exists() == false {
 
- 		return false, 0
 
- 	}
 
- 	return true, durationValue.Float()
 
- }
 
- // exportAudioAndSubtitles 导出音频和字幕文件
 
- func (f *FFMPEGHelper) exportAudioAndSubtitles(audioArgs, subArgs []string, exportType ExportType) (string, error) {
 
- 	// 输入的两个数组,有可能是 nil
 
- 	// 这里导出依赖的是 ffmpeg 这个程序,需要的是构建导出的语句
 
- 	if exportType == SubtitleAndAudio {
 
- 		execErrorString, err := f.execFFMPEG(audioArgs)
 
- 		if err != nil {
 
- 			return execErrorString, err
 
- 		}
 
- 		execErrorString, err = f.execFFMPEG(subArgs)
 
- 		if err != nil {
 
- 			return execErrorString, err
 
- 		}
 
- 	} else if exportType == Audio {
 
- 		execErrorString, err := f.execFFMPEG(audioArgs)
 
- 		if err != nil {
 
- 			return execErrorString, err
 
- 		}
 
- 	} else if exportType == Subtitle {
 
- 		execErrorString, err := f.execFFMPEG(subArgs)
 
- 		if err != nil {
 
- 			return execErrorString, err
 
- 		}
 
- 	} else {
 
- 		return "", errors.New("FFMPEGHelper ExportType not support")
 
- 	}
 
- 	return "", nil
 
- }
 
- // execFFMPEG 执行 ffmpeg 命令
 
- func (f *FFMPEGHelper) execFFMPEG(cmds []string) (string, error) {
 
- 	if cmds == nil || len(cmds) == 0 {
 
- 		return "", nil
 
- 	}
 
- 	cmd := exec.Command("ffmpeg", cmds...)
 
- 	buf := bytes.NewBufferString("")
 
- 	//指定输出位置
 
- 	cmd.Stderr = buf
 
- 	cmd.Stdout = buf
 
- 	err := cmd.Start()
 
- 	if err != nil {
 
- 		return buf.String(), err
 
- 	}
 
- 	err = cmd.Wait()
 
- 	if err != nil {
 
- 		return buf.String(), err
 
- 	}
 
- 	return "", nil
 
- }
 
- // getAudioAndSubExportArgs 构建从原始视频导出字幕、音频的 ffmpeg 的参数 audioArgs, subArgs
 
- func (f *FFMPEGHelper) getAudioAndSubExportArgs(videoFileFullPath string, ffmpegInfo *FFMPEGInfo) ([]string, []string) {
 
- 	/*
 
- 		导出多个字幕
 
- 		ffmpeg.exe -i xx.mp4 -vn -an -map 0:7 subs-7.srt -map 0:6 subs-6.srt
 
- 		导出音频,从 1m 27s 开始,导出向后的 28 s,转换为 mp3 格式
 
- 		ffmpeg.exe -i xx.mp4 -vn -map 0:1 -ss 00:1:27 -f mp3 -t 28 audio.mp3
 
- 		导出音频,转换为 mp3 格式
 
- 		ffmpeg.exe -i xx.mp4 -vn -map 0:1 -f mp3 audio.mp3
 
- 		导出音频,转换为 16000k 16bit 单通道 采样率的 test.pcm
 
- 		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
 
- 		截取字幕的时间片段
 
- 		ffmpeg.exe -i "subs-3.srt" -ss 00:1:27 -t 28 subs-3-cut-from-org.srt
 
- 	*/
 
- 	var subArgs = make([]string, 0)
 
- 	var audioArgs = make([]string, 0)
 
- 	// 基础的输入视频参数
 
- 	subArgs = append(subArgs, "-i")
 
- 	audioArgs = append(audioArgs, "-i")
 
- 	subArgs = append(subArgs, videoFileFullPath)
 
- 	audioArgs = append(audioArgs, videoFileFullPath)
 
- 	// 字幕导出的参数构建
 
- 	subArgs = append(subArgs, "-vn") // 不输出视频流
 
- 	subArgs = append(subArgs, "-an") // 不输出音频流
 
- 	nowCacheFolderPath, err := ffmpegInfo.GetCacheFolderFPath()
 
- 	if err != nil {
 
- 		log_helper.GetLogger().Errorln("getAudioAndSubExportArgs", videoFileFullPath, err.Error())
 
- 		return nil, nil
 
- 	}
 
- 	if len(ffmpegInfo.SubtitleInfoList) == 0 {
 
- 		// 如果没有,就返回空
 
- 		subArgs = nil
 
- 	} else {
 
- 		for _, subtitleInfo := range ffmpegInfo.SubtitleInfoList {
 
- 			f.addSubMapArg(&subArgs, subtitleInfo.Index,
 
- 				filepath.Join(nowCacheFolderPath, subtitleInfo.GetName()+common.SubExtSRT))
 
- 			f.addSubMapArg(&subArgs, subtitleInfo.Index,
 
- 				filepath.Join(nowCacheFolderPath, subtitleInfo.GetName()+common.SubExtASS))
 
- 		}
 
- 	}
 
- 	// 音频导出的参数构建
 
- 	audioArgs = append(audioArgs, "-vn")
 
- 	if len(ffmpegInfo.AudioInfoList) == 0 {
 
- 		// 如果没有,就返回空
 
- 		audioArgs = nil
 
- 	} else {
 
- 		for _, audioInfo := range ffmpegInfo.AudioInfoList {
 
- 			f.addAudioMapArg(&audioArgs, audioInfo.Index,
 
- 				filepath.Join(nowCacheFolderPath, audioInfo.GetName()+extPCM))
 
- 		}
 
- 	}
 
- 	return audioArgs, subArgs
 
- }
 
- // getAudioAndSubExportArgsByTimeRange 导出某个时间范围内的音频和字幕文件文件 startTimeString 00:1:27 timeLeng 向后多少秒
 
- func (f *FFMPEGHelper) getAudioExportArgsByTimeRange(audioFullPath string, startTimeString, timeLeng, outAudioFullPath string) []string {
 
- 	/*
 
- 		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
 
- 		ffmpeg.exe -i aa.srt -ss 00:1:27 -t 28 bb.srt
 
- 	*/
 
- 	var audioArgs = make([]string, 0)
 
- 	// 指定读取的音频文件编码格式
 
- 	audioArgs = append(audioArgs, "-ar")
 
- 	audioArgs = append(audioArgs, "16000")
 
- 	audioArgs = append(audioArgs, "-ac")
 
- 	audioArgs = append(audioArgs, "1")
 
- 	audioArgs = append(audioArgs, "-f")
 
- 	audioArgs = append(audioArgs, "s16le")
 
- 	audioArgs = append(audioArgs, "-i")
 
- 	audioArgs = append(audioArgs, audioFullPath)
 
- 	audioArgs = append(audioArgs, "-ss")
 
- 	audioArgs = append(audioArgs, startTimeString)
 
- 	audioArgs = append(audioArgs, "-t")
 
- 	audioArgs = append(audioArgs, timeLeng)
 
- 	// 指定导出的音频文件编码格式
 
- 	audioArgs = append(audioArgs, "-acodec")
 
- 	audioArgs = append(audioArgs, "pcm_s16le")
 
- 	audioArgs = append(audioArgs, "-f")
 
- 	audioArgs = append(audioArgs, "s16le")
 
- 	audioArgs = append(audioArgs, "-ac")
 
- 	audioArgs = append(audioArgs, "1")
 
- 	audioArgs = append(audioArgs, "-ar")
 
- 	audioArgs = append(audioArgs, "16000")
 
- 	audioArgs = append(audioArgs, outAudioFullPath)
 
- 	return audioArgs
 
- }
 
- func (f *FFMPEGHelper) getSubExportArgsByTimeRange(subFullPath string, startTimeString, timeLength, outSubFullPath string) []string {
 
- 	/*
 
- 		ffmpeg.exe -i aa.srt -ss 00:1:27 -t 28 bb.srt
 
- 	*/
 
- 	var subArgs = make([]string, 0)
 
- 	subArgs = append(subArgs, "-i")
 
- 	subArgs = append(subArgs, subFullPath)
 
- 	subArgs = append(subArgs, "-ss")
 
- 	subArgs = append(subArgs, startTimeString)
 
- 	subArgs = append(subArgs, "-t")
 
- 	subArgs = append(subArgs, timeLength)
 
- 	subArgs = append(subArgs, outSubFullPath)
 
- 	return subArgs
 
- }
 
- // addSubMapArg 构建字幕的导出参数
 
- func (f *FFMPEGHelper) addSubMapArg(subArgs *[]string, index int, subSaveFullPath string) {
 
- 	*subArgs = append(*subArgs, "-map")
 
- 	*subArgs = append(*subArgs, fmt.Sprintf("0:%d", index))
 
- 	*subArgs = append(*subArgs, subSaveFullPath)
 
- }
 
- // addAudioMapArg 构建音频的导出参数
 
- func (f *FFMPEGHelper) addAudioMapArg(subArgs *[]string, index int, audioSaveFullPath string) {
 
- 	// -acodec pcm_s16le -f s16le -ac 1 -ar 16000
 
- 	*subArgs = append(*subArgs, "-map")
 
- 	*subArgs = append(*subArgs, fmt.Sprintf("0:%d", index))
 
- 	*subArgs = append(*subArgs, "-acodec")
 
- 	*subArgs = append(*subArgs, "pcm_s16le")
 
- 	*subArgs = append(*subArgs, "-f")
 
- 	*subArgs = append(*subArgs, "s16le")
 
- 	*subArgs = append(*subArgs, "-ac")
 
- 	*subArgs = append(*subArgs, "1")
 
- 	*subArgs = append(*subArgs, "-ar")
 
- 	*subArgs = append(*subArgs, "16000")
 
- 	*subArgs = append(*subArgs, audioSaveFullPath)
 
- }
 
- func (f FFMPEGHelper) getVersion(exeName string) (string, error) {
 
- 	const args = "-version"
 
- 	cmdArgs := strings.Fields(args)
 
- 	cmd := exec.Command(exeName, cmdArgs...)
 
- 	buf := bytes.NewBufferString("")
 
- 	//指定输出位置
 
- 	cmd.Stderr = buf
 
- 	cmd.Stdout = buf
 
- 	err := cmd.Start()
 
- 	if err != nil {
 
- 		return "", err
 
- 	}
 
- 	err = cmd.Wait()
 
- 	if err != nil {
 
- 		return "", err
 
- 	}
 
- 	return buf.String(), nil
 
- }
 
- // isSupportSubCodecName 是否是 FFMPEG 支持的 CodecName
 
- func (f FFMPEGHelper) isSupportSubCodecName(name string) bool {
 
- 	switch name {
 
- 	case Subtitle_StreamCodec_subrip,
 
- 		Subtitle_StreamCodec_ass,
 
- 		Subtitle_StreamCodec_ssa,
 
- 		Subtitle_StreamCodec_srt:
 
- 		return true
 
- 	default:
 
- 		return false
 
- 	}
 
- }
 
- const (
 
- 	codecTypeSub   = "subtitle"
 
- 	codecTypeAudio = "audio"
 
- 	extMP3         = ".mp3"
 
- 	extPCM         = ".pcm"
 
- )
 
- type ExportType int
 
- const (
 
- 	Subtitle         ExportType = iota // 导出字幕
 
- 	Audio                              // 导出音频
 
- 	SubtitleAndAudio                   // 导出字幕和音频
 
- )
 
- /*
 
- 	FFMPEG 支持的字幕 Codec Name:
 
- 	 ..S... arib_caption         ARIB STD-B24 caption
 
- 	 DES... ass                  ASS (Advanced SSA) subtitle (decoders: ssa ass ) (encoders: ssa ass )
 
- 	 DES... dvb_subtitle         DVB subtitles (decoders: dvbsub ) (encoders: dvbsub )
 
- 	 ..S... dvb_teletext         DVB teletext
 
- 	 DES... dvd_subtitle         DVD subtitles (decoders: dvdsub ) (encoders: dvdsub )
 
- 	 D.S... eia_608              EIA-608 closed captions (decoders: cc_dec )
 
- 	 D.S... hdmv_pgs_subtitle    HDMV Presentation Graphic Stream subtitles (decoders: pgssub )
 
- 	 ..S... hdmv_text_subtitle   HDMV Text subtitle
 
- 	 D.S... jacosub              JACOsub subtitle
 
- 	 D.S... microdvd             MicroDVD subtitle
 
- 	 DES... mov_text             MOV text
 
- 	 D.S... mpl2                 MPL2 subtitle
 
- 	 D.S... pjs                  PJS (Phoenix Japanimation Society) subtitle
 
- 	 D.S... realtext             RealText subtitle
 
- 	 D.S... sami                 SAMI subtitle
 
- 	 ..S... srt                  SubRip subtitle with embedded timing
 
- 	 ..S... ssa                  SSA (SubStation Alpha) subtitle
 
- 	 D.S... stl                  Spruce subtitle format
 
- 	 DES... subrip               SubRip subtitle (decoders: srt subrip ) (encoders: srt subrip )
 
- 	 D.S... subviewer            SubViewer subtitle
 
- 	 D.S... subviewer1           SubViewer v1 subtitle
 
- 	 DES... text                 raw UTF-8 text
 
- 	 ..S... ttml                 Timed Text Markup Language
 
- 	 D.S... vplayer              VPlayer subtitle
 
- 	 DES... webvtt               WebVTT subtitle
 
- 	 DES... xsub                 XSUB
 
- */
 
- const (
 
- 	Subtitle_StreamCodec_subrip = "subrip"
 
- 	Subtitle_StreamCodec_ass    = "ass"
 
- 	Subtitle_StreamCodec_ssa    = "ssa"
 
- 	Subtitle_StreamCodec_srt    = "srt"
 
- )
 
 
  |