Răsfoiți Sursa

完成,第二版本的字幕时间轴校正

Signed-off-by: allan716 <[email protected]>
allan716 3 ani în urmă
părinte
comite
6d8009fba2

+ 3 - 18
cmd/chinesesubfinder/main.go

@@ -3,7 +3,6 @@ package main
 import (
 	"github.com/allanpk716/ChineseSubFinder/internal"
 	commonValue "github.com/allanpk716/ChineseSubFinder/internal/common"
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_timeline_fixer"
 	config2 "github.com/allanpk716/ChineseSubFinder/internal/pkg/config"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/hot_fix"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
@@ -157,6 +156,9 @@ func DownLoadStart(httpProxy string) {
 			WhenSubSupplierInvalidWebHook: config.WhenSubSupplierInvalidWebHook,
 			EmbyConfig:                    config.EmbyConfig,
 			SaveOneSeasonSub:              config.SaveOneSeasonSub,
+
+			SubTimelineFixerConfig: config.SubTimelineFixerConfig,
+			FixTimeLine:            config.FixTimeLine,
 		})
 
 	defer func() {
@@ -202,23 +204,6 @@ func DownLoadStart(httpProxy string) {
 		log.Errorln("RefreshEmbySubList", err)
 		return
 	}
-
-	// 开始字幕的统一校正
-	log.Infoln("Auto Fix Sub Timeline Start...")
-	fixer := sub_timeline_fixer.NewSubTimelineFixerHelper(config.EmbyConfig, config.SubTimelineFixerConfig)
-	err = fixer.FixRecentlyItemsSubTimeline(config.MovieFolder, config.SeriesFolder)
-	if err != nil {
-		log.Errorln("FixRecentlyItemsSubTimeline", err)
-		return
-	}
-	log.Infoln("Auto Fix Sub Timeline End")
-	// 再次刷新
-	// 刷新 Emby 的字幕,下载完毕字幕了,就统一刷新一下
-	err = downloader.RefreshEmbySubList()
-	if err != nil {
-		log.Errorln("RefreshEmbySubList", err)
-		return
-	}
 }
 
 var (

+ 21 - 0
internal/downloader.go

@@ -11,6 +11,7 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/shooter"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/xunlei"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/zimuku"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_timeline_fixer"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
@@ -41,6 +42,8 @@ type Downloader struct {
 	subFormatter             ifaces.ISubFormatter          //	字幕格式化命名的实现
 	subNameFormatter         subcommon.FormatterName       // 从 inSubFormatter 推断出来
 	needForcedScanAndDownSub bool                          // 将会强制扫描所有的视频,下载字幕,替换已经存在的字幕,不进行时间段和已存在则跳过的判断。且不会进过 Emby API 的逻辑,智能进行强制去以本程序的方式去扫描。
+
+	subTimelineFixerHelperEx *sub_timeline_fixer.SubTimelineFixerHelperEx // 字幕时间轴校正
 }
 
 func NewDownloader(inSubFormatter ifaces.ISubFormatter, _reqParam ...types.ReqParam) *Downloader {
@@ -81,6 +84,13 @@ func NewDownloader(inSubFormatter ifaces.ISubFormatter, _reqParam ...types.ReqPa
 	downloader.movieFileFullPathList = make([]string, 0)
 	downloader.seriesSubNeedDlMap = make(map[string][]emby.EmbyMixInfo)
 
+	// 初始化,字幕校正的实例
+	downloader.subTimelineFixerHelperEx = sub_timeline_fixer.NewSubTimelineFixerHelperEx(downloader.reqParam.SubTimelineFixerConfig)
+
+	if downloader.reqParam.FixTimeLine == true {
+		downloader.subTimelineFixerHelperEx.Check()
+	}
+
 	return &downloader
 }
 
@@ -161,6 +171,7 @@ func (d Downloader) DownloadSub4Movie(dir string) error {
 	}()
 	var err error
 	d.log.Infoln("Download Movie Sub Started...")
+	// -----------------------------------------------------
 	// 优先判断特殊的操作
 	if d.needForcedScanAndDownSub == true {
 		// 全扫描
@@ -185,6 +196,7 @@ func (d Downloader) DownloadSub4Movie(dir string) error {
 			}
 		}
 	}
+	// -----------------------------------------------------
 	// 并发控制
 	movieDlFunc := func(i interface{}) error {
 		inData := i.(InputData)
@@ -216,6 +228,7 @@ func (d Downloader) DownloadSub4Movie(dir string) error {
 
 		return nil
 	}
+	// -----------------------------------------------------
 	antPool, err := ants.NewPoolWithFunc(d.reqParam.Threads, func(inData interface{}) {
 		data := inData.(InputData)
 		defer data.Wg.Done()
@@ -594,6 +607,14 @@ func (d Downloader) writeSubFile2VideoPath(videoFileFullPath string, finalSubFil
 	d.log.Infoln("OrgSubName:", finalSubFile.Name)
 	d.log.Infoln("SubDownAt:", desSubFullPath)
 
+	// 然后还需要判断是否需要校正字幕的时间轴
+	if d.reqParam.FixTimeLine == true {
+		err = d.subTimelineFixerHelperEx.Process(videoFileFullPath, desSubFullPath)
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 

+ 268 - 0
internal/logic/sub_timeline_fixer/SubTimelineFixerHelperEx.go

@@ -0,0 +1,268 @@
+package sub_timeline_fixer
+
+import (
+	"errors"
+	"fmt"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/ass"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/srt"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/ffmpeg_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/vad"
+	"github.com/allanpk716/ChineseSubFinder/internal/types/sub_timeline_fiexer"
+	"github.com/allanpk716/ChineseSubFinder/internal/types/subparser"
+	"github.com/emirpasic/gods/maps/treemap"
+	"github.com/emirpasic/gods/utils"
+	"os"
+)
+
+type SubTimelineFixerHelperEx struct {
+	ffmpegHelper       *ffmpeg_helper.FFMPEGHelper
+	subParserHub       *sub_parser_hub.SubParserHub
+	timelineFixer      *sub_timeline_fixer.SubTimelineFixer
+	needDownloadFFMPeg bool
+}
+
+func NewSubTimelineFixerHelperEx(fixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixerHelperEx {
+	return &SubTimelineFixerHelperEx{
+		ffmpegHelper:       ffmpeg_helper.NewFFMPEGHelper(),
+		subParserHub:       sub_parser_hub.NewSubParserHub(ass.NewParser(), srt.NewParser()),
+		timelineFixer:      sub_timeline_fixer.NewSubTimelineFixer(fixerConfig),
+		needDownloadFFMPeg: false,
+	}
+}
+
+// Check 是否安装了 ffmpeg 和 ffprobe
+func (s *SubTimelineFixerHelperEx) Check() bool {
+	version, err := s.ffmpegHelper.Version()
+	if err != nil {
+		s.needDownloadFFMPeg = false
+		log_helper.GetLogger().Errorln("Need Install ffmpeg and ffprobe !")
+		return false
+	}
+	s.needDownloadFFMPeg = true
+	log_helper.GetLogger().Infoln(version)
+	return true
+}
+
+func (s SubTimelineFixerHelperEx) Process(videoFileFullPath, srcSubFPath string) error {
+
+	if s.needDownloadFFMPeg == false {
+		log_helper.GetLogger().Errorln("Need Install ffmpeg and ffprobe, Can't Do TimeLine Fix")
+		return nil
+	}
+
+	var infoSrc *subparser.FileInfo
+	bProcess := false
+	offSetTime := 0.0
+	// 先尝试获取内置字幕的信息
+	bok, ffmpegInfo, err := s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Subtitle)
+	if err != nil {
+		return err
+	}
+	if bok == false {
+		return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo = false Subtitle -- " + videoFileFullPath)
+	}
+	// 内置的字幕,这里只列举一种格式出来,其实会有一个字幕的 srt 和 ass 两种格式都导出存在
+	// len(ffmpegInfo.SubtitleInfoList)
+	if len(ffmpegInfo.SubtitleInfoList) <= 0 {
+		// 如果内置字幕没有,那么就需要尝试获取音频信息
+		bok, ffmpegInfo, err = s.ffmpegHelper.GetFFMPEGInfo(videoFileFullPath, ffmpeg_helper.Audio)
+		if err != nil {
+			return err
+		}
+		if bok == false {
+			return errors.New("SubTimelineFixerHelperEx.Process.GetFFMPEGInfo = false Audio -- " + videoFileFullPath)
+		}
+
+		// 使用音频进行时间轴的校正
+		if len(ffmpegInfo.AudioInfoList) <= 0 {
+			log_helper.GetLogger().Warnln("Can`t find audio info, skip time fix --", videoFileFullPath)
+			return nil
+		}
+		bProcess, infoSrc, offSetTime, err = s.processByAudio(ffmpegInfo.AudioInfoList[0].FullPath, srcSubFPath)
+	} else {
+		// 使用内置的字幕进行时间轴的校正,这里需要考虑一个问题,内置的字幕可能是有问题的(先考虑一种,就是字幕的长度不对,是一小段的)
+		// 那么就可以比较多个内置字幕的大小选择大的去使用
+		baseSubFPath := ""
+		if len(ffmpegInfo.SubtitleInfoList) > 1 {
+			// 如果有多个内置的字幕,还是要判断下的,选体积最大的那个吧
+			fileSizes := treemap.NewWith(utils.Int64Comparator)
+			for index, info := range ffmpegInfo.ExternalSubInfos {
+				fi, err := os.Stat(info.FileFullPath)
+				if err != nil {
+					fileSizes.Put(0, index)
+				} else {
+					fileSizes.Put(fi.Size(), index)
+				}
+			}
+			_, index := fileSizes.Max()
+			baseSubFPath = ffmpegInfo.ExternalSubInfos[index.(int)].FileFullPath
+		} else {
+			// 如果只有一个字幕就没必要纠结了,用这个去对比吧
+			baseSubFPath = ffmpegInfo.SubtitleInfoList[0].FullPath
+		}
+		bProcess, infoSrc, offSetTime, err = s.processBySub(baseSubFPath, srcSubFPath)
+	}
+
+	// 开始调整字幕时间轴
+	if bProcess == false {
+		log_helper.GetLogger().Infoln("Skip TimeLine Fix --", srcSubFPath)
+		return nil
+	}
+	err = s.changeTimeLineAndSave(infoSrc, offSetTime, srcSubFPath)
+	if err != nil {
+		return err
+	}
+
+	log_helper.GetLogger().Infoln("Fix Offset:", offSetTime, srcSubFPath)
+	log_helper.GetLogger().Infoln("BackUp Org SubFile:", offSetTime, srcSubFPath+backUpExt)
+
+	return nil
+}
+
+func (s SubTimelineFixerHelperEx) processBySub(baseSubFileFPath, srcSubFileFPath string) (bool, *subparser.FileInfo, float64, error) {
+
+	bFind, infoBase, err := s.subParserHub.DetermineFileTypeFromFile(baseSubFileFPath)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	if bFind == false {
+		log_helper.GetLogger().Warnln("sub not match --", baseSubFileFPath)
+		return false, nil, 0, nil
+	}
+	bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromFile(srcSubFileFPath)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	if bFind == false {
+		log_helper.GetLogger().Warnln("sub not match --", srcSubFileFPath)
+		return false, nil, 0, nil
+	}
+	// ---------------------------------------------------------------------------------------
+	baseUnitNew, err := sub_helper.GetVADInfoFeatureFromSubNew(infoBase, s.timelineFixer.FixerConfig.V2_FrontAndEndPerBase)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	srcUnitNew, err := sub_helper.GetVADInfoFeatureFromSubNew(infoSrc, s.timelineFixer.FixerConfig.V2_FrontAndEndPerSrc)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	// ---------------------------------------------------------------------------------------
+	bok, offsetTime, sd, err := s.timelineFixer.GetOffsetTimeV2(baseUnitNew, srcUnitNew, nil)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	if bok == false {
+		log_helper.GetLogger().Warnln("processSub.GetOffsetTimeV2 return false -- " + baseSubFileFPath + " -- " + srcSubFileFPath)
+		return false, nil, 0, nil
+	}
+	// SD 要达标
+	if sd > s.timelineFixer.FixerConfig.V2_MaxStartTimeDiffSD {
+		log_helper.GetLogger().Warnln(fmt.Sprintf("skip, processBySub sd: %v > %v -- %s", sd, s.timelineFixer.FixerConfig.V2_MaxStartTimeDiffSD, srcSubFileFPath))
+		return false, nil, 0, nil
+	}
+	// 时间偏移的最小值才修正
+	if offsetTime < s.timelineFixer.FixerConfig.V2_MinOffset && offsetTime > -s.timelineFixer.FixerConfig.V2_MinOffset {
+		log_helper.GetLogger().Warnln(fmt.Sprintf("skip, processBySub offset: %v > -%v && %v < %v-- %s",
+			offsetTime, s.timelineFixer.FixerConfig.V2_MinOffset,
+			offsetTime, s.timelineFixer.FixerConfig.V2_MinOffset,
+			srcSubFileFPath))
+		return false, nil, 0, nil
+	}
+
+	return true, infoSrc, offsetTime, nil
+}
+
+func (s SubTimelineFixerHelperEx) processByAudio(baseAudioFileFPath, srcSubFileFPath string) (bool, *subparser.FileInfo, float64, error) {
+
+	audioVADInfos, err := vad.GetVADInfoFromAudio(vad.AudioInfo{
+		FileFullPath: baseAudioFileFPath,
+		SampleRate:   16000,
+		BitDepth:     16,
+	}, true)
+	if err != nil {
+		return false, nil, 0, err
+	}
+
+	bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromFile(srcSubFileFPath)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	if bFind == false {
+		log_helper.GetLogger().Warnln("sub not match --", srcSubFileFPath)
+		return false, nil, 0, nil
+	}
+	// ---------------------------------------------------------------------------------------
+	srcUnitNew, err := sub_helper.GetVADInfoFeatureFromSubNew(infoSrc, s.timelineFixer.FixerConfig.V2_FrontAndEndPerSrc)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	// ---------------------------------------------------------------------------------------
+	bok, offsetTime, sd, err := s.timelineFixer.GetOffsetTimeV2(nil, srcUnitNew, audioVADInfos)
+	if err != nil {
+		return false, nil, 0, err
+	}
+	if bok == false {
+		log_helper.GetLogger().Warnln("processSub.GetOffsetTimeV2 return false -- " + baseAudioFileFPath + " -- " + srcSubFileFPath)
+		return false, nil, 0, nil
+	}
+
+	// SD 要达标
+	if sd > s.timelineFixer.FixerConfig.V2_MaxStartTimeDiffSD {
+		log_helper.GetLogger().Warnln(fmt.Sprintf("processByAudio sd: %v > %v -- %s", sd, s.timelineFixer.FixerConfig.V2_MaxStartTimeDiffSD, srcSubFileFPath))
+		return false, nil, 0, nil
+	}
+	// 时间偏移的最小值才修正
+	if offsetTime < s.timelineFixer.FixerConfig.V2_MinOffset && offsetTime > -s.timelineFixer.FixerConfig.V2_MinOffset {
+		log_helper.GetLogger().Warnln(fmt.Sprintf("skip, processByAudio offset: %v > -%v && %v < %v-- %s",
+			offsetTime, s.timelineFixer.FixerConfig.V2_MinOffset,
+			offsetTime, s.timelineFixer.FixerConfig.V2_MinOffset,
+			srcSubFileFPath))
+		return false, nil, 0, nil
+	}
+
+	return true, infoSrc, offsetTime, nil
+}
+
+func (s SubTimelineFixerHelperEx) changeTimeLineAndSave(infoSrc *subparser.FileInfo, offsetTime float64, desSubSaveFPath string) error {
+	/*
+		修复的字幕先存放到缓存目录,然后需要把原有的字幕进行“备份”,改名,然后再替换过来
+	*/
+	subFileName := desSubSaveFPath + tmpExt
+	if my_util.IsFile(subFileName) == true {
+		err := os.Remove(subFileName)
+		if err != nil {
+			return err
+		}
+	}
+	_, err := s.timelineFixer.FixSubTimeline(infoSrc, offsetTime, subFileName)
+	if err != nil {
+		return err
+	}
+
+	if my_util.IsFile(desSubSaveFPath+backUpExt) == true {
+		err = os.Remove(desSubSaveFPath + backUpExt)
+		if err != nil {
+			return err
+		}
+	}
+
+	err = os.Rename(desSubSaveFPath, desSubSaveFPath+backUpExt)
+	if err != nil {
+		return err
+	}
+
+	err = os.Rename(subFileName, desSubSaveFPath)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+const tmpExt = ".csf-tmp"
+const backUpExt = ".csf-bk"

+ 49 - 0
internal/logic/sub_timeline_fixer/SubTimelineFixerHelperEx_test.go

@@ -0,0 +1,49 @@
+package sub_timeline_fixer
+
+import (
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/config"
+	"testing"
+)
+
+func TestSubTimelineFixerHelperEx_Check(t *testing.T) {
+
+	if NewSubTimelineFixerHelperEx(config.GetConfig().SubTimelineFixerConfig).Check() == false {
+		t.Fatal("Need Install FFMPEG")
+	}
+}
+
+func TestSubTimelineFixerHelperEx_Process(t *testing.T) {
+
+	type args struct {
+		videoFileFullPath string
+		srcSubFPath       string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "R&M S05E01", args: args{
+				videoFileFullPath: "X:\\连续剧\\瑞克和莫蒂 (2013)\\Season 5\\Rick and Morty - S05E01 - Mort Dinner Rick Andre WEBDL-1080p.mkv",
+				srcSubFPath:       "C:\\WorkSpace\\Go2Hell\\src\\github.com\\allanpk716\\ChineseSubFinder\\internal\\logic\\sub_timeline_fixer\\CSF-SubFixCache\\Rick and Morty - S05E01 - Mort Dinner Rick Andre WEBDL-1080p\\R&M S05E01 - 简.ass"},
+			wantErr: false,
+		},
+		{
+			name: "Foundation (2021) - S01E09", args: args{
+				videoFileFullPath: "X:\\连续剧\\Foundation (2021)\\Season 1\\Foundation (2021) - S01E09 - The First Crisis WEBDL-1080p.mkv",
+				srcSubFPath:       "C:\\WorkSpace\\Go2Hell\\src\\github.com\\allanpk716\\ChineseSubFinder\\internal\\logic\\sub_timeline_fixer\\CSF-SubFixCache\\Foundation (2021) - S01E09 - The First Crisis WEBDL-1080p\\chinese(简英,zimuku).default.ass"},
+			wantErr: false,
+		},
+	}
+
+	s := NewSubTimelineFixerHelperEx(config.GetConfig().SubTimelineFixerConfig)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			if err := s.Process(tt.args.videoFileFullPath, tt.args.srcSubFPath); (err != nil) != tt.wantErr {
+				t.Errorf("Process() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}

+ 2 - 5
internal/pkg/sub_timeline_fixer/fixer.go

@@ -3,7 +3,6 @@ package sub_timeline_fixer
 import (
 	"errors"
 	"fmt"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/ffmpeg_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
@@ -26,14 +25,12 @@ import (
 )
 
 type SubTimelineFixer struct {
-	FixerConfig  sub_timeline_fiexer.SubTimelineFixerConfig
-	ffmpegHelper *ffmpeg_helper.FFMPEGHelper
+	FixerConfig sub_timeline_fiexer.SubTimelineFixerConfig
 }
 
 func NewSubTimelineFixer(fixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixer {
 	return &SubTimelineFixer{
-		FixerConfig:  fixerConfig,
-		ffmpegHelper: ffmpeg_helper.NewFFMPEGHelper(),
+		FixerConfig: fixerConfig,
 	}
 }
 

+ 1 - 0
internal/types/config.go

@@ -20,6 +20,7 @@ type Config struct {
 	CustomVideoExts               string                                     // 自定义视频扩展名,多个扩展名用英文逗号分隔。是在原有基础上新增。
 	RunAtStartup                  bool                                       // 扫描任务是否在启动程序的时候马上执行 见,https://github.com/allanpk716/ChineseSubFinder/issues/50
 	SubTimelineFixerConfig        sub_timeline_fiexer.SubTimelineFixerConfig // 时间轴校正配置信息
+	FixTimeLine                   bool                                       // 	开启校正字幕时间轴,默认 false
 
 	MovieFolder  string // 电影文件夹
 	SeriesFolder string // 连续剧文件夹

+ 7 - 1
internal/types/reqparam.go

@@ -1,6 +1,9 @@
 package types
 
-import "github.com/allanpk716/ChineseSubFinder/internal/types/emby"
+import (
+	"github.com/allanpk716/ChineseSubFinder/internal/types/emby"
+	"github.com/allanpk716/ChineseSubFinder/internal/types/sub_timeline_fiexer"
+)
 
 // ReqParam 可选择传入的参数
 type ReqParam struct {
@@ -13,6 +16,9 @@ type ReqParam struct {
 	EmbyConfig                    emby.EmbyConfig // Emby API 高阶设置参数
 	SaveOneSeasonSub              bool            // 保存整个季度的字幕
 
+	SubTimelineFixerConfig sub_timeline_fiexer.SubTimelineFixerConfig // 时间轴校正配置信息
+	FixTimeLine            bool                                       // 	开启校正字幕时间轴,默认 false
+
 	HttpProxy string // HttpClient 相关
 	UserAgent string // HttpClient 相关
 	Referer   string // HttpClient 相关