Browse Source

调整,支持多个字幕同时截取

Signed-off-by: allan716 <[email protected]>
allan716 3 years ago
parent
commit
5d564470d7

+ 2 - 5
go.sum

@@ -129,8 +129,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/cyruzin/golang-tmdb v1.4.3 h1:68vNEG4djN5eTeQQCajPDi0VpR+WwFGaham/Or0ef84=
-github.com/cyruzin/golang-tmdb v1.4.3/go.mod h1:HSTffPiDK+Q8gzRlZ22oCu6Otj/7TwUlTqEzFqJoG00=
 github.com/cyruzin/golang-tmdb v1.4.4 h1:EGYWPSHsYISUOIKWKffHJzRXtDh9RDW7wqcZJxjZWeU=
 github.com/cyruzin/golang-tmdb v1.4.4/go.mod h1:ZSryJLCcY+9TiKU+LbouXKns++YBrM8Tizannr05c+I=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -583,8 +581,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -973,9 +971,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
 gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=

+ 2 - 2
internal/backend/controllers/v1/preview.go

@@ -118,14 +118,14 @@ func (cb *ControllerBase) PreviewGetExportInfo(c *gin.Context) {
 		return
 	}
 
-	m3u8, subPath, err := cb.cronHelper.Downloader.PreviewQueue.GetVideoHLSAndSubByTimeRangeExportPathInfo(job.VideoFPath, job.SubFPath, job.StartTime, job.EndTime)
+	m3u8, subPaths, err := cb.cronHelper.Downloader.PreviewQueue.GetVideoHLSAndSubByTimeRangeExportPathInfo(job.VideoFPath, job.SubFPaths, job.StartTime, job.EndTime)
 	if err != nil {
 		return
 	}
 
 	c.JSON(http.StatusOK, preview_queue.Job{
 		VideoFPath: m3u8,
-		SubFPath:   subPath,
+		SubFPaths:  subPaths,
 	})
 	return
 }

+ 80 - 33
pkg/ffmpeg_helper/ffmpeg_helper.go

@@ -268,7 +268,7 @@ func (f *FFMPEGHelper) ExportSubArgsByTimeRange(subFullPath, outName string, sta
 }
 
 // ExportVideoHLSAndSubByTimeRange 导出指定的时间轴的视频HLS和字幕,然后从 outDirPath 中获取 outputlist.m3u8 和字幕的文件
