Bläddra i källkod

抄 gohsl 的相应实现

Signed-off-by: allan716 <[email protected]>
allan716 3 år sedan
förälder
incheckning
8358e9718a

+ 1 - 1
cmd/chinesesubfinder/main.go

@@ -125,7 +125,7 @@ func main() {
 		common.SetApiToken("")
 	}
 	// 是否开启开发模式,跳过某些流程
-	settings.Get().SpeedDevMode = false
+	settings.Get().SpeedDevMode = true
 	err := settings.Get().Save()
 	if err != nil {
 		loggerBase.Panicln("settings.Get().Save() err:", err)

+ 4 - 0
internal/backend/base_router.go

@@ -86,6 +86,9 @@ func InitRouter(
 	// v1路由: /v1/xxx
 	GroupV1 := router.Group("/" + cbV1.GetVersion())
 	{
+		GroupV1.GET("/preview/playlist/:videofpathbase64", cbV1.HlsPlaylist)
+		GroupV1.GET("/preview/segments/:resolution/:segment/:videofpathbase64", cbV1.HlsSegment)
+
 		GroupV1.Use(middle.CheckAuth())
 
 		GroupV1.GET("/settings", cbV1.SettingsHandler)
@@ -125,6 +128,7 @@ func InitRouter(
 		GroupV1.POST("/preview/job_result", cbV1.PreviewJobResult)
 		GroupV1.POST("/preview/export_info", cbV1.PreviewGetExportInfo)
 		GroupV1.POST("/preview/clean_up", cbV1.PreviewCleanUp)
+
 	}
 
 	GroupAPIV1 := router.Group("/api/v1")

+ 3 - 0
internal/backend/controllers/v1/controller_base.go

@@ -1,6 +1,7 @@
 package v1
 
 import (
+	"github.com/allanpk716/ChineseSubFinder/pkg/hls_center"
 	"net/http"
 
 	"github.com/allanpk716/ChineseSubFinder/pkg/settings"
@@ -23,6 +24,7 @@ type ControllerBase struct {
 	pathUrlMap                          map[string]string
 	videoScanAndRefreshHelper           *video_scan_and_refresh_helper.VideoScanAndRefreshHelper
 	videoListHelper                     *video_list_helper.VideoListHelper
+	hslCenter                           *hls_center.Center
 	videoScanAndRefreshHelperIsRunning  bool
 	videoScanAndRefreshHelperLocker     lock.Lock
 	videoScanAndRefreshHelperErrMessage string
@@ -39,6 +41,7 @@ func NewControllerBase(cronHelper *cron_helper.CronHelper, restartSignal chan in
 			sub_formatter.GetSubFormatter(cronHelper.Logger, settings.Get().AdvancedSettings.SubNameFormatter),
 			cronHelper.FileDownloader, nil),
 		videoListHelper:                 video_list_helper.NewVideoListHelper(cronHelper.Logger),
+		hslCenter:                       hls_center.NewCenter(cronHelper.Logger),
 		videoScanAndRefreshHelperLocker: lock.NewLock(),
 		restartSignal:                   restartSignal,
 	}

+ 61 - 0
internal/backend/controllers/v1/hls.go

@@ -0,0 +1,61 @@
+package v1
+
+import (
+	b64 "encoding/base64"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"strconv"
+)
+
+// HlsPlaylist 获取 m3u8 列表
+func (cb *ControllerBase) HlsPlaylist(c *gin.Context) {
+
+	var err error
+	defer func() {
+		// 统一的异常处理
+		cb.ErrorProcess(c, "HlsPlaylist", err)
+	}()
+
+	videoFPathBase64 := c.Param("videofpathbase64")
+	videoFPath, err := b64.StdEncoding.DecodeString(videoFPathBase64)
+	if err != nil {
+		return
+	}
+
+	// segments/720/0/videofpathbase64
+	template := fmt.Sprintf("/%s/preview/segments/{{.Resolution}}/{{.Segment}}/%v", cb.GetVersion(), videoFPathBase64)
+	err = cb.hslCenter.WritePlaylist(template, string(videoFPath), c.Writer)
+	if err != nil {
+		return
+	}
+}
+
+// HlsSegment 获取具体一个 ts 文件
+func (cb *ControllerBase) HlsSegment(c *gin.Context) {
+
+	var err error
+	defer func() {
+		// 统一的异常处理
+		cb.ErrorProcess(c, "HlsSegment", err)
+	}()
+
+	resolution := c.Param("resolution")
+	segment := c.Param("segment")
+	videoFPathBase64 := c.Param("videofpathbase64")
+	videoFPath, err := b64.StdEncoding.DecodeString(videoFPathBase64)
+	if err != nil {
+		return
+	}
+	segmentInt64, err := strconv.ParseInt(segment, 0, 64)
+	if err != nil {
+		return
+	}
+	resolutionInt64, err := strconv.ParseInt(resolution, 0, 64)
+	if err != nil {
+		return
+	}
+	err = cb.hslCenter.WriteSegment(string(videoFPath), segmentInt64, resolutionInt64, c.Writer)
+	if err != nil {
+		return
+	}
+}

+ 54 - 0
pkg/cmdutil/get_output.go

@@ -0,0 +1,54 @@
+package cmdutil
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os/exec"
+	"syscall"
+
+	log "github.com/sirupsen/logrus"
+)
+
+func ExecAndGetStdoutJson(cmd *exec.Cmd, v interface{}) error {
+	b, err := ExecAndGetStdoutBytes(cmd)
+	if err != nil {
+		return err
+	}
+	err = json.Unmarshal(b, v)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func ExecAndGetStdoutBytes(cmd *exec.Cmd) ([]byte, error) {
+	b := new(bytes.Buffer)
+	if err := ExecAndWriteStdout(cmd, b); err != nil {
+		return nil, err
+	}
+	return b.Bytes(), nil
+}
+
+func ExecAndWriteStdout(cmd *exec.Cmd, w io.Writer) error {
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		return fmt.Errorf("error opening stdout of command: %v", err)
+	}
+	defer stdout.Close()
+	log.Debugf("Executing: %v %v", cmd.Path, cmd.Args)
+	if err = cmd.Start(); err != nil {
+		return fmt.Errorf("error starting command: %v", err)
+	}
+	if _, err := io.Copy(w, stdout); err != nil {
+		// Ask the process to exit
+		cmd.Process.Signal(syscall.SIGKILL)
+		cmd.Process.Wait()
+		return fmt.Errorf("error copying stdout to buffer: %v", err)
+	}
+	if err := cmd.Wait(); err != nil {
+		return fmt.Errorf("command failed %v", err)
+	}
+	return nil
+}

+ 8 - 8
pkg/ffmpeg_helper/ffmpeg_helper.go

@@ -52,10 +52,10 @@ func (f *FFMPEGHelper) Version() (string, error) {
 	return outMsg0 + "\r\n" + outMsg1, nil
 }
 
-// GetFFMPEGInfo 获取 视频的 FFMPEG 信息,包含音频和字幕
+// ExportFFMPEGInfo 获取 视频的 FFMPEG 信息,包含音频和字幕
 // 优先会导出 中、英、日、韩 类型的,字幕如果没有语言类型,则也导出,然后需要额外的字幕语言的判断去辅助标记(读取文件内容)
 // 音频只会导出一个,优先导出 中、英、日、韩 类型的
-func (f *FFMPEGHelper) GetFFMPEGInfo(videoFileFullPath string, exportType ExportType) (bool, *FFMPEGInfo, error) {
+func (f *FFMPEGHelper) ExportFFMPEGInfo(videoFileFullPath string, exportType ExportType) (bool, *FFMPEGInfo, error) {
 
 	const args = "-v error -show_format -show_streams -print_format json"
 	cmdArgs := strings.Fields(args)
@@ -87,7 +87,7 @@ func (f *FFMPEGHelper) GetFFMPEGInfo(videoFileFullPath string, exportType Export
 		if bok == false && ffMPEGInfo != nil {
 			err := os.RemoveAll(nowCacheFolderPath)
 			if err != nil {
-				f.log.Errorln("GetFFMPEGInfo - RemoveAll", err.Error())
+				f.log.Errorln("ExportFFMPEGInfo - RemoveAll", err.Error())
 				return
 			}
 		}
@@ -99,7 +99,7 @@ func (f *FFMPEGHelper) GetFFMPEGInfo(videoFileFullPath string, exportType Export
 		return false, nil, err
 	}
 
-	ffMPEGInfo.Duration = f.getVideoDuration(videoFileFullPath)
+	ffMPEGInfo.Duration = f.GetVideoDuration(videoFileFullPath)
 
 	// 判断这个视频是否已经导出过内置的字幕和音频文件了
 	if ffMPEGInfo.IsExported(exportType) == false {
@@ -138,7 +138,7 @@ func (f *FFMPEGHelper) GetFFMPEGInfo(videoFileFullPath string, exportType Export
 				return true, ffMPEGInfo, nil
 			}
 		} else {
-			f.log.Errorln("GetFFMPEGInfo.getAudioAndSubExportArgs Not Support ExportType")
+			f.log.Errorln("ExportFFMPEGInfo.getAudioAndSubExportArgs Not Support ExportType")
 			return false, nil, nil
 		}
 		// 上面的操作为了就是确保后续的导出不会出问题
@@ -165,8 +165,8 @@ func (f *FFMPEGHelper) GetFFMPEGInfo(videoFileFullPath string, exportType Export
 	return bok, ffMPEGInfo, nil
 }
 
-// GetAudioDurationInfo 获取音频的长度信息
-func (f *FFMPEGHelper) GetAudioDurationInfo(audioFileFullPath string) (bool, float64, error) {
+// ExportAudioDurationInfo 获取音频的长度信息
+func (f *FFMPEGHelper) ExportAudioDurationInfo(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)
@@ -902,7 +902,7 @@ func (f *FFMPEGHelper) isSupportSubCodecName(name string) bool {
 	}
 }
 
-func (f *FFMPEGHelper) getVideoDuration(videoFileFullPath string) float64 {
+func (f *FFMPEGHelper) GetVideoDuration(videoFileFullPath string) float64 {
 
 	const args = "-v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 -i"
 	cmdArgs := strings.Fields(args)

+ 3 - 3
pkg/ffmpeg_helper/ffmpeg_helper_test.go

@@ -22,12 +22,12 @@ func TestGetFFMPEGInfo(t *testing.T) {
 	videoFile := unit_test_helper.GetTestDataResourceRootPath([]string{"ffmpeg", "org"}, 4, false)
 	videoFile = filepath.Join(videoFile, "sampleVideo.mp4")
 	f := NewFFMPEGHelper(log_helper.GetLogger4Tester())
-	bok, ffmpegInfo, err := f.GetFFMPEGInfo(videoFile, Audio)
+	bok, ffmpegInfo, err := f.ExportFFMPEGInfo(videoFile, Audio)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if bok == false {
-		t.Fatal("GetFFMPEGInfo = false")
+		t.Fatal("ExportFFMPEGInfo = false")
 	}
 
 	subArgs, audioArgs := f.getAudioAndSubExportArgs(videoFile, ffmpegInfo)
@@ -113,7 +113,7 @@ func TestGetAudioInfo(t *testing.T) {
 	audioFullPath := filepath.Join(testDataPath, "sampleAudio.wav")
 
 	f := NewFFMPEGHelper(log_helper.GetLogger4Tester())
-	bok, duration, err := f.GetAudioDurationInfo(audioFullPath)
+	bok, duration, err := f.ExportAudioDurationInfo(audioFullPath)
 	if err != nil || bok == false {
 		t.Fatal(err)
 	}

+ 2 - 2
pkg/ffmpeg_helper/ffmpeg_info.go

@@ -104,7 +104,7 @@ func (f *FFMPEGInfo) IsExported(exportType ExportType) bool {
 	return bProcessDone
 }
 
-func (f FFMPEGInfo) CreateExportedMask() error {
+func (f *FFMPEGInfo) CreateExportedMask() error {
 	maskFileFPath, err := f.getExportedMaskFileFPath()
 	if err != nil {
 		return err
@@ -120,7 +120,7 @@ func (f FFMPEGInfo) CreateExportedMask() error {
 	return nil
 }
 
-func (f FFMPEGInfo) getExportedMaskFileFPath() (string, error) {
+func (f *FFMPEGInfo) getExportedMaskFileFPath() (string, error) {
 	nowCacheFolder, err := f.GetCacheFolderFPath()
 	if err != nil {
 		return "", err

+ 76 - 0
pkg/hls_center/cache/cache.go

@@ -0,0 +1,76 @@
+package cache
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	log "github.com/sirupsen/logrus"
+)
+
+type Cache interface {
+	Set(ctx context.Context, key string, value []byte) error
+	Get(ctx context.Context, ey string) ([]byte, error)
+}
+
+type DirCache struct {
+	path string
+}
+
+func NewDirCache(path string) *DirCache {
+	return &DirCache{path}
+}
+
+func (d *DirCache) getCachePath(key string) string {
+	return filepath.Join(d.path, key)
+}
+
+func (d *DirCache) Get(ctx context.Context, key string) ([]byte, error) {
+	file, err := os.Open(d.getCachePath(key))
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil, nil
+		}
+		return nil, err
+	}
+	defer file.Close()
+	b := new(bytes.Buffer)
+	if _, err = io.Copy(b, file); err != nil {
+		log.Errorf("Error copying file to cache value: %v", err)
+		return nil, err
+	}
+	return b.Bytes(), nil
+}
+
+func (d *DirCache) Set(ctx context.Context, key string, value []byte) error {
+	log.Debugf("Setting cache item %v")
+	if err := os.MkdirAll(d.path, 0777); err != nil {
+		log.Errorf("Could not create cache dir %v: %v", d.path, err)
+		return err
+	}
+	cacheTmpFile, err := ioutil.TempFile(d.path, key+".*")
+	if err != nil {
+		log.Errorf("Could not create cache file %v: %v", cacheTmpFile, err)
+		return err
+	}
+	if _, err := io.Copy(cacheTmpFile, bytes.NewReader(value)); err != nil {
+		log.Errorf("Could not write cache file %v: %v", cacheTmpFile, err)
+		cacheTmpFile.Close()
+		os.Remove(cacheTmpFile.Name())
+		return err
+	}
+	if err = cacheTmpFile.Close(); err != nil {
+		log.Errorf("Could not close cache file %v: %v", cacheTmpFile, err)
+		os.Remove(cacheTmpFile.Name())
+		return err
+	}
+	if err = os.Rename(cacheTmpFile.Name(), d.getCachePath(key)); err != nil {
+		log.Errorf("Could not move cache file %v: %v", cacheTmpFile, err)
+		os.Remove(cacheTmpFile.Name())
+		return err
+	}
+	return nil
+}

+ 164 - 0
pkg/hls_center/center.go

@@ -0,0 +1,164 @@
+package hls_center
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"github.com/allanpk716/ChineseSubFinder/pkg"
+	"github.com/allanpk716/ChineseSubFinder/pkg/ffmpeg_helper"
+	"github.com/allanpk716/ChineseSubFinder/pkg/hls_center/worker"
+	"github.com/sirupsen/logrus"
+	"io"
+	"path/filepath"
+	"text/template"
+)
+
+type Center struct {
+	logger       *logrus.Logger
+	ffmpegHelper *ffmpeg_helper.FFMPEGHelper
+	encodeWorker *worker.WorkerServer
+}
+
+func NewCenter(logger *logrus.Logger) *Center {
+
+	cacheRootDir, err := pkg.GetVideoAndSubPreviewCacheFolder()
+	if err != nil {
+		panic(err)
+	}
+	encodeWorker := worker.NewWorkerServer(worker.WorkerServerConf{
+		NumWorkers: 2,
+		CacheDir:   filepath.Join(cacheRootDir, "segments"),
+		Worker:     worker.NewCommandWorker("ffmpeg"),
+	})
+
+	return &Center{
+		logger:       logger,
+		ffmpegHelper: ffmpeg_helper.NewFFMPEGHelper(logger),
+		encodeWorker: encodeWorker,
+	}
+}
+
+// WritePlaylist 构建 m3u8 文件
+func (c *Center) WritePlaylist(urlTemplate string, videoFileFPath string, w io.Writer) error {
+
+	t := template.Must(template.New("urlTemplate").Parse(urlTemplate))
+
+	if pkg.IsFile(videoFileFPath) == false {
+		return errors.New("WritePlaylist video file not exist, file = " + videoFileFPath)
+	}
+
+	duration := c.ffmpegHelper.GetVideoDuration(videoFileFPath)
+	getUrl := func(segmentIndex int) string {
+		buf := new(bytes.Buffer)
+		t.Execute(buf, struct {
+			Resolution int64
+			Segment    int
+		}{
+			720,
+			segmentIndex,
+		})
+		return buf.String()
+	}
+
+	fmt.Fprint(w, "#EXTM3U\n")
+	fmt.Fprint(w, "#EXT-X-VERSION:3\n")
+	fmt.Fprint(w, "#EXT-X-MEDIA-SEQUENCE:0\n")
+	fmt.Fprint(w, "#EXT-X-ALLOW-CACHE:YES\n")
+	fmt.Fprint(w, "#EXT-X-TARGETDURATION:"+fmt.Sprintf("%.f", hlsSegmentLength)+"\n")
+	fmt.Fprint(w, "#EXT-X-PLAYLIST-TYPE:VOD\n")
+
+	leftover := duration
+	segmentIndex := 0
+
+	for leftover > 0 {
+		if leftover > hlsSegmentLength {
+			fmt.Fprintf(w, "#EXTINF: %f,\n", hlsSegmentLength)
+		} else {
+			fmt.Fprintf(w, "#EXTINF: %f,\n", leftover)
+		}
+		fmt.Fprintf(w, getUrl(segmentIndex)+"\n")
+		segmentIndex++
+		leftover = leftover - hlsSegmentLength
+	}
+	fmt.Fprint(w, "#EXT-X-ENDLIST\n")
+	return nil
+}
+
+// WriteSegment 构建 ts 文件
+func (c *Center) WriteSegment(videoFileFPath string, segmentIndex int64, resolution int64, w io.Writer) error {
+
+	if pkg.IsFile(videoFileFPath) == false {
+		return errors.New("WriteSegment video file not exist, file = " + videoFileFPath)
+	}
+
+	args := encodingArgs(videoFileFPath, segmentIndex, resolution)
+	return c.encodeWorker.Serve(args, w)
+}
+
+func encodingArgs(videoFile string, segment int64, resolution int64) []string {
+	startTime := segment * hlsSegmentLength
+	// see http://superuser.com/questions/908280/what-is-the-correct-way-to-fix-keyframes-in-ffmpeg-for-dash
+	return []string{
+		// Prevent encoding to run longer than 30 seonds
+		"-timelimit", "45",
+
+		// TODO: Some stuff to investigate
+		// "-probesize", "524288",
+		// "-fpsprobesize", "10",
+		// "-analyzeduration", "2147483647",
+		// "-hwaccel:0", "vda",
+
+		// The start time
+		// important: needs to be before -i to do input seeking
+		"-ss", fmt.Sprintf("%v.00", startTime),
+
+		// The source file
+		"-i", videoFile,
+
+		// Put all streams to output
+		// "-map", "0",
+
+		// The duration
+		"-t", fmt.Sprintf("%v.00", hlsSegmentLength),
+
+		// TODO: Find out what it does
+		//"-strict", "-2",
+
+		// Synchronize audio
+		"-async", "1",
+
+		// 720p
+		"-vf", fmt.Sprintf("scale=-2:%v", resolution),
+
+		// x264 video codec
+		"-vcodec", "libx264",
+
+		// x264 preset
+		"-preset", "veryfast",
+
+		// aac audio codec
+		"-c:a", "aac",
+		"-b:a", "128k",
+		"-ac", "2",
+
+		// TODO
+		"-pix_fmt", "yuv420p",
+
+		//"-r", "25", // fixed framerate
+
+		"-force_key_frames", "expr:gte(t,n_forced*5.000)",
+
+		//"-force_key_frames", "00:00:00.00",
+		//"-x264opts", "keyint=25:min-keyint=25:scenecut=-1",
+
+		//"-f", "mpegts",
+
+		"-f", "ssegment",
+		"-segment_time", fmt.Sprintf("%v.00", hlsSegmentLength),
+		"-initial_offset", fmt.Sprintf("%v.00", startTime),
+
+		"pipe:out%03d.ts",
+	}
+}
+
+const hlsSegmentLength = 5.0 // Seconds

+ 99 - 0
pkg/hls_center/worker/worker.go

@@ -0,0 +1,99 @@
+package worker
+
+import (
+	"bytes"
+	"context"
+	"github.com/allanpk716/ChineseSubFinder/pkg/hls_center/cache"
+	"io"
+	"path/filepath"
+
+	log "github.com/sirupsen/logrus"
+)
+
+type WorkHandler interface {
+	Key(request interface{}) string
+	Handle(request interface{}, w io.Writer) error
+}
+
+type WorkerServerConf struct {
+	NumWorkers int
+	CacheDir   string
+	Worker     WorkHandler
+}
+
+type token struct{}
+
+type WorkerServer struct {
+	conf   WorkerServerConf
+	cache  cache.Cache
+	tokens chan token
+}
+
+func NewWorkerServer(conf WorkerServerConf) *WorkerServer {
+	tokens := make(chan token, conf.NumWorkers)
+	for i := conf.NumWorkers; i > 0; i-- {
+		tokens <- token{}
+	}
+	return &WorkerServer{conf, cache.NewDirCache(conf.CacheDir), tokens}
+}
+
+func (s *WorkerServer) handler() WorkHandler {
+	return s.conf.Worker
+}
+
+func (s *WorkerServer) getCachePath(r interface{}) string {
+	return filepath.Join(s.conf.CacheDir, s.handler().Key(r))
+}
+
+func (s *WorkerServer) tryServeFromCache(r interface{}, w io.Writer) (bool, error) {
+	data, err := s.cache.Get(context.Background(), s.handler().Key(r))
+	// If error getting item, return not served with error
+	if err != nil {
+		return false, err
+	}
+	// If no item found, return not served with no error
+	if data == nil {
+		return false, nil
+	}
+	// If copying fails, return served with error
+	if _, err = io.Copy(w, bytes.NewReader(data)); err != nil {
+		return true, err
+	}
+	// Everything worked, return served with no error
+	return true, nil
+}
+
+// TODO timeout & context
+func (s *WorkerServer) Serve(request interface{}, w io.Writer) error {
+
+	if served, err := s.tryServeFromCache(request, w); served || err != nil {
+		if served {
+			log.Debugf("Served request %v from cache", request)
+		}
+		if err != nil {
+			log.Errorf("Error serving request from cache: %v", err)
+		}
+		return err
+	}
+
+	// Wait for token
+	token := <-s.tokens
+	defer func() {
+		s.tokens <- token
+	}()
+
+	log.Debugf("Processing request %v", request)
+
+	cw := new(bytes.Buffer)
+	mw := io.MultiWriter(cw, w)
+	if err := s.handler().Handle(request, mw); err != nil {
+		log.Errorf("Error handling request: %v", err)
+		return err
+	}
+
+	if err := s.cache.Set(context.Background(), s.handler().Key(request), cw.Bytes()); err != nil {
+		log.Errorf("Error caching request: %v", err)
+	}
+
+	return nil
+}

+ 34 - 0
pkg/hls_center/worker/worker_cmd.go

@@ -0,0 +1,34 @@
+package worker
+
+import (
+	"crypto/sha1"
+	"fmt"
+	"github.com/allanpk716/ChineseSubFinder/pkg/cmdutil"
+	"io"
+	"os/exec"
+)
+
+type CommandWorker struct {
+	executable string
+}
+
+func NewCommandWorker(executable string) *CommandWorker {
+	return &CommandWorker{executable}
+}
+
+func (c *CommandWorker) Key(r interface{}) string {
+	args := r.([]string)
+	h := sha1.New()
+	h.Write([]byte(c.executable))
+	for _, v := range args {
+		h.Write([]byte(v))
+	}
+	sum := h.Sum(nil)
+	return fmt.Sprintf("%x", sum)
+}
+
+func (c *CommandWorker) Handle(r interface{}, w io.Writer) error {
+	args := r.([]string)
+	cmd := exec.Command(c.executable, args...)
+	return cmdutil.ExecAndWriteStdout(cmd, w)
+}

+ 6 - 6
pkg/logic/sub_timeline_fixer/SubTimelineFixerHelperEx.go

@@ -71,12 +71,12 @@ func (s SubTimelineFixerHelperEx) Process(videoFileFullPath, srcSubFPath string)
 	var ffmpegInfo *ffmpeg_helper.FFMPEGInfo
 	var err error
 	// 先尝试获取内置字幕的信息
-	bok, ffmpegInfo, err = s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Subtitle)
+	bok, ffmpegInfo, err = s.ffmpegHelper.ExportFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Subtitle)
 	if err != nil {
 		return err
 	}
 	if bok == false {
-		return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo = false Subtitle -- " + videoFileFullPath)
+		return errors.New("SubTimelineFixerHelperEx.Process.ExportFFMPEGInfo = false Subtitle -- " + videoFileFullPath)
 	}
 
 	// 这个需要提前考虑,如果只有一个内置的字幕,且这个字幕的大小小于 2kb,那么认为这个字幕是有问题的,就直接切换到 audio 校正
@@ -95,16 +95,16 @@ func (s SubTimelineFixerHelperEx) Process(videoFileFullPath, srcSubFPath string)
 	if ffmpegInfo.SubtitleInfoList == nil || len(ffmpegInfo.SubtitleInfoList) <= 0 || oneSubAndIsError == true {
 
 		if ffmpegInfo.AudioInfoList == nil || len(ffmpegInfo.AudioInfoList) == 0 {
-			return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo Can`t Find SubTitle And Audio To Export -- " + videoFileFullPath)
+			return errors.New("SubTimelineFixerHelperEx.Process.ExportFFMPEGInfo Can`t Find SubTitle And Audio To Export -- " + videoFileFullPath)
 		}
 
 		// 如果内置字幕没有,那么就需要尝试获取音频信息
-		bok, ffmpegInfo, err = s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Audio)
+		bok, ffmpegInfo, err = s.ffmpegHelper.ExportFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Audio)
 		if err != nil {
 			return err
 		}
 		if bok == false {
-			return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo = false Audio -- " + videoFileFullPath)
+			return errors.New("SubTimelineFixerHelperEx.Process.ExportFFMPEGInfo = false Audio -- " + videoFileFullPath)
 		}
 
 		// 使用音频进行时间轴的校正
@@ -224,7 +224,7 @@ func (s SubTimelineFixerHelperEx) ProcessByAudioFile(baseAudioFileFPath, srcSubF
 func (s SubTimelineFixerHelperEx) IsVideoCanExportSubtitleAndAudio(videoFileFullPath string) (bool, *ffmpeg_helper.FFMPEGInfo, []vad.VADInfo, *subparser.FileInfo, error) {
 
 	// 先尝试获取内置字幕的信息
-	bok, ffmpegInfo, err := s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.SubtitleAndAudio)
+	bok, ffmpegInfo, err := s.ffmpegHelper.ExportFFMPEGInfo(videoFileFullPath, ffmpeg_helper.SubtitleAndAudio)
 	if err != nil {
 		return false, nil, nil, nil, err
 	}