-func (f *FFMPEGHelper) ExportVideoHLSAndSubByTimeRange(videoFullPath, subFullPath, startTimeString, timeLength, segmentTime, outDirPath string) (string, string, error) {
+func (f *FFMPEGHelper) ExportVideoHLSAndSubByTimeRange(videoFullPath string, subFullPaths []string, startTimeString, timeLength, segmentTime, outDirPath string) (string, []string, error) {
 
 	// 导出视频
 	if pkg.IsFile(videoFullPath) == false {
@@ -278,12 +278,14 @@ func (f *FFMPEGHelper) ExportVideoHLSAndSubByTimeRange(videoFullPath, subFullPat
 			// 需要从 steamDirPath 搜索最大的一个文件出来
 			videoFullPath = pkg.GetMaxSizeFile(steamDirPath)
 		} else {
-			return "", "", errors.New("video file not found")
+			return "", nil, errors.New("video file not found")
 		}
 	}
 
-	if pkg.IsFile(subFullPath) == false {
-		return "", "", errors.New("sub file not exist")
+	for _, subFullPath := range subFullPaths {
+		if pkg.IsFile(subFullPath) == false {
+			return "", nil, errors.New("sub file not exist:" + subFullPath)
+		}
 	}
 
 	fileName := filepath.Base(videoFullPath)
@@ -293,21 +295,21 @@ func (f *FFMPEGHelper) ExportVideoHLSAndSubByTimeRange(videoFullPath, subFullPat
 	if pkg.IsDir(outDirSubPath) == true {
 		err := os.RemoveAll(outDirSubPath)
 		if err != nil {
-			return "", "", err
+			return "", nil, err
 		}
 	}
 	err := os.MkdirAll(outDirSubPath, os.ModePerm)
 	if err != nil {
-		return "", "", err
+		return "", nil, err
 	}
 
-	//// 先剪切
+	// 先剪切
 	//videoExt := filepath.Ext(fileName)
 	//cutOffVideoFPath := filepath.Join(outDirPath, frontName+"_cut"+videoExt)
 	//args := f.getVideoExportArgsByTimeRange(videoFullPath, startTimeString, timeLength, cutOffVideoFPath)
 	//execFFMPEG, err := f.execFFMPEG(args)
 	//if err != nil {
-	//	return errors.New(execFFMPEG + err.Error())
+	//	return "", nil, errors.New(execFFMPEG + err.Error())
 	//}
 	//// 转换 HLS
 	//args = f.getVideo2HLSArgs(cutOffVideoFPath, segmentTime, outDirPath)
@@ -317,44 +319,50 @@ func (f *FFMPEGHelper) ExportVideoHLSAndSubByTimeRange(videoFullPath, subFullPat
 	//}
 
 	// 直接导出
-	args := f.getVideoHLSExportArgsByTimeRange(videoFullPath, startTimeString, timeLength, segmentTime, outDirSubPath)
-	execFFMPEG, err := f.execFFMPEG(args)
+	args = f.getVideoHLSExportArgsByTimeRange(videoFullPath, startTimeString, timeLength, segmentTime, outDirSubPath)
+	execFFMPEG, err = f.execFFMPEG(args)
 	if err != nil {
-		return "", "", errors.New(execFFMPEG + err.Error())
+		return "", nil, errors.New(execFFMPEG + err.Error())
 	}
 
 	// 导出字幕
-	tmpSubFPath := subFullPath
-	nowSubExt := filepath.Ext(tmpSubFPath)
+	outSubFPaths := make([]string, 0)
+	for i, subFullPath := range subFullPaths {
 
-	if strings.ToLower(nowSubExt) != common.SubExtSRT {
-		// 这里需要优先判断字幕是否是 SRT,如果是 ASS 的,那么需要转换一次才行
-		middleSubFPath := filepath.Join(outDirSubPath, frontName+"_middle"+common.SubExtSRT)
-		args = f.getSubASS2SRTArgs(tmpSubFPath, middleSubFPath)
+		tmpSubFPath := subFullPath
+		nowSubExt := filepath.Ext(tmpSubFPath)
+
+		if strings.ToLower(nowSubExt) != common.SubExtSRT {
+			// 这里需要优先判断字幕是否是 SRT,如果是 ASS 的,那么需要转换一次才行
+			middleSubFPath := filepath.Join(outDirSubPath, fmt.Sprintf(frontName+"_middle_%d"+common.SubExtSRT, i))
+			args = f.getSubASS2SRTArgs(tmpSubFPath, middleSubFPath)
+			execFFMPEG, err = f.execFFMPEG(args)
+			if err != nil {
+				return "", nil, errors.New(execFFMPEG + err.Error())
+			}
+			tmpSubFPath = middleSubFPath
+		}
+		outSubFileFPath := filepath.Join(outDirSubPath, fmt.Sprintf(frontName+"_%d"+common.SubExtSRT, i))
+		args = f.getSubExportArgsByTimeRange(tmpSubFPath, startTimeString, timeLength, outSubFileFPath)
 		execFFMPEG, err = f.execFFMPEG(args)
 		if err != nil {
-			return "", "", errors.New(execFFMPEG + err.Error())
+			return "", nil, errors.New(execFFMPEG + err.Error())
 		}
-		tmpSubFPath = middleSubFPath
-	}
-	outSubFileFPath := filepath.Join(outDirSubPath, frontName+common.SubExtSRT)
-	args = f.getSubExportArgsByTimeRange(tmpSubFPath, startTimeString, timeLength, outSubFileFPath)
-	execFFMPEG, err = f.execFFMPEG(args)
-	if err != nil {
-		return "", "", errors.New(execFFMPEG + err.Error())
-	}
-	// 字幕的相对位置
-	subRelPath, err := filepath.Rel(outDirPath, outSubFileFPath)
-	if err != nil {
-		return "", "", err
+		// 字幕的相对位置
+		subRelPath, err := filepath.Rel(outDirPath, outSubFileFPath)
+		if err != nil {
+			return "", nil, err
+		}
+		outSubFPaths = append(outSubFPaths, subRelPath)
 	}
+
 	// outputlist.m3u8 的相对位置
 	outputListRelPath, err := filepath.Rel(outDirPath, filepath.Join(outDirSubPath, "outputlist.m3u8"))
 	if err != nil {
-		return "", "", err
+		return "", nil, err
 	}
 
-	return outputListRelPath, subRelPath, nil
+	return outputListRelPath, outSubFPaths, nil
 }
 
 // parseJsonString2GetFFProbeInfo 使用 ffprobe 获取视频的 stream 信息,从中解析出字幕和音频的索引
@@ -636,6 +644,7 @@ func (f *FFMPEGHelper) getVideoExportArgsByTimeRange(videoFullPath string, start
 		ffmpeg.exe -i '.\Chainsaw Man - S01E02 - ARRIVAL IN TOKYO HDTV-1080p.mp4' -ss 00:00:00 -t 300  -c:v copy -c:a copy wawa.mp4
 	*/
 	videoArgs := make([]string, 0)
+	videoArgs = append(videoArgs, "-y")
 	videoArgs = append(videoArgs, "-ss")
 	videoArgs = append(videoArgs, startTimeString)
 	videoArgs = append(videoArgs, "-t")
@@ -647,6 +656,21 @@ func (f *FFMPEGHelper) getVideoExportArgsByTimeRange(videoFullPath string, start
 	videoArgs = append(videoArgs, "-i")
 	videoArgs = append(videoArgs, videoFullPath)
 
+	//videoArgs = append(videoArgs, "-s")
+	//videoArgs = append(videoArgs, "640x480")
+	//videoArgs = append(videoArgs, "-vframes")
+	//videoArgs = append(videoArgs, "90")
+	//videoArgs = append(videoArgs, "-r")
+	//videoArgs = append(videoArgs, "29.97")
+	//videoArgs = append(videoArgs, "-c:v")
+	//videoArgs = append(videoArgs, "h264")
+	//videoArgs = append(videoArgs, "-b:v")
+	//videoArgs = append(videoArgs, "500k")
+	//videoArgs = append(videoArgs, "-b:a")
+	//videoArgs = append(videoArgs, "48k")
+	//videoArgs = append(videoArgs, "-ac")
+	//videoArgs = append(videoArgs, "2")
+
 	videoArgs = append(videoArgs, "-c:v")
 	videoArgs = append(videoArgs, "copy")
 	videoArgs = append(videoArgs, "-c:a")
@@ -676,13 +700,36 @@ func (f *FFMPEGHelper) getVideoHLSExportArgsByTimeRange(videoFullPath string, st
 	videoArgs = append(videoArgs, "-i")
 	videoArgs = append(videoArgs, videoFullPath)
 
+	// 限制线程数
+	videoArgs = append(videoArgs, "-threads")
+	videoArgs = append(videoArgs, "2")
+	// 约束强制贞切割?
 	videoArgs = append(videoArgs, "-force_key_frames")
 	videoArgs = append(videoArgs, "\"expr:gte(t,n_forced*"+sgmentTime+")\"")
-
+	// 原编码格式
 	videoArgs = append(videoArgs, "-c:v")
 	videoArgs = append(videoArgs, "copy")
 	videoArgs = append(videoArgs, "-c:a")
 	videoArgs = append(videoArgs, "copy")
+	// 转码为 h264
+	//videoArgs = append(videoArgs, "-vcodec")
+	//videoArgs = append(videoArgs, "h264")
+	// -s 640x480 -vframes 90 -r 29.97 -c:v h264 -b:v 500k -b:a 48k -ac 2
+	//videoArgs = append(videoArgs, "-s")
+	//videoArgs = append(videoArgs, "640x480")
+	//videoArgs = append(videoArgs, "-vframes")
+	//videoArgs = append(videoArgs, "90")
+	//videoArgs = append(videoArgs, "-r")
+	//videoArgs = append(videoArgs, "29.97")
+	//videoArgs = append(videoArgs, "-c:v")
+	//videoArgs = append(videoArgs, "h264")
+	//videoArgs = append(videoArgs, "-b:v")
+	//videoArgs = append(videoArgs, "500k")
+	//videoArgs = append(videoArgs, "-b:a")
+	//videoArgs = append(videoArgs, "48k")
+	//videoArgs = append(videoArgs, "-ac")
+	//videoArgs = append(videoArgs, "2")
+
 	videoArgs = append(videoArgs, "-f")
 	videoArgs = append(videoArgs, "segment")
 	videoArgs = append(videoArgs, "-segment_time")

+ 16 - 5
pkg/ffmpeg_helper/ffmpeg_helper_test.go

@@ -4,6 +4,7 @@ import (
 	"os"
 	"path/filepath"
 	"testing"
+	"time"
 
 	"github.com/allanpk716/ChineseSubFinder/pkg"
 
@@ -134,17 +135,27 @@ func TestExportVideoHLSAndSubByTimeRange(t *testing.T) {
 
 	outDirPath := "C:\\Tmp\\media\\test\\hls"
 	videoFPath := "C:\\Tmp\\media\\test\\Chainsaw Man - S01E02 - ARRIVAL IN TOKYO HDTV-1080p.mp4"
-	subFPath := "C:\\Tmp\\media\\test\\Chainsaw Man - S01E02 - ARRIVAL IN TOKYO HDTV-1080p.chinese(简,csf).default.srt"
-	//subFPath := "C:\\Tmp\\media\\test\\Three Thousand Years of Longing (2022) WEBDL-1080p.chinese(简英,assrt).ass"
+
+	subFPaths := []string{
+		"C:\\Tmp\\media\\test\\Chainsaw Man - S01E02 - ARRIVAL IN TOKYO HDTV-1080p.chinese(简,csf).default.srt",
+		"C:\\Tmp\\media\\test\\Chainsaw Man - S01E01 - DOG & CHAINSAW WEBRip-1080p.chinese(简,csf).default.srt",
+	}
+
 	f := NewFFMPEGHelper(log_helper.GetLogger4Tester())
-	m3u8, sub, err := f.ExportVideoHLSAndSubByTimeRange(videoFPath, subFPath, "10", "300", "5.000", outDirPath)
+	println("Start:", time.Now().Format("2006-01-02 15:04:05"))
+	m3u8, subs, err := f.ExportVideoHLSAndSubByTimeRange(videoFPath, subFPaths, "10", "10", "5.000", outDirPath)
 	if err != nil {
 		t.Fatal(err)
 	}
+	println("Start:", time.Now().Format("2006-01-02 15:04:05"))
 	if pkg.IsFile(filepath.Join(outDirPath, m3u8)) == false {
 		t.Fatal("m3u8 file not found")
 	}
-	if pkg.IsFile(filepath.Join(outDirPath, sub)) == false {
-		t.Fatal("sub file not found")
+
+	for _, path := range subs {
+		if pkg.IsFile(filepath.Join(outDirPath, path)) == false {
+			t.Fatal("sub file not found")
+		}
 	}
+
 }

+ 30 - 16
pkg/preview_queue/queue.go

@@ -2,6 +2,7 @@ package preview_queue
 
 import (
 	"errors"
+	"fmt"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -52,34 +53,47 @@ func NewPreviewQueue(log *logrus.Logger) *PreviewQueue {
 }
 
 // GetVideoHLSAndSubByTimeRangeExportPathInfo 获取视频的HLS和字幕的导出路径信息
-func (p *PreviewQueue) GetVideoHLSAndSubByTimeRangeExportPathInfo(videoFullPath, subFullPath, startTimeString, timeLength string) (string, string, error) {
+func (p *PreviewQueue) GetVideoHLSAndSubByTimeRangeExportPathInfo(videoFullPath string, subFullPaths []string, startTimeString, timeLength string) (string, []string, error) {
 	// 导出视频
 	if pkg.IsFile(videoFullPath) == false {
-		return "", "", errors.New("video file not exist, maybe is bluray file, so not support yet")
+		return "", nil, errors.New("video file not exist, maybe is bluray file, so not support yet")
 	}
-	if pkg.IsFile(subFullPath) == false {
-		return "", "", errors.New("sub file not exist")
+
+	for _, subFullPath := range subFullPaths {
+		if pkg.IsFile(subFullPath) == false {
+			return "", nil, errors.New("sub file not exist:" + subFullPath)
+		}
 	}
+
 	outDirPath, err := pkg.GetVideoAndSubPreviewCacheFolder()
 	if err != nil {
-		return "", "", err
+		return "", nil, err
 	}
 	fileName := filepath.Base(videoFullPath)
 	frontName := strings.ReplaceAll(fileName, filepath.Ext(fileName), "")
 	outDirSubPath := filepath.Join(outDirPath, frontName, startTimeString+"-"+timeLength)
-	outSubFileFPath := filepath.Join(outDirSubPath, frontName+common.SubExtSRT)
+
 	// 字幕的相对位置
-	subRelPath, err := filepath.Rel(outDirPath, outSubFileFPath)
-	if err != nil {
-		return "", "", err
+	outSubFPaths := make([]string, 0)
+	for i := 0; i < len(subFullPaths); i++ {
+
+		outSubFileFPath := filepath.Join(outDirSubPath, fmt.Sprintf(frontName+"_%d"+common.SubExtSRT, i))
+
+		var subRelPath string
+		subRelPath, err = filepath.Rel(outDirPath, outSubFileFPath)
+		if err != nil {
+			return "", nil, err
+		}
+		outSubFPaths = append(outSubFPaths, subRelPath)
 	}
+
 	// outputlist.m3u8 的相对位置
 	outputListRelPath, err := filepath.Rel(outDirPath, filepath.Join(outDirSubPath, "outputlist.m3u8"))
 	if err != nil {
-		return "", "", err
+		return "", nil, err
 	}
 
-	return outputListRelPath, subRelPath, nil
+	return outputListRelPath, outSubFPaths, nil
 }
 
 // IsJobInQueue 是否正在队列中排队,或者正在被处理
@@ -204,7 +218,7 @@ func (p *PreviewQueue) processSub(job *Job) error {
 		return err
 	}
 	// 具体处理这个任务,这个任务在加入队列之前就可以预测将要存放在哪,以及名称是什么
-	m3u8FPath, subFPath, err := p.ffmpegHelper.ExportVideoHLSAndSubByTimeRange(job.VideoFPath, job.SubFPath, job.StartTime, job.EndTime, segmentTime, nowOutRootDirPath)
+	m3u8FPath, subFPath, err := p.ffmpegHelper.ExportVideoHLSAndSubByTimeRange(job.VideoFPath, job.SubFPaths, job.StartTime, job.EndTime, segmentTime, nowOutRootDirPath)
 	if err != nil {
 		return err
 	}
@@ -215,10 +229,10 @@ func (p *PreviewQueue) processSub(job *Job) error {
 }
 
 type Job struct {
-	VideoFPath string `json:"video_f_path"`
-	SubFPath   string `json:"sub_f_path"`
-	StartTime  string `json:"start_time"`
-	EndTime    string `json:"end_time"`
+	VideoFPath string   `json:"video_f_path"`
+	SubFPaths  []string `json:"sub_f_paths"`
+	StartTime  string   `json:"start_time"`
+	EndTime    string   `json:"end_time"`
 }
 
 type Reply struct {