Sfoglia il codice sorgente

保存进度

Signed-off-by: 716 <[email protected]>
716 3 anni fa
parent
commit
0898ec6e3d
32 ha cambiato i file con 1844 aggiunte e 1848 eliminazioni
  1. 4 4
      cmd/chinesesubfinder/main.go
  2. 0 3
      internal/ifaces/iSupplier.go
  3. 12 22
      internal/logic/downloader_helper/downloader_helper.go
  4. 4 3
      internal/logic/emby_helper/embyhelper.go
  5. 13 7
      internal/logic/pre_download_process/pre_download_proces.go
  6. 10 13
      internal/logic/sub_supplier/shooter/shooter.go
  7. 25 22
      internal/logic/sub_supplier/subhd/subhd.go
  8. 10 13
      internal/logic/sub_supplier/xunlei/xunlei.go
  9. 14 18
      internal/logic/sub_supplier/zimuku/zimuku.go
  10. 8 5
      internal/logic/sub_timeline_fixer/SubTimelineFixerHelperEx.go
  11. 322 321
      internal/logic/sub_timeline_fixer/sub_timeline_fixer_helper.go
  12. 42 62
      internal/pkg/downloader/downloader.go
  13. 6 6
      internal/pkg/downloader/downloader_things.go
  14. 23 25
      internal/pkg/emby_api/emby_api.go
  15. 11 0
      internal/pkg/global_value/global_value.go
  16. 33 32
      internal/pkg/my_util/util.go
  17. 0 48
      internal/pkg/proxy_helper/proxy_helper.go
  18. 0 22
      internal/pkg/proxy_helper/proxy_helper_test.go
  19. 13 7
      internal/pkg/settings/advanced_settings.go
  20. 0 2
      internal/pkg/settings/common_settings.go
  21. 10 0
      internal/pkg/settings/emby_settings.go
  22. 7 0
      internal/pkg/settings/proxy_settings.go
  23. 34 12
      internal/pkg/settings/settings.go
  24. 20 0
      internal/pkg/settings/timeline_fixer_settings.go
  25. 1076 1075
      internal/pkg/sub_timeline_fixer/fixer.go
  26. 24 12
      internal/pkg/sub_timeline_fixer/restore.go
  27. 73 0
      internal/pkg/url_connectedness_helper/url_connectedness_helper.go
  28. 49 0
      internal/pkg/url_connectedness_helper/url_connectedness_helper_test.go
  29. 0 9
      internal/types/emby/config.go
  30. 0 39
      internal/types/reqparam.go
  31. 0 65
      internal/types/sub_timeline_fiexer/sub_timeline_fixer_config.go
  32. 1 1
      scripts/temp-test.sh

+ 4 - 4
cmd/chinesesubfinder/main.go

@@ -7,10 +7,10 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/proxy_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/rod_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/url_connectedness_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	"github.com/prometheus/common/log"
 	"github.com/robfig/cron/v3"
@@ -41,12 +41,12 @@ func main() {
 		log.Infoln("UseProxy = false")
 	} else {
 		log.Infoln("UseProxy:", httpProxy)
-		proxySpeed, proxyStatus, err := proxy_helper.ProxyTest(httpProxy)
+		proxySpeed, proxyStatus, err := url_connectedness_helper.UrlConnectednessTest(httpProxy)
 		if err != nil {
-			log.Errorln("ProxyTest Target Site http://google.com", err)
+			log.Errorln("UrlConnectednessTest Target Site http://google.com", err)
 			return
 		} else {
-			log.Infoln("ProxyTest Target Site http://google.com", "Speed:", proxySpeed, "Status:", proxyStatus)
+			log.Infoln("UrlConnectednessTest Target Site http://google.com", "Speed:", proxySpeed, "Status:", proxyStatus)
 		}
 	}
 

+ 0 - 3
internal/ifaces/iSupplier.go

@@ -1,7 +1,6 @@
 package ifaces
 
 import (
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/series"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/supplier"
 )
@@ -9,8 +8,6 @@ import (
 type ISupplier interface {
 	GetSupplierName() string
 
-	GetReqParam() types.ReqParam
-
 	GetSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error)
 
 	GetSubListFromFile4Series(seriesInfo *series.SeriesInfo) ([]supplier.SubInfo, error)

+ 12 - 22
internal/logic/downloader_helper/downloader_helper.go

@@ -4,22 +4,25 @@ import (
 	commonValue "github.com/allanpk716/ChineseSubFinder/internal/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/downloader"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/something_static"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	"github.com/sirupsen/logrus"
 )
 
 type DownloaderHelper struct {
 	downloader *downloader.Downloader
+	settings   settings.Settings
 	logger     *logrus.Logger
 }
 
-func NewDownloaderHelper() *DownloaderHelper {
-	return &DownloaderHelper{}
+func NewDownloaderHelper(settings settings.Settings) *DownloaderHelper {
+	return &DownloaderHelper{
+		settings: settings,
+	}
 }
 
-func (d DownloaderHelper) Start(config types.Config) error {
+func (d DownloaderHelper) Start() error {
 	var err error
 	// 清理通知中心
 	notify_center.Notify.Clear()
@@ -37,20 +40,7 @@ func (d DownloaderHelper) Start(config types.Config) error {
 	}
 
 	// 下载实例
-	d.downloader, err = downloader.NewDownloader(sub_formatter.GetSubFormatter(config.SubNameFormatter),
-		types.ReqParam{
-			HttpProxy:                     config.HttpProxy,
-			DebugMode:                     config.DebugMode,
-			SaveMultiSub:                  config.SaveMultiSub,
-			Threads:                       config.Threads,
-			SubTypePriority:               config.SubTypePriority,
-			WhenSubSupplierInvalidWebHook: config.WhenSubSupplierInvalidWebHook,
-			EmbyConfig:                    config.EmbyConfig,
-			SaveOneSeasonSub:              config.SaveOneSeasonSub,
-
-			SubTimelineFixerConfig: config.SubTimelineFixerConfig,
-			FixTimeLine:            config.FixTimeLine,
-		})
+	d.downloader, err = downloader.NewDownloader(sub_formatter.GetSubFormatter(d.settings.AdvancedSettings.SubNameFormatter), d.settings)
 
 	if err != nil {
 		d.logger.Errorln("NewDownloader", err)
@@ -72,7 +62,7 @@ func (d DownloaderHelper) Start(config types.Config) error {
 	}
 	// 从 csf-bk 文件还原时间轴修复前的字幕文件
 	if d.downloader.NeedRestoreFixTimeLineBK == true {
-		err = d.downloader.RestoreFixTimelineBK(config.MovieFolder, config.SeriesFolder)
+		err = d.downloader.RestoreFixTimelineBK()
 		if err != nil {
 			d.logger.Errorln("RestoreFixTimelineBK", err)
 		}
@@ -83,19 +73,19 @@ func (d DownloaderHelper) Start(config types.Config) error {
 		d.logger.Errorln("RefreshEmbySubList", err)
 		return err
 	}
-	err = d.downloader.GetUpdateVideoListFromEmby(config.MovieFolder, config.SeriesFolder)
+	err = d.downloader.GetUpdateVideoListFromEmby()
 	if err != nil {
 		d.logger.Errorln("GetUpdateVideoListFromEmby", err)
 		return err
 	}
 	// 开始下载,电影
-	err = d.downloader.DownloadSub4Movie(config.MovieFolder)
+	err = d.downloader.DownloadSub4Movie()
 	if err != nil {
 		d.logger.Errorln("DownloadSub4Movie", err)
 		return err
 	}
 	// 开始下载,连续剧
-	err = d.downloader.DownloadSub4Series(config.SeriesFolder)
+	err = d.downloader.DownloadSub4Series()
 	if err != nil {
 		d.logger.Errorln("DownloadSub4Series", err)
 		return err

+ 4 - 3
internal/logic/emby_helper/embyhelper.go

@@ -5,6 +5,7 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/common"
 	embyHelper "github.com/allanpk716/ChineseSubFinder/internal/pkg/emby_api"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/emby"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/language"
@@ -19,13 +20,13 @@ import (
 
 type EmbyHelper struct {
 	embyApi    *embyHelper.EmbyApi
-	EmbyConfig emby.EmbyConfig
+	EmbyConfig settings.EmbySettings
 	threads    int
 	timeOut    time.Duration
 	listLock   sync.Mutex
 }
 
-func NewEmbyHelper(embyConfig emby.EmbyConfig) *EmbyHelper {
+func NewEmbyHelper(embyConfig settings.EmbySettings) *EmbyHelper {
 	em := EmbyHelper{EmbyConfig: embyConfig}
 	em.embyApi = embyHelper.NewEmbyApi(embyConfig)
 	em.threads = 6
@@ -33,7 +34,7 @@ func NewEmbyHelper(embyConfig emby.EmbyConfig) *EmbyHelper {
 	return &em
 }
 
-func (em *EmbyHelper) GetRecentlyAddVideoList(movieRootDir, seriesRootDir string) ([]emby.EmbyMixInfo, map[string][]emby.EmbyMixInfo, error) {
+func (em *EmbyHelper) GetRecentlyAddVideoList() ([]emby.EmbyMixInfo, map[string][]emby.EmbyMixInfo, error) {
 
 	// 获取电影和连续剧的文件夹名称
 	movieFolderName := filepath.Base(movieRootDir)

+ 13 - 7
internal/logic/pre_download_process/pre_download_proces.go

@@ -4,8 +4,8 @@ import (
 	"errors"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/proxy_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/url_connectedness_helper"
 )
 
 type PreDownloadProcess struct {
@@ -23,6 +23,7 @@ func (p *PreDownloadProcess) Init() *PreDownloadProcess {
 		return p
 	}
 	p.stageName = "Init"
+	// ------------------------------------------------------------------------
 	// 如果是 Debug 模式,那么就需要写入特殊文件
 	if settings.GetSettings().AdvancedSettings.DebugMode == true {
 		err := log_helper.WriteDebugFile()
@@ -37,19 +38,21 @@ func (p *PreDownloadProcess) Init() *PreDownloadProcess {
 			return p
 		}
 	}
+	// ------------------------------------------------------------------------
 	// 测试代理
-	if settings.GetSettings().CommonSettings.UseHttpProxy == false {
+	if settings.GetSettings().AdvancedSettings.ProxySettings.UseHttpProxy == false {
 		log_helper.GetLogger().Infoln("UseHttpProxy = false")
 	} else {
-		log_helper.GetLogger().Infoln("UseHttpProxy:", settings.GetSettings().CommonSettings.HttpProxyAddress)
-		proxySpeed, proxyStatus, err := proxy_helper.ProxyTest(settings.GetSettings().CommonSettings.HttpProxyAddress)
+		log_helper.GetLogger().Infoln("UseHttpProxy:", settings.GetSettings().AdvancedSettings.ProxySettings.HttpProxyAddress)
+		proxySpeed, proxyStatus, err := url_connectedness_helper.UrlConnectednessTest(settings.GetSettings().AdvancedSettings.ProxySettings.HttpProxyAddress)
 		if err != nil {
-			p.gError = errors.New("ProxyTest Target Site http://google.com " + err.Error())
+			p.gError = errors.New("UrlConnectednessTest Target Site http://google.com " + err.Error())
 			return p
 		} else {
-			log_helper.GetLogger().Infoln("ProxyTest Target Site http://google.com", "Speed:", proxySpeed, "Status:", proxyStatus)
+			log_helper.GetLogger().Infoln("UrlConnectednessTest Target Site http://google.com", "Speed:", proxySpeed, "Status:", proxyStatus)
 		}
 	}
+	// ------------------------------------------------------------------------
 	// 判断文件夹是否存在
 	if len(settings.GetSettings().CommonSettings.MoviePaths) < 1 {
 		log_helper.GetLogger().Infoln("MoviePaths not set, len == 0")
@@ -71,6 +74,10 @@ func (p *PreDownloadProcess) Init() *PreDownloadProcess {
 			log_helper.GetLogger().Infoln("SeriesPaths Index", i, "--", path)
 		}
 	}
+	// ------------------------------------------------------------------------
+	// Hot Fix Start
+
+	// ------------------------------------------------------------------------
 
 	return p
 }
@@ -81,7 +88,6 @@ func (p *PreDownloadProcess) Start() *PreDownloadProcess {
 		return p
 	}
 	p.stageName = "Start"
-	// do something
 
 	return p
 }

+ 10 - 13
internal/logic/sub_supplier/shooter/shooter.go

@@ -7,10 +7,11 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/language"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/series"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/supplier"
+	"github.com/huandu/go-clone"
 	"github.com/sirupsen/logrus"
 	"math"
 	"os"
@@ -19,22 +20,22 @@ import (
 )
 
 type Supplier struct {
-	reqParam types.ReqParam
+	settings settings.Settings
 	log      *logrus.Logger
 	topic    int
 }
 
-func NewSupplier(_reqParam ...types.ReqParam) *Supplier {
+func NewSupplier(_settings settings.Settings) *Supplier {
 
 	sup := Supplier{}
 	sup.log = log_helper.GetLogger()
 	sup.topic = common.DownloadSubsPerSite
-	if len(_reqParam) > 0 {
-		sup.reqParam = _reqParam[0]
-		if sup.reqParam.Topic > 0 && sup.reqParam.Topic != sup.topic {
-			sup.topic = sup.reqParam.Topic
-		}
+
+	sup.settings = clone.Clone(_settings).(settings.Settings)
+	if sup.settings.AdvancedSettings.Topic > 0 && sup.settings.AdvancedSettings.Topic != sup.topic {
+		sup.topic = sup.settings.AdvancedSettings.Topic
 	}
+
 	return &sup
 }
 
@@ -42,10 +43,6 @@ func (s Supplier) GetSupplierName() string {
 	return common.SubSiteShooter
 }
 
-func (s Supplier) GetReqParam() types.ReqParam {
-	return s.reqParam
-}
-
 func (s Supplier) GetSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error) {
 	return s.getSubListFromFile(filePath)
 }
@@ -81,7 +78,7 @@ func (s Supplier) getSubListFromFile(filePath string) ([]supplier.SubInfo, error
 
 	fileName := filepath.Base(filePath)
 
-	httpClient := my_util.NewHttpClient(s.reqParam)
+	httpClient := my_util.NewHttpClient(*s.settings.AdvancedSettings.ProxySettings)
 	resp, err := httpClient.R().
 		SetFormData(map[string]string{
 			"filehash": hash,

+ 25 - 22
internal/logic/sub_supplier/subhd/subhd.go

@@ -11,13 +11,14 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/rod_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/language"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/series"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/supplier"
 	"github.com/go-rod/rod"
 	"github.com/go-rod/rod/lib/launcher"
+	"github.com/huandu/go-clone"
 	"github.com/nfnt/resize"
 	"github.com/sirupsen/logrus"
 	"image/jpeg"
@@ -31,32 +32,38 @@ import (
 )
 
 type Supplier struct {
-	reqParam    types.ReqParam
-	log         *logrus.Logger
-	topic       int
-	rodLauncher *launcher.Launcher
-	tt          time.Duration
-	debugMode   bool
+	settings         settings.Settings
+	log              *logrus.Logger
+	topic            int
+	rodLauncher      *launcher.Launcher
+	tt               time.Duration
+	debugMode        bool
+	httpProxyAddress string
 }
 
-func NewSupplier(_reqParam ...types.ReqParam) *Supplier {
+func NewSupplier(_settings settings.Settings) *Supplier {
 
 	sup := Supplier{}
 	sup.log = log_helper.GetLogger()
 	sup.topic = common.DownloadSubsPerSite
-	if len(_reqParam) > 0 {
-		sup.reqParam = _reqParam[0]
-		if sup.reqParam.Topic > 0 && sup.reqParam.Topic != sup.topic {
-			sup.topic = sup.reqParam.Topic
-		}
+
+	sup.settings = clone.Clone(_settings).(settings.Settings)
+	if sup.settings.AdvancedSettings.Topic > 0 && sup.settings.AdvancedSettings.Topic != sup.topic {
+		sup.topic = sup.settings.AdvancedSettings.Topic
 	}
 
 	// 默认超时是 2 * 60s,如果是调试模式则是 5 min
 	sup.tt = common.HTMLTimeOut
-	sup.debugMode = sup.reqParam.DebugMode
-	if sup.reqParam.DebugMode == true {
+	sup.debugMode = sup.settings.AdvancedSettings.DebugMode
+	if sup.debugMode == true {
 		sup.tt = common.OneMovieProcessTimeOut
 	}
+	// 判断是否启用代理
+	if sup.settings.AdvancedSettings.ProxySettings.UseHttpProxy == true {
+		sup.httpProxyAddress = sup.settings.AdvancedSettings.ProxySettings.HttpProxyAddress
+	} else {
+		sup.httpProxyAddress = ""
+	}
 
 	return &sup
 }
@@ -65,10 +72,6 @@ func (s Supplier) GetSupplierName() string {
 	return common.SubSiteSubHd
 }
 
-func (s Supplier) GetReqParam() types.ReqParam {
-	return s.reqParam
-}
-
 func (s Supplier) GetSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error) {
 	return s.getSubListFromFile4Movie(filePath)
 }
@@ -77,7 +80,7 @@ func (s Supplier) GetSubListFromFile4Series(seriesInfo *series.SeriesInfo) ([]su
 
 	var browser *rod.Browser
 	// TODO 是用本地的 Browser 还是远程的,推荐是远程的
-	browser, err := rod_helper.NewBrowser(s.reqParam.HttpProxy, true)
+	browser, err := rod_helper.NewBrowser(s.httpProxyAddress, true)
 	if err != nil {
 		return nil, err
 	}
@@ -196,7 +199,7 @@ func (s Supplier) getSubListFromKeyword4Movie(keyword string) ([]supplier.SubInf
 
 	var browser *rod.Browser
 	// TODO 是用本地的 Browser 还是远程的,推荐是远程的
-	browser, err := rod_helper.NewBrowser(s.reqParam.HttpProxy, true)
+	browser, err := rod_helper.NewBrowser(s.httpProxyAddress, true)
 	if err != nil {
 		return nil, err
 	}
@@ -647,7 +650,7 @@ search:
 	//鬆開滑鼠左鍵, 拖动完毕
 	mouse.MustUp("left")
 
-	if s.reqParam.DebugMode == true {
+	if s.debugMode == true {
 		//截圖保存
 		nowProcessRoot, err := my_util.GetRootDebugFolder()
 		if err == nil {

+ 10 - 13
internal/logic/sub_supplier/xunlei/xunlei.go

@@ -8,10 +8,11 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/series"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/supplier"
+	"github.com/huandu/go-clone"
 	"github.com/sirupsen/logrus"
 	"math"
 	"os"
@@ -19,22 +20,22 @@ import (
 )
 
 type Supplier struct {
-	reqParam types.ReqParam
+	settings settings.Settings
 	log      *logrus.Logger
 	topic    int
 }
 
-func NewSupplier(_reqParam ...types.ReqParam) *Supplier {
+func NewSupplier(_settings settings.Settings) *Supplier {
 
 	sup := Supplier{}
 	sup.log = log_helper.GetLogger()
 	sup.topic = common.DownloadSubsPerSite
-	if len(_reqParam) > 0 {
-		sup.reqParam = _reqParam[0]
-		if sup.reqParam.Topic > 0 && sup.reqParam.Topic != sup.topic {
-			sup.topic = sup.reqParam.Topic
-		}
+
+	sup.settings = clone.Clone(_settings).(settings.Settings)
+	if sup.settings.AdvancedSettings.Topic > 0 && sup.settings.AdvancedSettings.Topic != sup.topic {
+		sup.topic = sup.settings.AdvancedSettings.Topic
 	}
+
 	return &sup
 }
 
@@ -42,10 +43,6 @@ func (s Supplier) GetSupplierName() string {
 	return common.SubSiteXunLei
 }
 
-func (s Supplier) GetReqParam() types.ReqParam {
-	return s.reqParam
-}
-
 func (s Supplier) GetSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error) {
 	return s.getSubListFromFile(filePath)
 }
@@ -73,7 +70,7 @@ func (s Supplier) getSubListFromFile(filePath string) ([]supplier.SubInfo, error
 	if len(cid) == 0 {
 		return nil, common.XunLeiCIdIsEmpty
 	}
-	httpClient := my_util.NewHttpClient(s.reqParam)
+	httpClient := my_util.NewHttpClient(*s.settings.AdvancedSettings.ProxySettings)
 	resp, err := httpClient.R().
 		SetResult(&jsonList).
 		Get(fmt.Sprintf(common.SubXunLeiRootUrl, cid))

+ 14 - 18
internal/logic/sub_supplier/zimuku/zimuku.go

@@ -10,11 +10,12 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	language2 "github.com/allanpk716/ChineseSubFinder/internal/types/language"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/series"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/supplier"
+	"github.com/huandu/go-clone"
 	"github.com/sirupsen/logrus"
 	"path/filepath"
 	"regexp"
@@ -23,21 +24,20 @@ import (
 )
 
 type Supplier struct {
-	reqParam types.ReqParam
+	settings settings.Settings
 	log      *logrus.Logger
 	topic    int
 }
 
-func NewSupplier(_reqParam ...types.ReqParam) *Supplier {
+func NewSupplier(_settings settings.Settings) *Supplier {
 
 	sup := Supplier{}
 	sup.log = log_helper.GetLogger()
 	sup.topic = common.DownloadSubsPerSite
-	if len(_reqParam) > 0 {
-		sup.reqParam = _reqParam[0]
-		if sup.reqParam.Topic > 0 && sup.reqParam.Topic != sup.topic {
-			sup.topic = sup.reqParam.Topic
-		}
+
+	sup.settings = clone.Clone(_settings).(settings.Settings)
+	if sup.settings.AdvancedSettings.Topic > 0 && sup.settings.AdvancedSettings.Topic != sup.topic {
+		sup.topic = sup.settings.AdvancedSettings.Topic
 	}
 	return &sup
 }
@@ -46,10 +46,6 @@ func (s Supplier) GetSupplierName() string {
 	return common.SubSiteZiMuKu
 }
 
-func (s Supplier) GetReqParam() types.ReqParam {
-	return s.reqParam
-}
-
 func (s Supplier) GetSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error) {
 	return s.getSubListFromMovie(filePath)
 }
@@ -352,7 +348,7 @@ func (s Supplier) step0(keyword string) (string, error) {
 			notify_center.Notify.Add("zimuku_step0", err.Error())
 		}
 	}()
-	httpClient := my_util.NewHttpClient(s.reqParam)
+	httpClient := my_util.NewHttpClient(*s.settings.AdvancedSettings.ProxySettings)
 	// 第一级界面,有多少个字幕
 	resp, err := httpClient.R().
 		SetQueryParams(map[string]string{
@@ -382,7 +378,7 @@ func (s Supplier) step1(filmDetailPageUrl string) (SubResult, error) {
 		}
 	}()
 	filmDetailPageUrl = my_util.AddBaseUrl(common.SubZiMuKuRootUrl, filmDetailPageUrl)
-	httpClient := my_util.NewHttpClient(s.reqParam)
+	httpClient := my_util.NewHttpClient(*s.settings.AdvancedSettings.ProxySettings)
 	resp, err := httpClient.R().
 		Get(filmDetailPageUrl)
 	if err != nil {
@@ -483,7 +479,7 @@ func (s Supplier) step2(subInfo *SubInfo) error {
 		}
 	}()
 	detailUrl := my_util.AddBaseUrl(common.SubZiMuKuRootUrl, subInfo.DetailUrl)
-	httpClient := my_util.NewHttpClient(s.reqParam)
+	httpClient := my_util.NewHttpClient(*s.settings.AdvancedSettings.ProxySettings)
 	resp, err := httpClient.R().
 		Get(detailUrl)
 	if err != nil {
@@ -513,7 +509,7 @@ func (s Supplier) step3(subDownloadPageUrl string) (string, []byte, error) {
 		}
 	}()
 	subDownloadPageUrl = my_util.AddBaseUrl(common.SubZiMuKuRootUrl, subDownloadPageUrl)
-	httpClient := my_util.NewHttpClient(s.reqParam)
+	httpClient := my_util.NewHttpClient(*s.settings.AdvancedSettings.ProxySettings)
 	resp, err := httpClient.R().
 		Get(subDownloadPageUrl)
 	if err != nil {
@@ -528,9 +524,9 @@ func (s Supplier) step3(subDownloadPageUrl string) (string, []byte, error) {
 	var filename string
 	var data []byte
 
-	s.reqParam.Referer = subDownloadPageUrl
+	s.settings.AdvancedSettings.ProxySettings.Referer = subDownloadPageUrl
 	for i := 0; i < len(matched); i++ {
-		data, filename, err = my_util.DownFile(my_util.AddBaseUrl(common.SubZiMuKuRootUrl, matched[i][1]), s.reqParam)
+		data, filename, err = my_util.DownFile(my_util.AddBaseUrl(common.SubZiMuKuRootUrl, matched[i][1]), *s.settings.AdvancedSettings.ProxySettings)
 		if err != nil {
 			s.log.Errorln("ZiMuKu step3 DownloadFile", err)
 			continue

+ 8 - 5
internal/logic/sub_timeline_fixer/SubTimelineFixerHelperEx.go

@@ -7,10 +7,10 @@ import (
 	"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/settings"
 	"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"
@@ -22,15 +22,18 @@ type SubTimelineFixerHelperEx struct {
 	ffmpegHelper        *ffmpeg_helper.FFMPEGHelper
 	subParserHub        *sub_parser_hub.SubParserHub
 	timelineFixPipeLine *sub_timeline_fixer.Pipeline
-	fixerConfig         sub_timeline_fiexer.SubTimelineFixerConfig
+	fixerConfig         settings.TimelineFixerSettings
 	needDownloadFFMPeg  bool
 }
 
-func NewSubTimelineFixerHelperEx(fixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixerHelperEx {
+func NewSubTimelineFixerHelperEx(fixerConfig settings.TimelineFixerSettings) *SubTimelineFixerHelperEx {
+
+	fixerConfig.Check()
+
 	return &SubTimelineFixerHelperEx{
 		ffmpegHelper:        ffmpeg_helper.NewFFMPEGHelper(),
 		subParserHub:        sub_parser_hub.NewSubParserHub(ass.NewParser(), srt.NewParser()),
-		timelineFixPipeLine: sub_timeline_fixer.NewPipeline(fixerConfig.V2_MaxOffsetTime),
+		timelineFixPipeLine: sub_timeline_fixer.NewPipeline(fixerConfig.MaxOffsetTime),
 		fixerConfig:         fixerConfig,
 		needDownloadFFMPeg:  false,
 	}
@@ -130,7 +133,7 @@ func (s SubTimelineFixerHelperEx) Process(videoFileFullPath, srcSubFPath string)
 	}
 
 	// 开始调整字幕时间轴
-	if bProcess == false || math.Abs(pipeResultMax.GetOffsetTime()) < s.fixerConfig.V2_MinOffset {
+	if bProcess == false || math.Abs(pipeResultMax.GetOffsetTime()) < s.fixerConfig.MinOffset {
 		log_helper.GetLogger().Infoln("Skip TimeLine Fix -- OffsetTime:", pipeResultMax.GetOffsetTime(), srcSubFPath)
 		return nil
 	}

+ 322 - 321
internal/logic/sub_timeline_fixer/sub_timeline_fixer_helper.go

@@ -1,323 +1,324 @@
 package sub_timeline_fixer
 
-import (
-	"fmt"
-	"github.com/allanpk716/ChineseSubFinder/internal/common"
-	"github.com/allanpk716/ChineseSubFinder/internal/ifaces"
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/emby_helper"
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/ass"
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/srt"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
-	formatterEmby "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/emby"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/normal"
-	"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/emby"
-	"github.com/allanpk716/ChineseSubFinder/internal/types/sub_timeline_fiexer"
-	"github.com/allanpk716/ChineseSubFinder/internal/types/subparser"
-	"os"
-	"path"
-	"path/filepath"
-	"strings"
-	"time"
-)
-
-type SubTimelineFixerHelper struct {
-	embyHelper       *emby_helper.EmbyHelper
-	EmbyConfig       emby.EmbyConfig
-	FixerConfig      sub_timeline_fiexer.SubTimelineFixerConfig
-	subParserHub     *sub_parser_hub.SubParserHub
-	subTimelineFixer *sub_timeline_fixer.SubTimelineFixer
-	formatter        map[string]ifaces.ISubFormatter
-	threads          int
-	timeOut          time.Duration
-}
-
-func NewSubTimelineFixerHelper(embyConfig emby.EmbyConfig, subTimelineFixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixerHelper {
-	sub := SubTimelineFixerHelper{
-		EmbyConfig:       embyConfig,
-		FixerConfig:      subTimelineFixerConfig,
-		embyHelper:       emby_helper.NewEmbyHelper(embyConfig),
-		subParserHub:     sub_parser_hub.NewSubParserHub(ass.NewParser(), srt.NewParser()),
-		subTimelineFixer: sub_timeline_fixer.NewSubTimelineFixer(subTimelineFixerConfig),
-		formatter:        make(map[string]ifaces.ISubFormatter),
-		threads:          6,
-		timeOut:          60 * time.Second,
-	}
-	// TODO 如果字幕格式新增了实现,这里也需要添加对应的实例
-	// 初始化支持的 formatter
-	// normal
-	sub.formatter = make(map[string]ifaces.ISubFormatter)
-	normalM := normal.NewFormatter()
-	sub.formatter[normalM.GetFormatterName()] = normalM
-	// emby
-	embyM := formatterEmby.NewFormatter()
-	sub.formatter[embyM.GetFormatterName()] = embyM
-
-	return &sub
-}
-
-func (s SubTimelineFixerHelper) FixRecentlyItemsSubTimeline(movieRootDir, seriesRootDir string) error {
-
-	// 首先得开启,不然就直接跳过不执行
-	if s.EmbyConfig.FixTimeLine == false {
-		log_helper.GetLogger().Debugf("EmbyConfig.FixTimeLine = false, Skip")
-		return nil
-	}
-
-	movieList, seriesList, err := s.embyHelper.GetRecentlyAddVideoList(movieRootDir, seriesRootDir)
-	if err != nil {
-		return err
-	}
-
-	// 输出调试信息
-	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - movieList Start")
-	for s, value := range movieList {
-		log_helper.GetLogger().Debugln(s, value)
-	}
-	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - movieList End")
-
-	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - seriesList Start")
-	for s, _ := range seriesList {
-		log_helper.GetLogger().Debugln(s)
-	}
-	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - seriesList End")
-
-	log_helper.GetLogger().Debugln("Start movieList fix Timeline")
-	// 先做电影的字幕校正、然后才是连续剧的
-	for _, info := range movieList {
-		// path.Dir 在 Windows 有梗,所以换个方式获取路径
-		videoRootPath := filepath.Dir(info.VideoFileFullPath)
-		err = s.fixOneVideoSub(info.VideoInfo.Id, videoRootPath)
-		if err != nil {
-			return err
-		}
-	}
-	log_helper.GetLogger().Debugln("End movieList fix Timeline")
-
-	log_helper.GetLogger().Debugln("Start seriesList fix Timeline")
-	for _, infos := range seriesList {
-		for _, info := range infos {
-			// path.Dir 在 Windows 有梗,所以换个方式获取路径
-			videoRootPath := filepath.Dir(info.VideoFileFullPath)
-			err = s.fixOneVideoSub(info.VideoInfo.Id, videoRootPath)
-			if err != nil {
-				return err
-			}
-		}
-	}
-	log_helper.GetLogger().Debugln("End seriesList fix Timeline")
-
-	// 强制调用,测试 CGO=1 编译问题
-	log_helper.GetLogger().Debugln("VAD Mode", vad.Mode)
-
-	return nil
-}
-
-func (s SubTimelineFixerHelper) fixOneVideoSub(videoId string, videoRootPath string) error {
-	log_helper.GetLogger().Debugln("fixOneVideoSub VideoROotPath:", videoRootPath)
-	// internalEngSub 默认第一个是 srt 然后第二个是 ass,就不要去遍历了
-	found, internalEngSub, containChineseSubFile, err := s.embyHelper.GetInternalEngSubAndExChineseEnglishSub(videoId)
-	if err != nil {
-		return err
-	}
-
-	if found == false {
-		log_helper.GetLogger().Debugln("GetInternalEngSubAndExChineseEnglishSub - found == false")
-		return nil
-	}
-
-	log_helper.GetLogger().Debugln("internalEngSub:", len(internalEngSub), "containChineseSubFile:", len(containChineseSubFile))
-	// 需要先把原有的外置字幕带有 -fix 的删除,然后再做修正
-	// 不然如果调整了条件,之前修复的本次其实就不修正了,那么就会“残留”下来,误以为是本次配置的信息导致的
-	for _, exSubInfo := range containChineseSubFile {
-		// 没有编辑的就跳过
-		if strings.Contains(exSubInfo.FileName, sub_timeline_fixer.FixMask) == false {
-			continue
-		}
-
-		subFileNeedRemove := filepath.Join(videoRootPath, exSubInfo.FileName)
-
-		if videoRootPath == "" {
-			log_helper.GetLogger().Debugln("videoRootPath == \"\", Skip Remove:", subFileNeedRemove)
-			continue
-		}
-
-		log_helper.GetLogger().Debugln("Remove fixed sub:", subFileNeedRemove)
-		err = os.Remove(subFileNeedRemove)
-		if err != nil {
-			return err
-		}
-	}
-
-	// 从外置双语(中英)字幕中找对对应的内置 srt 字幕进行匹配比较
-	for _, exSubInfo := range containChineseSubFile {
-		inSelectSubIndex := 1
-		if exSubInfo.Ext == common.SubExtSRT {
-			inSelectSubIndex = 0
-		}
-		// 修正过的字幕有标记,将不会再次修复
-		if strings.Contains(exSubInfo.FileName, sub_timeline_fixer.FixMask) == true {
-			continue
-		}
-
-		log_helper.GetLogger().Debugln("fixSubTimeline start")
-		bFound, subFixInfos, subNewName, err := s.fixSubTimeline(internalEngSub[inSelectSubIndex], exSubInfo)
-		if err != nil {
-			return err
-		}
-		if bFound == false {
-			log_helper.GetLogger().Debugln("fixSubTimeline bFound == false", exSubInfo.FileName)
-			continue
-		}
-		// 调试的时候用
-		if videoRootPath == "" {
-			log_helper.GetLogger().Debugln("videoRootPath == \"\", Skip fix sub:", exSubInfo.FileName)
-			continue
-		}
-		for _, info := range subFixInfos {
-			// 写入 fix 后的字幕文件覆盖之前的字幕文件
-			desFixedSubFullName := filepath.Join(videoRootPath, subNewName)
-			err = s.saveSubFile(desFixedSubFullName, info.FixContent)
-			if err != nil {
-				return err
-			}
-			log_helper.GetLogger().Infoln("Sub Timeline fixed:", desFixedSubFullName)
-		}
-	}
-
-	return nil
-}
-
-// fixSubTimeline 修复时间轴,containChineseSubFile 这里可能是,只要是带有中文的都算,简体、繁体、简英、繁英,需要后续额外的判断
-func (s SubTimelineFixerHelper) fixSubTimeline(enSubFile emby.SubInfo, containChineseSubFile emby.SubInfo) (bool, []sub_timeline_fixer.SubFixInfo, string, error) {
-	fixedSubName := ""
-	log_helper.GetLogger().Debugln("fixSubTimeline - DetermineFileTypeFromBytes", enSubFile.FileName)
-	bFind, infoBase, err := s.subParserHub.DetermineFileTypeFromBytes(enSubFile.Content, enSubFile.Ext)
-	if err != nil {
-		return false, nil, fixedSubName, err
-	}
-	if bFind == false {
-		return false, nil, fixedSubName, nil
-	}
-	infoBase.Name = enSubFile.FileName
-	/*
-		这里发现一个梗,内置的英文字幕导出的时候,有可能需要合并多个 Dialogue,见
-		internal/pkg/sub_helper/sub_helper.go 中 MergeMultiDialogue4EngSubtitle 的实现
-	*/
-	sub_helper.MergeMultiDialogue4EngSubtitle(infoBase)
-
-	log_helper.GetLogger().Debugln("fixSubTimeline - DetermineFileTypeFromBytes", containChineseSubFile.FileName)
-	bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromBytes(containChineseSubFile.Content, containChineseSubFile.Ext)
-	if err != nil {
-		return false, nil, fixedSubName, err
-	}
-	if bFind == false {
-		return false, nil, fixedSubName, nil
-	}
-	infoSrc.Name = containChineseSubFile.FileName
-	/*
-		这里发现一个梗,内置的英文字幕导出的时候,有可能需要合并多个 Dialogue,见
-		internal/pkg/sub_helper/sub_helper.go 中 MergeMultiDialogue4EngSubtitle 的实现
-	*/
-	sub_helper.MergeMultiDialogue4EngSubtitle(infoSrc)
-
-	infoBaseNameWithOutExt := strings.Replace(infoBase.Name, path.Ext(infoBase.Name), "", -1)
-	//infoSrcNameWithOutExt := strings.Replace(infoSrc.Name, path.Ext(infoSrc.Name), "", -1)
-
-	// 把原始的文件缓存下来,新建缓存的文件夹
-	subFixCacheRootPath, err := my_util.GetRootSubFixCacheFolder()
-	if err != nil {
-		return false, nil, fixedSubName, err
-	}
-	cacheTmpPath := filepath.Join(subFixCacheRootPath, infoBaseNameWithOutExt)
-	if my_util.IsDir(cacheTmpPath) == false {
-		err = os.MkdirAll(cacheTmpPath, os.ModePerm)
-		if err != nil {
-			return false, nil, fixedSubName, err
-		}
-	}
-	// 写入内置字幕、外置字幕原始文件
-	err = s.saveSubFile(filepath.Join(cacheTmpPath, infoBaseNameWithOutExt+".chinese(inside)"+infoBase.Ext), infoBase.Content)
-	if err != nil {
-		return false, nil, fixedSubName, err
-	}
-	err = s.saveSubFile(filepath.Join(cacheTmpPath, infoSrc.Name), infoSrc.Content)
-	if err != nil {
-		return false, nil, fixedSubName, err
-	}
-	bok, offsetTime, sd, err := s.subTimelineFixer.GetOffsetTimeV1(infoBase, infoSrc, filepath.Join(cacheTmpPath, infoSrc.Name+"-bar.html"), filepath.Join(cacheTmpPath, infoSrc.Name+".log"))
-	if offsetTime != 0 {
-		log_helper.GetLogger().Infoln(infoSrc.Name, "offset time is", fmt.Sprintf("%f", offsetTime), "s")
-	}
-	// 超过 SD 阈值了
-	if sd > s.FixerConfig.V1_MaxStartTimeDiffSD {
-		log_helper.GetLogger().Infoln(infoSrc.Name, "Start Time Diff SD, skip", fmt.Sprintf("%f", sd))
-		return false, nil, fixedSubName, nil
-	} else {
-		log_helper.GetLogger().Infoln(infoSrc.Name, "Start Time Diff SD", fmt.Sprintf("%f", sd))
-	}
-
-	if err != nil || bok == false {
-		return false, nil, fixedSubName, err
-	}
-
-	// 偏移很小就无视了
-	if offsetTime < s.FixerConfig.V1_MinOffset && offsetTime > -s.FixerConfig.V1_MinOffset {
-		log_helper.GetLogger().Infoln(infoSrc.Name, fmt.Sprintf("Min Offset Config is %f, skip ", s.FixerConfig.V1_MinOffset), fmt.Sprintf("now is %f", offsetTime))
-		return false, nil, fixedSubName, nil
-	}
-	// 写入校准时间轴后的字幕
-	var subFixInfos = make([]sub_timeline_fixer.SubFixInfo, 0)
-	for _, formatter := range s.formatter {
-		// 符合已知的字幕命名格式,不符合就跳过,都跳过也行,就不做任何操作而已
-		bMatch, fileNameWithOutExt, subExt, subLang, extraSubName := formatter.IsMatchThisFormat(infoSrc.Name)
-		if bMatch == false {
-			log_helper.GetLogger().Debugln(fmt.Sprintf("%s IsMatchThisFormat == false, Skip, %s", formatter.GetFormatterName(), infoSrc.Name))
-			continue
-		}
-		// 是否包含 default 关键词,暂时无需判断 forced
-		hasDefault := false
-		if strings.Contains(strings.ToLower(infoSrc.Name), subparser.Sub_Ext_Mark_Default) == true {
-			hasDefault = true
-		}
-		// 生成对应字幕命名格式的,字幕命名。这里注意,normal 的时候, extraSubName+"-fix" 是无效的,不会被设置,也就是直接覆盖之前的字幕了。
-		subNewName, subNewNameDefault, _ := formatter.GenerateMixSubNameBase(fileNameWithOutExt, subExt, subLang, extraSubName+sub_timeline_fixer.FixMask)
-
-		desFixSubFileFullPath := ""
-		if hasDefault == true {
-			fixedSubName = subNewNameDefault
-			desFixSubFileFullPath = filepath.Join(cacheTmpPath, subNewNameDefault)
-
-		} else {
-			fixedSubName = subNewName
-			desFixSubFileFullPath = filepath.Join(cacheTmpPath, subNewName)
-		}
-		fixContent, err := s.subTimelineFixer.FixSubTimelineOneOffsetTime(infoSrc, offsetTime, desFixSubFileFullPath)
-		if err != nil {
-			return false, nil, fixedSubName, err
-		}
-		subFixInfos = append(subFixInfos, *sub_timeline_fixer.NewSubFixInfo(infoSrc.Name, fixContent))
-	}
-
-	return true, subFixInfos, fixedSubName, nil
-}
-
-func (s SubTimelineFixerHelper) saveSubFile(desSaveSubFileFullPath string, content string) error {
-	dstFile, err := os.Create(desSaveSubFileFullPath)
-	if err != nil {
-		return err
-	}
-	defer func() {
-		_ = dstFile.Close()
-	}()
-	_, err = dstFile.WriteString(content)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
+//
+//import (
+//	"fmt"
+//	"github.com/allanpk716/ChineseSubFinder/internal/common"
+//	"github.com/allanpk716/ChineseSubFinder/internal/ifaces"
+//	"github.com/allanpk716/ChineseSubFinder/internal/logic/emby_helper"
+//	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/ass"
+//	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_parser/srt"
+//	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
+//	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
+//	formatterEmby "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/emby"
+//	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/normal"
+//	"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/emby"
+//	"github.com/allanpk716/ChineseSubFinder/internal/types/sub_timeline_fiexer"
+//	"github.com/allanpk716/ChineseSubFinder/internal/types/subparser"
+//	"os"
+//	"path"
+//	"path/filepath"
+//	"strings"
+//	"time"
+//)
+//
+//type SubTimelineFixerHelper struct {
+//	embyHelper       *emby_helper.EmbyHelper
+//	EmbyConfig       emby.EmbyConfig
+//	FixerConfig      sub_timeline_fiexer.SubTimelineFixerConfig
+//	subParserHub     *sub_parser_hub.SubParserHub
+//	subTimelineFixer *sub_timeline_fixer.SubTimelineFixer
+//	formatter        map[string]ifaces.ISubFormatter
+//	threads          int
+//	timeOut          time.Duration
+//}
+//
+//func NewSubTimelineFixerHelper(embyConfig emby.EmbyConfig, subTimelineFixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixerHelper {
+//	sub := SubTimelineFixerHelper{
+//		EmbyConfig:       embyConfig,
+//		FixerConfig:      subTimelineFixerConfig,
+//		embyHelper:       emby_helper.NewEmbyHelper(embyConfig),
+//		subParserHub:     sub_parser_hub.NewSubParserHub(ass.NewParser(), srt.NewParser()),
+//		subTimelineFixer: sub_timeline_fixer.NewSubTimelineFixer(subTimelineFixerConfig),
+//		formatter:        make(map[string]ifaces.ISubFormatter),
+//		threads:          6,
+//		timeOut:          60 * time.Second,
+//	}
+//	// TODO 如果字幕格式新增了实现,这里也需要添加对应的实例
+//	// 初始化支持的 formatter
+//	// normal
+//	sub.formatter = make(map[string]ifaces.ISubFormatter)
+//	normalM := normal.NewFormatter()
+//	sub.formatter[normalM.GetFormatterName()] = normalM
+//	// emby
+//	embyM := formatterEmby.NewFormatter()
+//	sub.formatter[embyM.GetFormatterName()] = embyM
+//
+//	return &sub
+//}
+//
+//func (s SubTimelineFixerHelper) FixRecentlyItemsSubTimeline(movieRootDir, seriesRootDir string) error {
+//
+//	// 首先得开启,不然就直接跳过不执行
+//	if s.EmbyConfig.FixTimeLine == false {
+//		log_helper.GetLogger().Debugf("EmbyConfig.FixTimeLine = false, Skip")
+//		return nil
+//	}
+//
+//	movieList, seriesList, err := s.embyHelper.GetRecentlyAddVideoList(movieRootDir, seriesRootDir)
+//	if err != nil {
+//		return err
+//	}
+//
+//	// 输出调试信息
+//	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - movieList Start")
+//	for s, value := range movieList {
+//		log_helper.GetLogger().Debugln(s, value)
+//	}
+//	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - movieList End")
+//
+//	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - seriesList Start")
+//	for s, _ := range seriesList {
+//		log_helper.GetLogger().Debugln(s)
+//	}
+//	log_helper.GetLogger().Debugln("FixRecentlyItemsSubTimeline - DebugInfo - seriesList End")
+//
+//	log_helper.GetLogger().Debugln("Start movieList fix Timeline")
+//	// 先做电影的字幕校正、然后才是连续剧的
+//	for _, info := range movieList {
+//		// path.Dir 在 Windows 有梗,所以换个方式获取路径
+//		videoRootPath := filepath.Dir(info.VideoFileFullPath)
+//		err = s.fixOneVideoSub(info.VideoInfo.Id, videoRootPath)
+//		if err != nil {
+//			return err
+//		}
+//	}
+//	log_helper.GetLogger().Debugln("End movieList fix Timeline")
+//
+//	log_helper.GetLogger().Debugln("Start seriesList fix Timeline")
+//	for _, infos := range seriesList {
+//		for _, info := range infos {
+//			// path.Dir 在 Windows 有梗,所以换个方式获取路径
+//			videoRootPath := filepath.Dir(info.VideoFileFullPath)
+//			err = s.fixOneVideoSub(info.VideoInfo.Id, videoRootPath)
+//			if err != nil {
+//				return err
+//			}
+//		}
+//	}
+//	log_helper.GetLogger().Debugln("End seriesList fix Timeline")
+//
+//	// 强制调用,测试 CGO=1 编译问题
+//	log_helper.GetLogger().Debugln("VAD Mode", vad.Mode)
+//
+//	return nil
+//}
+//
+//func (s SubTimelineFixerHelper) fixOneVideoSub(videoId string, videoRootPath string) error {
+//	log_helper.GetLogger().Debugln("fixOneVideoSub VideoROotPath:", videoRootPath)
+//	// internalEngSub 默认第一个是 srt 然后第二个是 ass,就不要去遍历了
+//	found, internalEngSub, containChineseSubFile, err := s.embyHelper.GetInternalEngSubAndExChineseEnglishSub(videoId)
+//	if err != nil {
+//		return err
+//	}
+//
+//	if found == false {
+//		log_helper.GetLogger().Debugln("GetInternalEngSubAndExChineseEnglishSub - found == false")
+//		return nil
+//	}
+//
+//	log_helper.GetLogger().Debugln("internalEngSub:", len(internalEngSub), "containChineseSubFile:", len(containChineseSubFile))
+//	// 需要先把原有的外置字幕带有 -fix 的删除,然后再做修正
+//	// 不然如果调整了条件,之前修复的本次其实就不修正了,那么就会“残留”下来,误以为是本次配置的信息导致的
+//	for _, exSubInfo := range containChineseSubFile {
+//		// 没有编辑的就跳过
+//		if strings.Contains(exSubInfo.FileName, sub_timeline_fixer.FixMask) == false {
+//			continue
+//		}
+//
+//		subFileNeedRemove := filepath.Join(videoRootPath, exSubInfo.FileName)
+//
+//		if videoRootPath == "" {
+//			log_helper.GetLogger().Debugln("videoRootPath == \"\", Skip Remove:", subFileNeedRemove)
+//			continue
+//		}
+//
+//		log_helper.GetLogger().Debugln("Remove fixed sub:", subFileNeedRemove)
+//		err = os.Remove(subFileNeedRemove)
+//		if err != nil {
+//			return err
+//		}
+//	}
+//
+//	// 从外置双语(中英)字幕中找对对应的内置 srt 字幕进行匹配比较
+//	for _, exSubInfo := range containChineseSubFile {
+//		inSelectSubIndex := 1
+//		if exSubInfo.Ext == common.SubExtSRT {
+//			inSelectSubIndex = 0
+//		}
+//		// 修正过的字幕有标记,将不会再次修复
+//		if strings.Contains(exSubInfo.FileName, sub_timeline_fixer.FixMask) == true {
+//			continue
+//		}
+//
+//		log_helper.GetLogger().Debugln("fixSubTimeline start")
+//		bFound, subFixInfos, subNewName, err := s.fixSubTimeline(internalEngSub[inSelectSubIndex], exSubInfo)
+//		if err != nil {
+//			return err
+//		}
+//		if bFound == false {
+//			log_helper.GetLogger().Debugln("fixSubTimeline bFound == false", exSubInfo.FileName)
+//			continue
+//		}
+//		// 调试的时候用
+//		if videoRootPath == "" {
+//			log_helper.GetLogger().Debugln("videoRootPath == \"\", Skip fix sub:", exSubInfo.FileName)
+//			continue
+//		}
+//		for _, info := range subFixInfos {
+//			// 写入 fix 后的字幕文件覆盖之前的字幕文件
+//			desFixedSubFullName := filepath.Join(videoRootPath, subNewName)
+//			err = s.saveSubFile(desFixedSubFullName, info.FixContent)
+//			if err != nil {
+//				return err
+//			}
+//			log_helper.GetLogger().Infoln("Sub Timeline fixed:", desFixedSubFullName)
+//		}
+//	}
+//
+//	return nil
+//}
+//
+//// fixSubTimeline 修复时间轴,containChineseSubFile 这里可能是,只要是带有中文的都算,简体、繁体、简英、繁英,需要后续额外的判断
+//func (s SubTimelineFixerHelper) fixSubTimeline(enSubFile emby.SubInfo, containChineseSubFile emby.SubInfo) (bool, []sub_timeline_fixer.SubFixInfo, string, error) {
+//	fixedSubName := ""
+//	log_helper.GetLogger().Debugln("fixSubTimeline - DetermineFileTypeFromBytes", enSubFile.FileName)
+//	bFind, infoBase, err := s.subParserHub.DetermineFileTypeFromBytes(enSubFile.Content, enSubFile.Ext)
+//	if err != nil {
+//		return false, nil, fixedSubName, err
+//	}
+//	if bFind == false {
+//		return false, nil, fixedSubName, nil
+//	}
+//	infoBase.Name = enSubFile.FileName
+//	/*
+//		这里发现一个梗,内置的英文字幕导出的时候,有可能需要合并多个 Dialogue,见
+//		internal/pkg/sub_helper/sub_helper.go 中 MergeMultiDialogue4EngSubtitle 的实现
+//	*/
+//	sub_helper.MergeMultiDialogue4EngSubtitle(infoBase)
+//
+//	log_helper.GetLogger().Debugln("fixSubTimeline - DetermineFileTypeFromBytes", containChineseSubFile.FileName)
+//	bFind, infoSrc, err := s.subParserHub.DetermineFileTypeFromBytes(containChineseSubFile.Content, containChineseSubFile.Ext)
+//	if err != nil {
+//		return false, nil, fixedSubName, err
+//	}
+//	if bFind == false {
+//		return false, nil, fixedSubName, nil
+//	}
+//	infoSrc.Name = containChineseSubFile.FileName
+//	/*
+//		这里发现一个梗,内置的英文字幕导出的时候,有可能需要合并多个 Dialogue,见
+//		internal/pkg/sub_helper/sub_helper.go 中 MergeMultiDialogue4EngSubtitle 的实现
+//	*/
+//	sub_helper.MergeMultiDialogue4EngSubtitle(infoSrc)
+//
+//	infoBaseNameWithOutExt := strings.Replace(infoBase.Name, path.Ext(infoBase.Name), "", -1)
+//	//infoSrcNameWithOutExt := strings.Replace(infoSrc.Name, path.Ext(infoSrc.Name), "", -1)
+//
+//	// 把原始的文件缓存下来,新建缓存的文件夹
+//	subFixCacheRootPath, err := my_util.GetRootSubFixCacheFolder()
+//	if err != nil {
+//		return false, nil, fixedSubName, err
+//	}
+//	cacheTmpPath := filepath.Join(subFixCacheRootPath, infoBaseNameWithOutExt)
+//	if my_util.IsDir(cacheTmpPath) == false {
+//		err = os.MkdirAll(cacheTmpPath, os.ModePerm)
+//		if err != nil {
+//			return false, nil, fixedSubName, err
+//		}
+//	}
+//	// 写入内置字幕、外置字幕原始文件
+//	err = s.saveSubFile(filepath.Join(cacheTmpPath, infoBaseNameWithOutExt+".chinese(inside)"+infoBase.Ext), infoBase.Content)
+//	if err != nil {
+//		return false, nil, fixedSubName, err
+//	}
+//	err = s.saveSubFile(filepath.Join(cacheTmpPath, infoSrc.Name), infoSrc.Content)
+//	if err != nil {
+//		return false, nil, fixedSubName, err
+//	}
+//	bok, offsetTime, sd, err := s.subTimelineFixer.GetOffsetTimeV1(infoBase, infoSrc, filepath.Join(cacheTmpPath, infoSrc.Name+"-bar.html"), filepath.Join(cacheTmpPath, infoSrc.Name+".log"))
+//	if offsetTime != 0 {
+//		log_helper.GetLogger().Infoln(infoSrc.Name, "offset time is", fmt.Sprintf("%f", offsetTime), "s")
+//	}
+//	// 超过 SD 阈值了
+//	if sd > s.FixerConfig.V1_MaxStartTimeDiffSD {
+//		log_helper.GetLogger().Infoln(infoSrc.Name, "Start Time Diff SD, skip", fmt.Sprintf("%f", sd))
+//		return false, nil, fixedSubName, nil
+//	} else {
+//		log_helper.GetLogger().Infoln(infoSrc.Name, "Start Time Diff SD", fmt.Sprintf("%f", sd))
+//	}
+//
+//	if err != nil || bok == false {
+//		return false, nil, fixedSubName, err
+//	}
+//
+//	// 偏移很小就无视了
+//	if offsetTime < s.FixerConfig.V1_MinOffset && offsetTime > -s.FixerConfig.V1_MinOffset {
+//		log_helper.GetLogger().Infoln(infoSrc.Name, fmt.Sprintf("Min Offset Config is %f, skip ", s.FixerConfig.V1_MinOffset), fmt.Sprintf("now is %f", offsetTime))
+//		return false, nil, fixedSubName, nil
+//	}
+//	// 写入校准时间轴后的字幕
+//	var subFixInfos = make([]sub_timeline_fixer.SubFixInfo, 0)
+//	for _, formatter := range s.formatter {
+//		// 符合已知的字幕命名格式,不符合就跳过,都跳过也行,就不做任何操作而已
+//		bMatch, fileNameWithOutExt, subExt, subLang, extraSubName := formatter.IsMatchThisFormat(infoSrc.Name)
+//		if bMatch == false {
+//			log_helper.GetLogger().Debugln(fmt.Sprintf("%s IsMatchThisFormat == false, Skip, %s", formatter.GetFormatterName(), infoSrc.Name))
+//			continue
+//		}
+//		// 是否包含 default 关键词,暂时无需判断 forced
+//		hasDefault := false
+//		if strings.Contains(strings.ToLower(infoSrc.Name), subparser.Sub_Ext_Mark_Default) == true {
+//			hasDefault = true
+//		}
+//		// 生成对应字幕命名格式的,字幕命名。这里注意,normal 的时候, extraSubName+"-fix" 是无效的,不会被设置,也就是直接覆盖之前的字幕了。
+//		subNewName, subNewNameDefault, _ := formatter.GenerateMixSubNameBase(fileNameWithOutExt, subExt, subLang, extraSubName+sub_timeline_fixer.FixMask)
+//
+//		desFixSubFileFullPath := ""
+//		if hasDefault == true {
+//			fixedSubName = subNewNameDefault
+//			desFixSubFileFullPath = filepath.Join(cacheTmpPath, subNewNameDefault)
+//
+//		} else {
+//			fixedSubName = subNewName
+//			desFixSubFileFullPath = filepath.Join(cacheTmpPath, subNewName)
+//		}
+//		fixContent, err := s.subTimelineFixer.FixSubTimelineOneOffsetTime(infoSrc, offsetTime, desFixSubFileFullPath)
+//		if err != nil {
+//			return false, nil, fixedSubName, err
+//		}
+//		subFixInfos = append(subFixInfos, *sub_timeline_fixer.NewSubFixInfo(infoSrc.Name, fixContent))
+//	}
+//
+//	return true, subFixInfos, fixedSubName, nil
+//}
+//
+//func (s SubTimelineFixerHelper) saveSubFile(desSaveSubFileFullPath string, content string) error {
+//	dstFile, err := os.Create(desSaveSubFileFullPath)
+//	if err != nil {
+//		return err
+//	}
+//	defer func() {
+//		_ = dstFile.Close()
+//	}()
+//	_, err = dstFile.WriteString(content)
+//	if err != nil {
+//		return err
+//	}
+//
+//	return nil
+//}

+ 42 - 62
internal/pkg/downloader/downloader.go

@@ -18,11 +18,11 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_timeline_fixer"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
-	subcommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
+	subCommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
-	sub_timeline_fixer_pkg "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
+	subTimelineFixerPKG "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/task_control"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/emby"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/series"
 	"github.com/sirupsen/logrus"
@@ -33,15 +33,14 @@ import (
 
 // Downloader 实例化一次用一次,不要反复的使用,很多临时标志位需要清理。
 type Downloader struct {
-	reqParam                 types.ReqParam
+	settings                 settings.Settings
 	log                      *logrus.Logger
-	topic                    int                       // 最多能够下载 Top 几的字幕,每一个网站
 	mk                       *markSystem.MarkingSystem // MarkingSystem
 	embyHelper               *embyHelper.EmbyHelper
 	movieFileFullPathList    []string                      //  多个需要搜索字幕的电影文件全路径
 	seriesSubNeedDlMap       map[string][]emby.EmbyMixInfo //  多个需要搜索字幕的连续剧目录
 	subFormatter             ifaces.ISubFormatter          //	字幕格式化命名的实现
-	subNameFormatter         subcommon.FormatterName       // 从 inSubFormatter 推断出来
+	subNameFormatter         subCommon.FormatterName       // 从 inSubFormatter 推断出来
 	needForcedScanAndDownSub bool                          // 将会强制扫描所有的视频,下载字幕,替换已经存在的字幕,不进行时间段和已存在则跳过的判断。且不会进过 Emby API 的逻辑,智能进行强制去以本程序的方式去扫描。
 	NeedRestoreFixTimeLineBK bool                          // 从 csf-bk 文件还原时间轴修复前的字幕文件
 
@@ -52,41 +51,22 @@ type Downloader struct {
 	canceledLock sync.Mutex
 }
 
-func NewDownloader(inSubFormatter ifaces.ISubFormatter, _reqParam ...types.ReqParam) (*Downloader, error) {
+func NewDownloader(inSubFormatter ifaces.ISubFormatter, _settings settings.Settings) (*Downloader, error) {
 
 	var downloader Downloader
 	var err error
 	downloader.subFormatter = inSubFormatter
 	downloader.log = log_helper.GetLogger()
-	downloader.topic = common.DownloadSubsPerSite
-	if len(_reqParam) > 0 {
-		downloader.reqParam = _reqParam[0]
-		if downloader.reqParam.Topic > 0 && downloader.reqParam.Topic != downloader.topic {
-			downloader.topic = downloader.reqParam.Topic
-		}
-		// 如果 Debug 模式开启了,强制设置线程数为1,方便定位问题
-		if downloader.reqParam.DebugMode == true {
-			downloader.reqParam.Threads = 1
-		} else {
-			// 并发线程的范围控制
-			if downloader.reqParam.Threads <= 0 {
-				downloader.reqParam.Threads = 2
-			} else if downloader.reqParam.Threads >= 10 {
-				downloader.reqParam.Threads = 10
-			}
-		}
-		// 初始化 Emby API 接口
-		if downloader.reqParam.EmbyConfig.Url != "" && downloader.reqParam.EmbyConfig.ApiKey != "" {
-			downloader.embyHelper = embyHelper.NewEmbyHelper(downloader.reqParam.EmbyConfig)
-		}
-	} else {
-		downloader.reqParam = *types.NewReqParam()
+	// 参入设置信息
+	downloader.settings = _settings
+	// 检测是否某些参数超出范围
+	downloader.settings.Check()
+	// 初始化 Emby API 接口
+	if downloader.settings.EmbySettings.AddressUrl != "" && downloader.settings.EmbySettings.APIKey != "" {
+		downloader.embyHelper = embyHelper.NewEmbyHelper(*downloader.settings.EmbySettings)
 	}
-	// 强制下载线程为 1,太猛,不然都是错误
-	downloader.reqParam.Threads = 1
-
-	// 这里就不单独弄一个 reqParam.SubNameFormatter 字段来传递值了,因为 inSubFormatter 就已经知道是什么 formatter 了
-	downloader.subNameFormatter = subcommon.FormatterName(downloader.subFormatter.GetFormatterFormatterName())
+	// 这里就不单独弄一个 settings.SubNameFormatter 字段来传递值了,因为 inSubFormatter 就已经知道是什么 formatter 了
+	downloader.subNameFormatter = subCommon.FormatterName(downloader.subFormatter.GetFormatterFormatterName())
 
 	var sitesSequence = make([]string, 0)
 	// TODO 这里写固定了抉择字幕的顺序
@@ -94,19 +74,19 @@ func NewDownloader(inSubFormatter ifaces.ISubFormatter, _reqParam ...types.ReqPa
 	sitesSequence = append(sitesSequence, common.SubSiteSubHd)
 	sitesSequence = append(sitesSequence, common.SubSiteShooter)
 	sitesSequence = append(sitesSequence, common.SubSiteXunLei)
-	downloader.mk = markSystem.NewMarkingSystem(sitesSequence, downloader.reqParam.SubTypePriority)
+	downloader.mk = markSystem.NewMarkingSystem(sitesSequence, downloader.settings.AdvancedSettings.SubTypePriority)
 
 	downloader.movieFileFullPathList = make([]string, 0)
 	downloader.seriesSubNeedDlMap = make(map[string][]emby.EmbyMixInfo)
 
 	// 初始化,字幕校正的实例
-	downloader.subTimelineFixerHelperEx = sub_timeline_fixer.NewSubTimelineFixerHelperEx(downloader.reqParam.SubTimelineFixerConfig)
+	downloader.subTimelineFixerHelperEx = sub_timeline_fixer.NewSubTimelineFixerHelperEx(*downloader.settings.TimelineFixerSettings)
 
-	if downloader.reqParam.FixTimeLine == true {
+	if downloader.settings.AdvancedSettings.FixTimeLine == true {
 		downloader.subTimelineFixerHelperEx.Check()
 	}
 	// 初始化任务控制
-	downloader.taskControl, err = task_control.NewTaskControl(downloader.reqParam.Threads, log_helper.GetLogger())
+	downloader.taskControl, err = task_control.NewTaskControl(downloader.settings.CommonSettings.Threads, log_helper.GetLogger())
 	if err != nil {
 		return nil, err
 	}
@@ -118,25 +98,25 @@ func NewDownloader(inSubFormatter ifaces.ISubFormatter, _reqParam ...types.ReqPa
 func (d *Downloader) ReadSpeFile() error {
 	// 理论上是一次性的,用了这个文件就应该没了
 	// 强制的字幕扫描
-	needProcess_forced_scan_and_down_sub, err := forced_scan_and_down_sub.CheckSpeFile()
+	needProcessForcedScanAndDownSub, err := forced_scan_and_down_sub.CheckSpeFile()
 	if err != nil {
 		return err
 	}
-	d.needForcedScanAndDownSub = needProcess_forced_scan_and_down_sub
+	d.needForcedScanAndDownSub = needProcessForcedScanAndDownSub
 	// 从 csf-bk 文件还原时间轴修复前的字幕文件
-	needProcess_restore_fix_timeline_bk, err := restore_fix_timeline_bk.CheckSpeFile()
+	needProcessRestoreFixTimelineBK, err := restore_fix_timeline_bk.CheckSpeFile()
 	if err != nil {
 		return err
 	}
-	d.NeedRestoreFixTimeLineBK = needProcess_restore_fix_timeline_bk
+	d.NeedRestoreFixTimeLineBK = needProcessRestoreFixTimelineBK
 
-	d.log.Infoln("NeedRestoreFixTimeLineBK ==", needProcess_restore_fix_timeline_bk)
+	d.log.Infoln("NeedRestoreFixTimeLineBK ==", needProcessRestoreFixTimelineBK)
 
 	return nil
 }
 
 // GetUpdateVideoListFromEmby 这里首先会进行近期影片的获取,然后对这些影片进行刷新,然后在获取字幕列表,最终得到需要字幕获取的 video 列表
-func (d *Downloader) GetUpdateVideoListFromEmby(movieRootDir, seriesRootDir string) error {
+func (d *Downloader) GetUpdateVideoListFromEmby() error {
 	if d.embyHelper == nil {
 		return nil
 	}
@@ -156,7 +136,7 @@ func (d *Downloader) GetUpdateVideoListFromEmby(movieRootDir, seriesRootDir stri
 	}
 	var err error
 	var movieList []emby.EmbyMixInfo
-	movieList, d.seriesSubNeedDlMap, err = d.embyHelper.GetRecentlyAddVideoList(movieRootDir, seriesRootDir)
+	movieList, d.seriesSubNeedDlMap, err = d.embyHelper.GetRecentlyAddVideoList()
 	if err != nil {
 		return err
 	}
@@ -166,7 +146,7 @@ func (d *Downloader) GetUpdateVideoListFromEmby(movieRootDir, seriesRootDir stri
 	}
 	// 输出调试信息
 	d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap Start")
-	for s, _ := range d.seriesSubNeedDlMap {
+	for s := range d.seriesSubNeedDlMap {
 		d.log.Debugln(s)
 	}
 	d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap End")
@@ -215,7 +195,7 @@ func (d *Downloader) RefreshEmbySubList() error {
 }
 
 // DownloadSub4Movie 这里对接 Emby 的时候比较方便,只要更新 d.movieFileFullPathList 就行了,不像连续剧那么麻烦
-func (d *Downloader) DownloadSub4Movie(dir string) error {
+func (d *Downloader) DownloadSub4Movie() error {
 	defer func() {
 		// 所有的电影字幕下载完成,抉择完成,需要清理缓存目录
 		err := my_util.ClearRootTmpFolder()
@@ -303,7 +283,7 @@ func (d *Downloader) DownloadSub4Movie(dir string) error {
 	return nil
 }
 
-func (d *Downloader) DownloadSub4Series(dir string) error {
+func (d *Downloader) DownloadSub4Series() error {
 	var err error
 	defer func() {
 		// 所有的连续剧字幕下载完成,抉择完成,需要清理缓存目录
@@ -389,7 +369,7 @@ func (d *Downloader) DownloadSub4Series(dir string) error {
 	return nil
 }
 
-func (d *Downloader) RestoreFixTimelineBK(moviesDir, seriesDir string) error {
+func (d *Downloader) RestoreFixTimelineBK() error {
 
 	defer d.log.Infoln("End Restore Fix Timeline BK")
 	d.log.Infoln("Start Restore Fix Timeline BK...")
@@ -404,7 +384,7 @@ func (d *Downloader) RestoreFixTimelineBK(moviesDir, seriesDir string) error {
 		return nil
 	}
 
-	_, err := sub_timeline_fixer_pkg.Restore(moviesDir, seriesDir)
+	_, err := subTimelineFixerPKG.Restore(moviesDirs, seriesDirs)
 	if err != nil {
 		return err
 	}
@@ -426,14 +406,14 @@ func (d *Downloader) movieDlFunc(ctx context.Context, inData interface{}) error
 	// -----------------------------------------------------
 	// 构建每个字幕站点下载者的实例
 	var subSupplierHub = subSupplier.NewSubSupplierHub(
-		//subhd.NewSupplier(d.reqParam),
-		zimuku.NewSupplier(d.reqParam),
-		xunlei.NewSupplier(d.reqParam),
-		shooter.NewSupplier(d.reqParam),
+		//subhd.NewSupplier(d.settings),
+		zimuku.NewSupplier(d.settings),
+		xunlei.NewSupplier(d.settings),
+		shooter.NewSupplier(d.settings),
 	)
 	if common.SubhdCode != "" {
 		// 如果找到 code 了,那么就可以继续用这个实例
-		subSupplierHub.AddSubSupplier(subhd.NewSupplier(d.reqParam))
+		subSupplierHub.AddSubSupplier(subhd.NewSupplier(d.settings))
 	}
 
 	// 字幕都下载缓存好了,需要抉择存哪一个,优先选择中文双语的,然后到中文
@@ -465,14 +445,14 @@ func (d *Downloader) seriesDlFunc(ctx context.Context, inData interface{}) error
 	// 构建每个字幕站点下载者的实例
 	var subSupplierHub *subSupplier.SubSupplierHub
 	subSupplierHub = subSupplier.NewSubSupplierHub(
-		zimuku.NewSupplier(d.reqParam),
-		//subhd.NewSupplier(d.reqParam),
-		xunlei.NewSupplier(d.reqParam),
-		shooter.NewSupplier(d.reqParam),
+		zimuku.NewSupplier(d.settings),
+		//subhd.NewSupplier(d.settings),
+		xunlei.NewSupplier(d.settings),
+		shooter.NewSupplier(d.settings),
 	)
 	if common.SubhdCode != "" {
 		// 如果找到 code 了,那么就可以继续用这个实例
-		subSupplierHub.AddSubSupplier(subhd.NewSupplier(d.reqParam))
+		subSupplierHub.AddSubSupplier(subhd.NewSupplier(d.settings))
 	}
 
 	// 这里拿到了这一部连续剧的所有的剧集信息,以及所有下载到的字幕信息
@@ -560,7 +540,7 @@ func (d *Downloader) seriesDlFunc(ctx context.Context, inData interface{}) error
 		}
 	}
 	// 是否清理全季的缓存字幕文件夹
-	if d.reqParam.SaveOneSeasonSub == false {
+	if d.settings.AdvancedSettings.SaveFullSeasonTmpSubtitles == false {
 		err = sub_helper.DeleteOneSeasonSubCacheFolder(seriesInfo.DirPath)
 		if err != nil {
 			return err

+ 6 - 6
internal/pkg/downloader/downloader_things.go

@@ -24,7 +24,7 @@ func (d Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFi
 	videoFileName := filepath.Base(oneVideoFullPath)
 	// -------------------------------------------------
 	// 调试缓存,把下载好的字幕写到对应的视频目录下,方便调试
-	if d.reqParam.DebugMode == true {
+	if d.settings.DebugMode == true {
 
 		err = my_util.CopyFiles2DebugFolder([]string{videoFileName}, organizeSubFiles)
 		if err != nil {
@@ -44,7 +44,7 @@ func (d Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFi
 		// 找个错误可以忍
 		d.log.Errorln("SearchVideoMatchSubFileAndRemoveExtMark,", oneVideoFullPath, err)
 	}
-	if d.reqParam.SaveMultiSub == false {
+	if d.settings.SaveMultiSub == false {
 		// 选择最优的一个字幕
 		var finalSubFile *subparser.FileInfo
 		finalSubFile = d.mk.SelectOneSubFile(organizeSubFiles)
@@ -65,7 +65,7 @@ func (d Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFi
 		// 找到了,写入文件
 		err = d.writeSubFile2VideoPath(oneVideoFullPath, *finalSubFile, "", bSetDefault, false)
 		if err != nil {
-			d.log.Errorln("SaveMultiSub:", d.reqParam.SaveMultiSub, "writeSubFile2VideoPath:", err)
+			d.log.Errorln("SaveMultiSub:", d.settings.SaveMultiSub, "writeSubFile2VideoPath:", err)
 			return
 		}
 	} else {
@@ -90,7 +90,7 @@ func (d Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFi
 				}
 				err = d.writeSubFile2VideoPath(oneVideoFullPath, file, siteNames[i], setDefault, false)
 				if err != nil {
-					d.log.Errorln("SaveMultiSub:", d.reqParam.SaveMultiSub, "writeSubFile2VideoPath:", err)
+					d.log.Errorln("SaveMultiSub:", d.settings.SaveMultiSub, "writeSubFile2VideoPath:", err)
 					return
 				}
 			}
@@ -105,7 +105,7 @@ func (d Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFi
 			for i := len(finalSubFiles) - 1; i > -1; i-- {
 				err = d.writeSubFile2VideoPath(oneVideoFullPath, finalSubFiles[i], siteNames[i], false, false)
 				if err != nil {
-					d.log.Errorln("SaveMultiSub:", d.reqParam.SaveMultiSub, "writeSubFile2VideoPath:", err)
+					d.log.Errorln("SaveMultiSub:", d.settings.SaveMultiSub, "writeSubFile2VideoPath:", err)
 					return
 				}
 			}
@@ -194,7 +194,7 @@ func (d Downloader) writeSubFile2VideoPath(videoFileFullPath string, finalSubFil
 	d.log.Infoln("SubDownAt:", desSubFullPath)
 
 	// 然后还需要判断是否需要校正字幕的时间轴
-	if d.reqParam.FixTimeLine == true {
+	if d.settings.FixTimeLine == true {
 		err = d.subTimelineFixerHelperEx.Process(videoFileFullPath, desSubFullPath)
 		if err != nil {
 			return err

+ 23 - 25
internal/pkg/emby_api/emby_api.go

@@ -2,8 +2,8 @@ package emby_api
 
 import (
 	"fmt"
-	"github.com/allanpk716/ChineseSubFinder/internal/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/emby"
 	"github.com/go-resty/resty/v2"
 	"github.com/panjf2000/ants/v2"
@@ -14,20 +14,18 @@ import (
 )
 
 type EmbyApi struct {
-	embyConfig emby.EmbyConfig
+	embyConfig settings.EmbySettings
 	threads    int
 	timeOut    time.Duration
 	client     *resty.Client
 }
 
-func NewEmbyApi(embyConfig emby.EmbyConfig) *EmbyApi {
+func NewEmbyApi(embyConfig settings.EmbySettings) *EmbyApi {
 	em := EmbyApi{}
 	em.embyConfig = embyConfig
-	if em.embyConfig.LimitCount < common.EmbyApiGetItemsLimitMin ||
-		em.embyConfig.LimitCount > common.EmbyApiGetItemsLimitMax {
-
-		em.embyConfig.LimitCount = common.EmbyApiGetItemsLimitMin
-	}
+	// 检查是否超过范围
+	em.embyConfig.Check()
+	// 强制设置
 	em.threads = 6
 	em.timeOut = 5 * 60 * time.Second
 	// 见 https://github.com/allanpk716/ChineseSubFinder/issues/140
@@ -113,9 +111,9 @@ func (em EmbyApi) GetRecentlyItems() (emby.EmbyRecentlyItems, error) {
 		// 默认是不指定某一个User的视频列表
 		_, err = em.client.R().
 			SetQueryParams(map[string]string{
-				"api_key":          em.embyConfig.ApiKey,
+				"api_key":          em.embyConfig.APIKey,
 				"IsUnaired":        "false",
-				"Limit":            fmt.Sprintf("%d", em.embyConfig.LimitCount),
+				"Limit":            fmt.Sprintf("%d", em.embyConfig.MaxRequestVideoNumber),
 				"Recursive":        "true",
 				"SortOrder":        "Descending",
 				"IncludeItemTypes": "Episode,Movie",
@@ -123,7 +121,7 @@ func (em EmbyApi) GetRecentlyItems() (emby.EmbyRecentlyItems, error) {
 				"SortBy":           "DateCreated",
 			}).
 			SetResult(&recItems).
-			Get(em.embyConfig.Url + "/emby/Items")
+			Get(em.embyConfig.AddressUrl + "/emby/Items")
 
 		if err != nil {
 			return emby.EmbyRecentlyItems{}, err
@@ -142,9 +140,9 @@ func (em EmbyApi) GetRecentlyItems() (emby.EmbyRecentlyItems, error) {
 			// 获取指定用户的视频列表
 			_, err = em.client.R().
 				SetQueryParams(map[string]string{
-					"api_key":          em.embyConfig.ApiKey,
+					"api_key":          em.embyConfig.APIKey,
 					"IsUnaired":        "false",
-					"Limit":            fmt.Sprintf("%d", em.embyConfig.LimitCount),
+					"Limit":            fmt.Sprintf("%d", em.embyConfig.MaxRequestVideoNumber),
 					"Recursive":        "true",
 					"SortOrder":        "Descending",
 					"IncludeItemTypes": "Episode,Movie",
@@ -152,7 +150,7 @@ func (em EmbyApi) GetRecentlyItems() (emby.EmbyRecentlyItems, error) {
 					"SortBy":           "DateCreated",
 				}).
 				SetResult(&tmpRecItems).
-				Get(em.embyConfig.Url + "/emby/Users/" + item.Id + "/Items")
+				Get(em.embyConfig.AddressUrl + "/emby/Users/" + item.Id + "/Items")
 
 			if err != nil {
 				return emby.EmbyRecentlyItems{}, err
@@ -200,10 +198,10 @@ func (em EmbyApi) GetUserIdList() (emby.EmbyUsers, error) {
 	var recItems emby.EmbyUsers
 	_, err := em.client.R().
 		SetQueryParams(map[string]string{
-			"api_key": em.embyConfig.ApiKey,
+			"api_key": em.embyConfig.APIKey,
 		}).
 		SetResult(&recItems).
-		Get(em.embyConfig.Url + "/emby/Users/Query")
+		Get(em.embyConfig.AddressUrl + "/emby/Users/Query")
 
 	if err != nil {
 		return emby.EmbyUsers{}, err
@@ -219,10 +217,10 @@ func (em EmbyApi) GetItemAncestors(id string) ([]emby.EmbyItemsAncestors, error)
 
 	_, err := em.client.R().
 		SetQueryParams(map[string]string{
-			"api_key": em.embyConfig.ApiKey,
+			"api_key": em.embyConfig.APIKey,
 		}).
 		SetResult(&recItems).
-		Get(em.embyConfig.Url + "/emby/Items/" + id + "/Ancestors")
+		Get(em.embyConfig.AddressUrl + "/emby/Items/" + id + "/Ancestors")
 	if err != nil {
 		return nil, err
 	}
@@ -237,10 +235,10 @@ func (em EmbyApi) GetItemVideoInfo(id string) (emby.EmbyVideoInfo, error) {
 
 	_, err := em.client.R().
 		SetQueryParams(map[string]string{
-			"api_key": em.embyConfig.ApiKey,
+			"api_key": em.embyConfig.APIKey,
 		}).
 		SetResult(&recItem).
-		Get(em.embyConfig.Url + "/emby/LiveTv/Programs/" + id)
+		Get(em.embyConfig.AddressUrl + "/emby/LiveTv/Programs/" + id)
 	if err != nil {
 		return emby.EmbyVideoInfo{}, err
 	}
@@ -255,10 +253,10 @@ func (em EmbyApi) GetItemVideoInfoByUserId(userId, videoId string) (emby.EmbyVid
 
 	_, err := em.client.R().
 		SetQueryParams(map[string]string{
-			"api_key": em.embyConfig.ApiKey,
+			"api_key": em.embyConfig.APIKey,
 		}).
 		SetResult(&recItem).
-		Get(em.embyConfig.Url + "/emby/Users/" + userId + "/Items/" + videoId)
+		Get(em.embyConfig.AddressUrl + "/emby/Users/" + userId + "/Items/" + videoId)
 	if err != nil {
 		return emby.EmbyVideoInfoByUserId{}, err
 	}
@@ -271,9 +269,9 @@ func (em EmbyApi) UpdateVideoSubList(id string) error {
 
 	_, err := em.client.R().
 		SetQueryParams(map[string]string{
-			"api_key": em.embyConfig.ApiKey,
+			"api_key": em.embyConfig.APIKey,
 		}).
-		Post(em.embyConfig.Url + "/emby/Items/" + id + "/Refresh")
+		Post(em.embyConfig.AddressUrl + "/emby/Items/" + id + "/Refresh")
 	if err != nil {
 		return err
 	}
@@ -285,7 +283,7 @@ func (em EmbyApi) UpdateVideoSubList(id string) error {
 func (em EmbyApi) GetSubFileData(videoId, mediaSourceId, subIndex, subExt string) (string, error) {
 
 	response, err := em.client.R().
-		Get(em.embyConfig.Url + "/emby/Videos/" + videoId + "/" + mediaSourceId + "/Subtitles/" + subIndex + "/Stream" + subExt)
+		Get(em.embyConfig.AddressUrl + "/emby/Videos/" + videoId + "/" + mediaSourceId + "/Subtitles/" + subIndex + "/Stream" + subExt)
 	if err != nil {
 		return "", err
 	}

+ 11 - 0
internal/pkg/global_value/global_value.go

@@ -0,0 +1,11 @@
+package global_value
+
+// util.go
+var (
+	DefDebugFolder       = ""
+	DefTmpFolder         = ""
+	DefSubFixCacheFolder = ""
+	WantedExtMap         = make(map[string]string) // 人工确认的需要监控的视频后缀名
+	DefExtMap            = make(map[string]string) // 内置支持的视频后缀名列表
+	CustomVideoExts      = make([]string, 0)       // 用户额外自定义的视频后缀名列表
+)

+ 33 - 32
internal/pkg/my_util/util.go

@@ -8,7 +8,7 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/global_value"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/regex_things"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	browser "github.com/allanpk716/fake-useragent"
 	"github.com/go-resty/resty/v2"
 	"github.com/google/uuid"
@@ -26,29 +26,21 @@ import (
 )
 
 // NewHttpClient 新建一个 resty 的对象
-func NewHttpClient(_reqParam ...types.ReqParam) *resty.Client {
+func NewHttpClient(_proxySettings ...settings.ProxySettings) *resty.Client {
 	//const defUserAgent = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"
 	//const defUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.41"
-	// 随机的 Browser
-	defUserAgent := browser.Random()
 
-	var reqParam types.ReqParam
+	var proxySettings settings.ProxySettings
 	var HttpProxy, UserAgent, Referer string
 
-	if len(_reqParam) > 0 {
-		reqParam = _reqParam[0]
-	}
-	if len(reqParam.HttpProxy) > 0 {
-		HttpProxy = reqParam.HttpProxy
-	}
-	if len(reqParam.UserAgent) > 0 {
-		UserAgent = reqParam.UserAgent
-	} else {
-		UserAgent = defUserAgent
+	if len(_proxySettings) > 0 {
+		proxySettings = _proxySettings[0]
 	}
-	if len(reqParam.Referer) > 0 {
-		Referer = reqParam.Referer
+	if proxySettings.UseHttpProxy == true && len(proxySettings.HttpProxyAddress) > 0 {
+		HttpProxy = proxySettings.HttpProxyAddress
 	}
+	// 随机的 Browser
+	UserAgent = browser.Random()
 
 	httpClient := resty.New()
 	httpClient.SetTimeout(common.HTMLTimeOut)
@@ -59,10 +51,15 @@ func NewHttpClient(_reqParam ...types.ReqParam) *resty.Client {
 		httpClient.RemoveProxy()
 	}
 
+	if len(proxySettings.Referer) > 0 {
+		Referer = proxySettings.Referer
+	}
+
 	httpClient.SetHeaders(map[string]string{
 		"Content-Type": "application/json",
 		"User-Agent":   UserAgent,
 	})
+
 	if len(Referer) > 0 {
 		httpClient.SetHeader("Referer", Referer)
 	}
@@ -71,12 +68,12 @@ func NewHttpClient(_reqParam ...types.ReqParam) *resty.Client {
 }
 
 // DownFile 从指定的 url 下载文件
-func DownFile(urlStr string, _reqParam ...types.ReqParam) ([]byte, string, error) {
-	var reqParam types.ReqParam
-	if len(_reqParam) > 0 {
-		reqParam = _reqParam[0]
+func DownFile(urlStr string, _proxySettings ...settings.ProxySettings) ([]byte, string, error) {
+	var proxySettings settings.ProxySettings
+	if len(_proxySettings) > 0 {
+		proxySettings = _proxySettings[0]
 	}
-	httpClient := NewHttpClient(reqParam)
+	httpClient := NewHttpClient(proxySettings)
 	resp, err := httpClient.R().Get(urlStr)
 	if err != nil {
 		return nil, "", err
@@ -207,27 +204,31 @@ func GetEpisodeKeyName(season, eps int) string {
 // CopyFile copies a single file from src to dst
 func CopyFile(src, dst string) error {
 	var err error
-	var srcfd *os.File
-	var dstfd *os.File
-	var srcinfo os.FileInfo
+	var srcFd *os.File
+	var dstFd *os.File
+	var srcInfo os.FileInfo
 
-	if srcfd, err = os.Open(src); err != nil {
+	if srcFd, err = os.Open(src); err != nil {
 		return err
 	}
-	defer srcfd.Close()
+	defer func() {
+		_ = srcFd.Close()
+	}()
 
-	if dstfd, err = os.Create(dst); err != nil {
+	if dstFd, err = os.Create(dst); err != nil {
 		return err
 	}
-	defer dstfd.Close()
+	defer func() {
+		_ = dstFd.Close()
+	}()
 
-	if _, err = io.Copy(dstfd, srcfd); err != nil {
+	if _, err = io.Copy(dstFd, srcFd); err != nil {
 		return err
 	}
-	if srcinfo, err = os.Stat(src); err != nil {
+	if srcInfo, err = os.Stat(src); err != nil {
 		return err
 	}
-	return os.Chmod(dst, srcinfo.Mode())
+	return os.Chmod(dst, srcInfo.Mode())
 }
 
 // CopyDir copies a whole directory recursively

+ 0 - 48
internal/pkg/proxy_helper/proxy_helper.go

@@ -1,48 +0,0 @@
-package proxy_helper
-
-import (
-	"errors"
-	"net/http"
-	"net/url"
-	"regexp"
-	"time"
-)
-
-func ProxyTest(proxyAddr string) (Speed int, Status int, err error) {
-	// 首先检测 proxyAddr 是否合法,必须是 http 的代理,不支持 https 代理
-	re := regexp.MustCompile(`(http):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`)
-	result := re.FindAllStringSubmatch(proxyAddr, -1)
-	if result == nil {
-		err = errors.New("proxy address illegal, only support http://xx:xx")
-		return 0, 0, err
-	}
-	// 检测代理iP访问地址
-	var testUrl string
-	testUrl = "http://google.com"
-	// 解析代理地址
-	proxy, err := url.Parse(proxyAddr)
-	// 设置网络传输
-	netTransport := &http.Transport{
-		Proxy:                 http.ProxyURL(proxy),
-		MaxIdleConnsPerHost:   10,
-		ResponseHeaderTimeout: time.Second * time.Duration(5),
-	}
-	// 创建连接客户端
-	httpClient := &http.Client{
-		Timeout:   time.Second * 10,
-		Transport: netTransport,
-	}
-	begin := time.Now() //判断代理访问时间
-	// 使用代理IP访问测试地址
-	res, err := httpClient.Get(testUrl)
-	if err != nil {
-		return
-	}
-	defer res.Body.Close()
-	speed := int(time.Now().Sub(begin).Nanoseconds() / 1000 / 1000) //ms
-	// 判断是否成功访问,如果成功访问StatusCode应该为200
-	if res.StatusCode != http.StatusOK {
-		return
-	}
-	return speed, res.StatusCode, nil
-}

+ 0 - 22
internal/pkg/proxy_helper/proxy_helper_test.go

@@ -1,22 +0,0 @@
-package proxy_helper
-
-import "testing"
-
-func TestProxyTest(t *testing.T) {
-
-	gotSpeed, gotStatus, err := ProxyTest("http://192.168.50.252:20172")
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Logf("Speed: %d Statis: %d", gotSpeed, gotStatus)
-
-	_, _, err = ProxyTest("http:/192.168.1.123:123")
-	if err == nil {
-		t.Fatal(err)
-	}
-
-	_, _, err = ProxyTest("http://192.168.1.123:123")
-	if err == nil {
-		t.Fatal(err)
-	}
-}

+ 13 - 7
internal/pkg/settings/advanced_settings.go

@@ -1,17 +1,23 @@
 package settings
 
+import "github.com/allanpk716/ChineseSubFinder/internal/common"
+
 type AdvancedSettings struct {
-	DebugMode                  bool     `json:"debug_mode"`                     // 是否开启调试模式,这个是写入一个特殊的文件来开启日志的 Debug 输出
-	SaveFullSeasonTmpSubtitles bool     `json:"save_full_season_tmp_subtitles"` // 保存整季的缓存字幕
-	SubTypePriority            int      `json:"sub_type_priority"`              // 字幕下载的优先级,0 是自动,1 是 srt 优先,2 是 ass/ssa 优先
-	SubNameFormatter           int      `json:"sub_name_formatter"`             // 字幕命名格式(默认不填写或者超出范围,则为 emby 格式),0,emby 支持的的格式(AAA.chinese(简英,subhd).ass or AAA.chinese(简英,xunlei).default.ass),1常规格式(兼容性更好,AAA.zh.ass or AAA.zh.default.ass)
-	SaveMultiSub               bool     `json:"save_multi_sub"`                 // 保存多个网站的 Top 1 字幕
-	CustomVideoExts            []string `json:"custom_video_exts""`             // 自定义视频扩展名,是在原有基础上新增。
-	FixTimeLine                bool     `json:"fix_time_line"`                  // 开启校正字幕时间轴,默认 false
+	ProxySettings              *ProxySettings `json:"proxy_settings"`
+	DebugMode                  bool           `json:"debug_mode"`                     // 是否开启调试模式,这个是写入一个特殊的文件来开启日志的 Debug 输出
+	SaveFullSeasonTmpSubtitles bool           `json:"save_full_season_tmp_subtitles"` // 保存整季的缓存字幕
+	SubTypePriority            int            `json:"sub_type_priority"`              // 字幕下载的优先级,0 是自动,1 是 srt 优先,2 是 ass/ssa 优先
+	SubNameFormatter           int            `json:"sub_name_formatter"`             // 字幕命名格式(默认不填写或者超出范围,则为 emby 格式),0,emby 支持的的格式(AAA.chinese(简英,subhd).ass or AAA.chinese(简英,xunlei).default.ass),1常规格式(兼容性更好,AAA.zh.ass or AAA.zh.default.ass)
+	SaveMultiSub               bool           `json:"save_multi_sub"`                 // 保存多个网站的 Top 1 字幕
+	CustomVideoExts            []string       `json:"custom_video_exts""`             // 自定义视频扩展名,是在原有基础上新增。
+	FixTimeLine                bool           `json:"fix_time_line"`                  // 开启校正字幕时间轴,默认 false
+	Topic                      int            `json:"topic"`                          // 搜索结果的时候,返回 Topic N 以内的
 }
 
 func NewAdvancedSettings() *AdvancedSettings {
 	return &AdvancedSettings{
+		ProxySettings:   &ProxySettings{},
 		CustomVideoExts: make([]string, 0),
+		Topic:           common.DownloadSubsPerSite,
 	}
 }

+ 0 - 2
internal/pkg/settings/common_settings.go

@@ -1,8 +1,6 @@
 package settings
 
 type CommonSettings struct {
-	UseHttpProxy     bool     `json:"use_http_proxy"`       // 是否使用 http 代理
-	HttpProxyAddress string   `json:"http_proxy_address"`   // Http 代理地址,内网
 	ScanInterval     string   `json:"scan_interval"`        // 一轮字幕扫描的间隔
 	Threads          int      `json:"threads"`              // 同时扫描的并发数
 	RunScanAtStartUp bool     `json:"run_scan_at_start_up"` // 完成引导设置后,下次运行程序就开始扫描

+ 10 - 0
internal/pkg/settings/emby_settings.go

@@ -1,5 +1,7 @@
 package settings
 
+import "github.com/allanpk716/ChineseSubFinder/internal/common"
+
 type EmbySettings struct {
 	Enable                 bool              `json:"enable"`                   // 是否启用
 	AddressUrl             string            `json:"address_url"`              // 内网服务器的 url
@@ -17,3 +19,11 @@ func NewEmbySettings() *EmbySettings {
 		SeriesDirectoryMapping: make(map[string]string, 0),
 	}
 }
+
+func (e EmbySettings) Check() {
+	if e.MaxRequestVideoNumber < common.EmbyApiGetItemsLimitMin ||
+		e.MaxRequestVideoNumber > common.EmbyApiGetItemsLimitMax {
+
+		e.MaxRequestVideoNumber = common.EmbyApiGetItemsLimitMin
+	}
+}

+ 7 - 0
internal/pkg/settings/proxy_settings.go

@@ -0,0 +1,7 @@
+package settings
+
+type ProxySettings struct {
+	UseHttpProxy     bool   `json:"use_http_proxy"`     // 是否使用 http 代理
+	HttpProxyAddress string `json:"http_proxy_address"` // Http 代理地址,内网
+	Referer          string `json:"-"`                  // 可能下载文件的时候需要设置
+}

+ 34 - 12
internal/pkg/settings/settings.go

@@ -8,12 +8,13 @@ import (
 )
 
 type Settings struct {
-	configFPath       string
-	UserInfo          *UserInfo          `json:"user_info"`
-	CommonSettings    *CommonSettings    `json:"common_settings"`
-	AdvancedSettings  *AdvancedSettings  `json:"advanced_settings"`
-	EmbySettings      *EmbySettings      `json:"emby_settings"`
-	DeveloperSettings *DeveloperSettings `json:"developer_settings"`
+	configFPath           string
+	UserInfo              *UserInfo              `json:"user_info"`
+	CommonSettings        *CommonSettings        `json:"common_settings"`
+	AdvancedSettings      *AdvancedSettings      `json:"advanced_settings"`
+	EmbySettings          *EmbySettings          `json:"emby_settings"`
+	DeveloperSettings     *DeveloperSettings     `json:"developer_settings"`
+	TimelineFixerSettings *TimelineFixerSettings `json:"timeline_fixer_settings"`
 }
 
 // GetSettings 获取 Settings 的实例
@@ -51,12 +52,13 @@ func NewSettings() *Settings {
 	nowConfigFPath := ""
 
 	return &Settings{
-		configFPath:       nowConfigFPath,
-		UserInfo:          &UserInfo{},
-		CommonSettings:    NewCommonSettings(),
-		AdvancedSettings:  NewAdvancedSettings(),
-		EmbySettings:      NewEmbySettings(),
-		DeveloperSettings: NewDeveloperSettings(),
+		configFPath:           nowConfigFPath,
+		UserInfo:              &UserInfo{},
+		CommonSettings:        NewCommonSettings(),
+		AdvancedSettings:      NewAdvancedSettings(),
+		EmbySettings:          NewEmbySettings(),
+		DeveloperSettings:     NewDeveloperSettings(),
+		TimelineFixerSettings: NewTimelineFixerSettings(),
 	}
 }
 
@@ -74,6 +76,26 @@ func (s Settings) GetNoPasswordSettings() *Settings {
 	return nowSettings
 }
 
+// Check 检测,某些参数有范围限制
+func (s *Settings) Check() {
+
+	// 每个网站最多找 Top 几的字幕结果,评价系统成熟后,才有设计的意义
+	if s.AdvancedSettings.Topic < 0 || s.AdvancedSettings.Topic > 3 {
+		s.AdvancedSettings.Topic = 1
+	}
+	// 如果 Debug 模式开启了,强制设置线程数为1,方便定位问题
+	if s.AdvancedSettings.DebugMode == true {
+		s.CommonSettings.Threads = 1
+	} else {
+		// 并发线程的范围控制
+		if s.CommonSettings.Threads <= 0 {
+			s.CommonSettings.Threads = 1
+		} else if s.CommonSettings.Threads >= 3 {
+			s.CommonSettings.Threads = 3
+		}
+	}
+}
+
 var (
 	settings     *Settings
 	settingsOnce sync.Once

+ 20 - 0
internal/pkg/settings/timeline_fixer_settings.go

@@ -0,0 +1,20 @@
+package settings
+
+type TimelineFixerSettings struct {
+	MaxOffsetTime int     `json:"max_offset_time"` // 最大支持校正时间偏移的范围,单位秒
+	MinOffset     float64 `json:"min_offset"`      // 最小的时间片校正偏移,低于这个(正负)就跳过不校正,单位秒
+}
+
+func NewTimelineFixerSettings() *TimelineFixerSettings {
+	return &TimelineFixerSettings{}
+}
+
+func (t *TimelineFixerSettings) Check() {
+	if t.MaxOffsetTime < 0 || t.MaxOffsetTime > 120 {
+		t.MaxOffsetTime = 60 // 60s
+	}
+
+	if t.MinOffset < 0 || t.MinOffset > 1 {
+		t.MinOffset = 0.1 // 100ms
+	}
+}

+ 1076 - 1075
internal/pkg/sub_timeline_fixer/fixer.go

@@ -1,1077 +1,1078 @@
 package sub_timeline_fixer
 
-import (
-	"errors"
-	"fmt"
-	"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/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"
-	"github.com/go-echarts/go-echarts/v2/opts"
-	"github.com/grd/stat"
-	"github.com/james-bowman/nlp/measures/pairwise"
-	"github.com/mndrix/tukey"
-	"github.com/panjf2000/ants/v2"
-	"golang.org/x/net/context"
-	"gonum.org/v1/gonum/mat"
-	"math"
-	"os"
-	"sort"
-	"strings"
-	"sync"
-	"time"
-)
-
-type SubTimelineFixer struct {
-	FixerConfig sub_timeline_fiexer.SubTimelineFixerConfig
-}
-
-func NewSubTimelineFixer(fixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixer {
-	return &SubTimelineFixer{
-		FixerConfig: fixerConfig,
-	}
-}
-
-// StopWordCounter 停止词统计
-func (s *SubTimelineFixer) StopWordCounter(inString string, per int) []string {
-	statisticTimes := make(map[string]int)
-	wordsLength := strings.Fields(inString)
-
-	for counts, word := range wordsLength {
-		// 判断key是否存在,这个word是字符串,这个counts是统计的word的次数。
-		word, ok := statisticTimes[word]
-		if ok {
-			word = word
-			statisticTimes[wordsLength[counts]] = statisticTimes[wordsLength[counts]] + 1
-		} else {
-			statisticTimes[wordsLength[counts]] = 1
-		}
-	}
-
-	stopWords := make([]string, 0)
-	mapByValue := sortMapByValue(statisticTimes)
-
-	breakIndex := len(mapByValue) * per / 100
-	for index, wordInfo := range mapByValue {
-		if index > breakIndex {
-			break
-		}
-		stopWords = append(stopWords, wordInfo.Name)
-	}
-
-	return stopWords
-}
-
-// FixSubTimelineOneOffsetTime 校正整个字幕文件的时间轴,适用于一个偏移值的情况
-func (s *SubTimelineFixer) FixSubTimelineOneOffsetTime(infoSrc *subparser.FileInfo, inOffsetTime float64, desSaveSubFileFullPath string) (string, error) {
-
-	/*
-		从解析的实例中,正常来说是可以匹配出所有的 Dialogue 对话的 Start 和 End time 的信息
-		然后找到对应的字幕的文件,进行文件内容的替换来做时间轴的校正
-	*/
-	// 偏移时间
-	offsetTime := time.Duration(inOffsetTime*1000) * time.Millisecond
-	fixContent := infoSrc.Content
-	/*
-		这里进行时间转字符串的时候有一点比较特殊
-		正常来说输出的格式是类似 15:04:05.00
-		那么有个问题,字幕的时间格式是 0:00:12.00, 小时,是个数,除非有跨度到 20 小时的视频,不然小时就应该是个数
-		这就需要一个额外的函数去处理这些情况
-	*/
-	timeFormat := infoSrc.GetTimeFormat()
-	for _, srcOneDialogue := range infoSrc.Dialogues {
-
-		timeStart, err := my_util.ParseTime(srcOneDialogue.StartTime)
-		if err != nil {
-			return "", err
-		}
-		timeEnd, err := my_util.ParseTime(srcOneDialogue.EndTime)
-		if err != nil {
-			return "", err
-		}
-
-		fixTimeStart := timeStart.Add(offsetTime)
-		fixTimeEnd := timeEnd.Add(offsetTime)
-
-		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.StartTime, my_util.Time2SubTimeString(fixTimeStart, timeFormat))
-		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.EndTime, my_util.Time2SubTimeString(fixTimeEnd, timeFormat))
-	}
-
-	dstFile, err := os.Create(desSaveSubFileFullPath)
-	if err != nil {
-		return "", err
-	}
-	defer func() {
-		_ = dstFile.Close()
-	}()
-	_, err = dstFile.WriteString(fixContent)
-	if err != nil {
-		return "", err
-	}
-	return fixContent, nil
-}
-
-// FixSubTimelineByFixResults V2 专用的时间校正函数
-func (s SubTimelineFixer) FixSubTimelineByFixResults(infoSrc *subparser.FileInfo, srcUnitNew *sub_helper.SubUnit, fixedResults []FixResult, desSaveSubFileFullPath string) (string, error) {
-
-	startTime := srcUnitNew.GetStartTime(true)
-	startTimeBaseDouble := my_util.Time2SecondNumber(startTime)
-	/*
-		这里拿到的 fixedResults ,是进行过 V2_FrontAndEndPerSrc 头尾去除
-		那么调整目标字幕的时候,需要考虑截取掉的部分也要算进去
-	*/
-	/*
-		从解析的实例中,正常来说是可以匹配出所有的 Dialogue 对话的 Start 和 End time 的信息
-		然后找到对应的字幕的文件,进行文件内容的替换来做时间轴的校正
-	*/
-	fixContent := infoSrc.Content
-	/*
-		这里进行时间转字符串的时候有一点比较特殊
-		正常来说输出的格式是类似 15:04:05.00
-		那么有个问题,字幕的时间格式是 0:00:12.00, 小时,是个数,除非有跨度到 20 小时的视频,不然小时就应该是个数
-		这就需要一个额外的函数去处理这些情况
-	*/
-	timeFormat := infoSrc.GetTimeFormat()
-	cacheIndex := 0
-	/*
-		这里的理想情况是 Dialogues,每一句话都是递增的对白时间
-		但是实际情况可能是,前面几个对白是特效、音乐的备注,那么他们的跨度可以很大
-		然后才到正常的对话对白,这样就出现不是递增的时间对白情况
-		那么就需要对 Dialogues 进行排序,然后再进行处理
-	*/
-	sort.Sort(subparser.OneDialogueByStartTime(infoSrc.Dialogues))
-
-	for index, srcOneDialogue := range infoSrc.Dialogues {
-
-		timeStart, err := my_util.ParseTime(srcOneDialogue.StartTime)
-		if err != nil {
-			return "", err
-		}
-		timeEnd, err := my_util.ParseTime(srcOneDialogue.EndTime)
-		if err != nil {
-			return "", err
-		}
-
-		inOffsetTime := 0.0
-		orgStartTimeDouble := my_util.Time2SecondNumber(timeStart)
-		for cacheIndex < len(fixedResults) {
-
-			inRange, nowOffsetTime := fixedResults[cacheIndex].InRange(startTimeBaseDouble, orgStartTimeDouble)
-			if inRange == false {
-				// 大于当前的范围,递增一个区间进行再次的判断
-				// 但是需要确定的是,递增出来的这个区间的 Index 是有效的,如果是无效的,那么就使用最后一个区间的偏移时间
-				cacheIndex++
-				continue
-			} else {
-				inOffsetTime = nowOffsetTime
-				break
-			}
-		}
-		if cacheIndex >= len(fixedResults) {
-			// 下一个区间的 Index 已经越界了,那么就使用最后一个区间的偏移
-			inOffsetTime = fixedResults[len(fixedResults)-1].NewMean
-		}
-		// 偏移时间
-		println(index, inOffsetTime)
-		offsetTime := time.Duration(inOffsetTime*1000) * time.Millisecond
-		fixTimeStart := timeStart.Add(offsetTime)
-		fixTimeEnd := timeEnd.Add(offsetTime)
-
-		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.StartTime, "Index:"+fmt.Sprintf("%d-", index)+my_util.Time2SubTimeString(fixTimeStart, timeFormat))
-		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.EndTime, my_util.Time2SubTimeString(fixTimeEnd, timeFormat))
-	}
-
-	dstFile, err := os.Create(desSaveSubFileFullPath)
-	if err != nil {
-		return "", err
-	}
-	defer func() {
-		_ = dstFile.Close()
-	}()
-	_, err = dstFile.WriteString(fixContent)
-	if err != nil {
-		return "", err
-	}
-	return fixContent, nil
-}
-
-/*
-	对于 V1 版本的字幕时间轴校正来说,是有特殊的前置要求的
-	1. 视频要有英文字幕
-	2. 外置的字幕必须是中文的双语字幕(简英、繁英)
-*/
-// GetOffsetTimeV1 暂时只支持英文的基准字幕,源字幕必须是双语中英字幕
-func (s *SubTimelineFixer) GetOffsetTimeV1(infoBase, infoSrc *subparser.FileInfo, staticLineFileSavePath string, debugInfoFileSavePath string) (bool, float64, float64, error) {
-
-	var debugInfos = make([]string, 0)
-	// 构建基准语料库,目前阶段只需要考虑是 En 的就行了
-	var baseCorpus = make([]string, 0)
-	var baseDialogueFilterMap = make(map[int]int, 0)
-	/*
-		这里原来的写法是所有的 base 的都放进去匹配,这样会带来一些不必要的对白
-		需要剔除空白。那么就需要建立一个转换的字典
-	*/
-	for index, oneDialogueEx := range infoBase.DialoguesFilterEx {
-		if oneDialogueEx.EnLine == "" {
-			continue
-		}
-		baseCorpus = append(baseCorpus, oneDialogueEx.EnLine)
-		baseDialogueFilterMap[len(baseCorpus)-1] = index
-	}
-	// 初始化
-	pipLine, tfidf, err := NewTFIDF(baseCorpus)
-	if err != nil {
-		return false, 0, 0, err
-	}
-
-	/*
-		确认两个字幕间的偏移,暂定的方案是两边都连续匹配上 5 个索引,再抽取一个对话的时间进行修正计算
-	*/
-	maxCompareDialogue := s.FixerConfig.V1_MaxCompareDialogue
-	// 基线的长度
-	_, docsLength := tfidf.Dims()
-	var matchIndexList = make([]MatchIndex, 0)
-	sc := NewSubCompare(maxCompareDialogue)
-	// 开始比较相似度,默认认为是 Ch_en 就行了
-	for srcIndex := 0; srcIndex < len(infoSrc.DialoguesFilterEx); {
-
-		srcOneDialogueEx := infoSrc.DialoguesFilterEx[srcIndex]
-		// 这里只考虑 英文 的语言
-		if srcOneDialogueEx.EnLine == "" {
-			srcIndex++
-			continue
-		}
-		// run the query through the same pipeline that was fitted to the corpus and
-		// to project it into the same dimensional space
-		queryVector, err := pipLine.Transform(srcOneDialogueEx.EnLine)
-		if err != nil {
-			return false, 0, 0, err
-		}
-		// iterate over document feature vectors (columns) in the LSI matrix and compare
-		// with the query vector for similarity.  Similarity is determined by the difference
-		// between the angles of the vectors known as the cosine similarity
-		highestSimilarity := -1.0
-		// 匹配上的基准的索引
-		var baseIndex int
-		// 这里理论上需要把所有的基线遍历一次,但是,一般来说,两个字幕不可能差距在 50 行
-		// 这样的好处是有助于提高搜索的性能
-		// 那么就以当前的 src 的位置,向前、向后各 50 来遍历
-		nowMaxScanLength := srcIndex + 50
-		nowMinScanLength := srcIndex - 50
-		if nowMinScanLength < 0 {
-			nowMinScanLength = 0
-		}
-		if nowMaxScanLength > docsLength {
-			nowMaxScanLength = docsLength
-		}
-		for i := nowMinScanLength; i < nowMaxScanLength; i++ {
-			similarity := pairwise.CosineSimilarity(queryVector.(mat.ColViewer).ColView(0), tfidf.(mat.ColViewer).ColView(i))
-			if similarity > highestSimilarity {
-				baseIndex = i
-				highestSimilarity = similarity
-			}
-		}
-
-		startBaseIndex, startSrcIndex := sc.GetStartIndex()
-		if sc.Add(baseIndex, srcIndex) == false {
-			sc.Clear()
-			srcIndex = startSrcIndex + 1
-			continue
-			//sc.Add(baseIndex, srcIndex)
-		}
-		if sc.Check() == false {
-			srcIndex++
-			continue
-		} else {
-			sc.Clear()
-		}
-
-		matchIndexList = append(matchIndexList, MatchIndex{
-			BaseNowIndex: startBaseIndex,
-			//BaseNowIndex: baseDialogueFilterMap[startBaseIndex],
-			SrcNowIndex: startSrcIndex,
-			Similarity:  highestSimilarity,
-		})
-
-		//println(fmt.Sprintf("Similarity: %f Base[%d] %s-%s '%s' <--> Src[%d] %s-%s '%s'",
-		//	highestSimilarity,
-		//	baseIndex, infoBase.DialoguesFilterEx[baseIndex].relativelyStartTime, infoBase.DialoguesFilterEx[baseIndex].relativelyEndTime, baseCorpus[baseIndex],
-		//	srcIndex, srcOneDialogueEx.relativelyStartTime, srcOneDialogueEx.relativelyEndTime, srcOneDialogueEx.EnLine))
-
-		srcIndex++
-	}
-
-	var startDiffTimeLineData = make([]opts.LineData, 0)
-	var endDiffTimeLineData = make([]opts.LineData, 0)
-	var tmpStartDiffTime = make([]float64, 0)
-	var tmpEndDiffTime = make([]float64, 0)
-	var startDiffTimeList = make(stat.Float64Slice, 0)
-	var endDiffTimeList = make(stat.Float64Slice, 0)
-	var xAxis = make([]string, 0)
-	// 上面找出了连续匹配 maxCompareDialogue:N 次的字幕语句块
-	// 求出平均时间偏移
-	for mIndex, matchIndexItem := range matchIndexList {
-
-		for i := 0; i < maxCompareDialogue; i++ {
-			// 这里会统计连续的这 5 句话的时间差
-			//tmpBaseIndex := matchIndexItem.BaseNowIndex + i
-			tmpBaseIndex := baseDialogueFilterMap[matchIndexItem.BaseNowIndex+i]
-			tmpSrcIndex := matchIndexItem.SrcNowIndex + i
-
-			baseTimeStart, err := my_util.ParseTime(infoBase.DialoguesFilterEx[tmpBaseIndex].StartTime)
-			if err != nil {
-				return false, 0, 0, err
-			}
-			baseTimeEnd, err := my_util.ParseTime(infoBase.DialoguesFilterEx[tmpBaseIndex].EndTime)
-			if err != nil {
-				return false, 0, 0, err
-			}
-			srtTimeStart, err := my_util.ParseTime(infoSrc.DialoguesFilterEx[tmpSrcIndex].StartTime)
-			if err != nil {
-				return false, 0, 0, err
-			}
-			srtTimeEnd, err := my_util.ParseTime(infoSrc.DialoguesFilterEx[tmpSrcIndex].EndTime)
-			if err != nil {
-				return false, 0, 0, err
-			}
-
-			TimeDiffStart := baseTimeStart.Sub(srtTimeStart)
-			TimeDiffEnd := baseTimeEnd.Sub(srtTimeEnd)
-
-			startDiffTimeLineData = append(startDiffTimeLineData, opts.LineData{Value: TimeDiffStart.Seconds()})
-			endDiffTimeLineData = append(endDiffTimeLineData, opts.LineData{Value: TimeDiffEnd.Seconds()})
-
-			tmpStartDiffTime = append(tmpStartDiffTime, TimeDiffStart.Seconds())
-			tmpEndDiffTime = append(tmpEndDiffTime, TimeDiffEnd.Seconds())
-
-			startDiffTimeList = append(startDiffTimeList, TimeDiffStart.Seconds())
-			endDiffTimeList = append(endDiffTimeList, TimeDiffEnd.Seconds())
-
-			xAxis = append(xAxis, fmt.Sprintf("%d_%d", mIndex, i))
-
-			debugInfos = append(debugInfos, "bs "+infoBase.DialoguesFilterEx[tmpBaseIndex].StartTime+" <-> "+infoBase.DialoguesFilterEx[tmpBaseIndex].EndTime)
-			debugInfos = append(debugInfos, "sc "+infoSrc.DialoguesFilterEx[tmpSrcIndex].StartTime+" <-> "+infoSrc.DialoguesFilterEx[tmpSrcIndex].EndTime)
-			debugInfos = append(debugInfos, "StartDiffTime: "+fmt.Sprintf("%f", TimeDiffStart.Seconds()))
-			//println(fmt.Sprintf("Diff Start-End: %s - %s Base[%d] %s-%s '%s' <--> Src[%d] %s-%s '%s'",
-			//	TimeDiffStart, TimeDiffEnd,
-			//	tmpBaseIndex, infoBase.DialoguesFilterEx[tmpBaseIndex].relativelyStartTime, infoBase.DialoguesFilterEx[tmpBaseIndex].relativelyEndTime, infoBase.DialoguesFilterEx[tmpBaseIndex].EnLine,
-			//	tmpSrcIndex, infoSrc.DialoguesFilterEx[tmpSrcIndex].relativelyStartTime, infoSrc.DialoguesFilterEx[tmpSrcIndex].relativelyEndTime, infoSrc.DialoguesFilterEx[tmpSrcIndex].EnLine))
-		}
-		debugInfos = append(debugInfos, "---------------------------------------------")
-		//println("---------------------------------------------")
-	}
-
-	oldMean := stat.Mean(startDiffTimeList)
-	oldSd := stat.Sd(startDiffTimeList)
-	newMean := -1.0
-	newSd := -1.0
-	per := 1.0
-
-	// 如果 SD 较大的时候才需要剔除
-	if oldSd > 0.1 {
-		var outliersMap = make(map[float64]int, 0)
-		outliers, _, _ := tukey.Outliers(0.3, tmpStartDiffTime)
-		for _, outlier := range outliers {
-			outliersMap[outlier] = 0
-		}
-		var newStartDiffTimeList = make([]float64, 0)
-		for _, f := range tmpStartDiffTime {
-
-			_, ok := outliersMap[f]
-			if ok == true {
-				continue
-			}
-
-			newStartDiffTimeList = append(newStartDiffTimeList, f)
-		}
-
-		orgLen := startDiffTimeList.Len()
-		startDiffTimeList = make(stat.Float64Slice, 0)
-		for _, f := range newStartDiffTimeList {
-			startDiffTimeList = append(startDiffTimeList, f)
-		}
-		newLen := startDiffTimeList.Len()
-
-		per = float64(newLen) / float64(orgLen)
-
-		newMean = stat.Mean(startDiffTimeList)
-		newSd = stat.Sd(startDiffTimeList)
-	}
-
-	if newMean == -1.0 {
-		newMean = oldMean
-	}
-	if newSd == -1.0 {
-		newSd = oldSd
-	}
-
-	// 不为空的时候,生成调试文件
-	if staticLineFileSavePath != "" {
-		//staticLineFileSavePath = "bar.html"
-		err = SaveStaticLineV1(staticLineFileSavePath, infoBase.Name, infoSrc.Name,
-			per, oldMean, oldSd, newMean, newSd, xAxis,
-			startDiffTimeLineData, endDiffTimeLineData)
-		if err != nil {
-			return false, 0, 0, err
-		}
-	}
-
-	// 跳过的逻辑是 mean 是 0 ,那么现在如果判断有问题,缓存的调试文件继续生成,然后强制返回 0 来跳过后续的逻辑
-	// 这里需要考虑,找到的连续 5 句话匹配的有多少句,占比整体所有的 Dialogue 是多少,太低也需要跳过
-	matchIndexLineCount := len(matchIndexList) * maxCompareDialogue
-	//perMatch := float64(matchIndexLineCount) / float64(len(infoSrc.DialoguesFilterEx))
-	perMatch := float64(matchIndexLineCount) / float64(len(baseCorpus))
-	if perMatch < s.FixerConfig.V1_MinMatchedPercent {
-		tmpContent := infoSrc.Name + fmt.Sprintf(" Sequence match %d dialogues (< %f%%), Skip,", s.FixerConfig.V1_MaxCompareDialogue, s.FixerConfig.V1_MinMatchedPercent*100) + fmt.Sprintf(" %f%% ", perMatch*100)
-
-		debugInfos = append(debugInfos, tmpContent)
-
-		log_helper.GetLogger().Infoln(tmpContent)
-	} else {
-		tmpContent := infoSrc.Name + fmt.Sprintf(" Sequence match %d dialogues,", s.FixerConfig.V1_MaxCompareDialogue) + fmt.Sprintf(" %f%% ", perMatch*100)
-
-		debugInfos = append(debugInfos, tmpContent)
-
-		log_helper.GetLogger().Infoln(tmpContent)
-	}
-
-	// 输出调试的匹配时间轴信息的列表
-	if debugInfoFileSavePath != "" {
-		err = my_util.WriteStrings2File(debugInfoFileSavePath, debugInfos)
-		if err != nil {
-			return false, 0, 0, err
-		}
-	}
-	// 虽然有条件判断是认为有问题的,但是返回值还是要填写除去的
-	if perMatch < s.FixerConfig.V1_MinMatchedPercent {
-		return false, newMean, newSd, nil
-	}
-
-	return true, newMean, newSd, nil
-}
-
-// GetOffsetTimeV2 使用内置的字幕校正外置的字幕时间轴
-func (s *SubTimelineFixer) GetOffsetTimeV2(baseUnit, srcUnit *sub_helper.SubUnit, audioVadList []vad.VADInfo) (bool, []FixResult, error) {
-
-	// -------------------------------------------------
-	/*
-		开始针对对白单元进行匹配
-		下面的逻辑需要参考 FFT识别流程.jpg 这个图示来理解
-		实际实现的时候,会在上述 srcUnit 上,做一个滑动窗口来做匹配,80% 是窗口,20% 用于移动
-		步长固定在 10 步
-	*/
-	audioFloatList := vad.GetFloatSlice(audioVadList)
-
-	srcVADLen := len(srcUnit.VADList)
-	// 滑动窗口的长度
-	srcWindowLen := int(float64(srcVADLen) * s.FixerConfig.V2_WindowMatchPer)
-	// 划分为 4 个区域,每一个部分的长度
-	const parts = 10
-	perPartLen := srcVADLen / parts
-	matchedInfos := make([]MatchInfo, 0)
-
-	subVADBlockInfos := make([]SubVADBlockInfo, 0)
-	for i := 0; i < parts; i++ {
-
-		// 滑动窗体的起始 Index
-		srcSlideStartIndex := i * perPartLen
-		// 滑动的距离
-		srcSlideLen := perPartLen
-		// 一步的长度
-		oneStep := perPartLen / s.FixerConfig.V2_CompareParts
-		if srcSlideLen <= 0 {
-			srcSlideLen = 1
-		}
-		if oneStep <= 0 {
-			oneStep = 1
-		}
-		// -------------------------------------------------
-		windowInfo := WindowInfo{
-			BaseAudioFloatList: audioFloatList,
-			BaseUnit:           baseUnit,
-			SrcUnit:            srcUnit,
-			MatchedTimes:       0,
-			SrcWindowLen:       srcWindowLen,
-			SrcSlideStartIndex: srcSlideStartIndex,
-			SrcSlideLen:        srcSlideLen,
-			OneStep:            oneStep,
-		}
-		subVADBlockInfos = append(subVADBlockInfos, SubVADBlockInfo{
-			Index:      i,
-			StartIndex: srcSlideStartIndex,
-			EndIndex:   srcSlideStartIndex + srcSlideLen,
-		})
-		// 实际 FFT 的匹配逻辑函数
-		// 时间轴差值数组
-		matchInfo, err := s.slidingWindowProcessorV2(&windowInfo)
-		if err != nil {
-			return false, nil, err
-		}
-
-		matchedInfos = append(matchedInfos, *matchInfo)
-	}
-
-	fixedResults := make([]FixResult, 0)
-	sdLessCount := 0
-	// 这里的是 matchedInfos 是顺序的
-	for index, matchInfo := range matchedInfos {
-
-		log_helper.GetLogger().Infoln(index, "------------------------------------")
-		outCorrelationFixResult := s.calcMeanAndSDV2(matchInfo.StartDiffTimeListEx, matchInfo.StartDiffTimeList)
-		log_helper.GetLogger().Infoln(fmt.Sprintf("FFTAligner Old Mean: %v SD: %f Per: %v", outCorrelationFixResult.OldMean, outCorrelationFixResult.OldSD, outCorrelationFixResult.Per))
-		log_helper.GetLogger().Infoln(fmt.Sprintf("FFTAligner New Mean: %v SD: %f Per: %v", outCorrelationFixResult.NewMean, outCorrelationFixResult.NewSD, outCorrelationFixResult.Per))
-
-		value, indexMax := matchInfo.StartDiffTimeMap.Max()
-		log_helper.GetLogger().Infoln("FFTAligner Max score:", fmt.Sprintf("%v", value.(float64)), "Time:", fmt.Sprintf("%v", matchInfo.StartDiffTimeList[indexMax.(int)]))
-
-		outCorrelationFixResult.StartVADIndex = index * perPartLen
-		outCorrelationFixResult.EndVADIndex = index*perPartLen + perPartLen
-		fixedResults = append(fixedResults, outCorrelationFixResult)
-
-		if outCorrelationFixResult.NewSD < 0.1 {
-			sdLessCount++
-		}
-	}
-
-	// 如果 0.1 sd 以下的占比低于 70% 那么就认为字幕匹配失败
-	perLess := float64(sdLessCount) / float64(len(matchedInfos))
-	if perLess < 0.7 {
-		return false, nil, nil
-	}
-
-	// matchedInfos 与 fixedResults 是对等的关系,fixedResults 中是计算过 Mean 的值,而 matchedInfos 有原始的值
-	for i, info := range matchedInfos {
-		for j := 0; j < len(info.IndexMatchWindowInfoMap); j++ {
-
-			value, bFound := info.IndexMatchWindowInfoMap[j]
-			if bFound == false {
-				continue
-			}
-
-			fixedResults[i].MatchWindowInfos = append(fixedResults[i].MatchWindowInfos, value)
-		}
-	}
-	/*
-		如果 outCorrelationFixResult 的 SD > 0.1,那么大概率这个时间轴的值匹配的有问题,需要向左或者向右找一个值进行继承
-		-4 0.001
-		-4 0.001
-		-4 0.001
-		-200 0.1
-		-4 0.001
-		比如这种情况,那么就需要向左找到 -4 去继承。
-		具体的实现:
-			找到一个 SD > 0.1 的项目,那么就需要从左边和右边同时对比
-			首先是他们的差值要在 0.3s (绝对值)以内,优先往左边找,如果绝对值成立则判断 SD (SD 必须 < 0.1)
-			如果只是 SD 不成立,那么就继续往左,继续判断差值和 SD。
-			如果都找不到合适的,就要回到”起点“,从右开始找,逻辑一样
-			直到没有找到合适的信息,就报错
-	*/
-	// 进行细节的修正
-	for index, fixedResult := range fixedResults {
-		// SD 大于 0.1 或者是 当前的 NewMean 与上一个点的 NewMean 差值大于 0.3
-		if fixedResult.NewSD >= 0.1 || (index > 1 && math.Abs(fixedResult.NewMean-fixedResults[index-1].NewMean) > 0.3) {
-			bok, newMean, newSD := s.fixOnePartV2(index, fixedResults)
-			if bok == true {
-				fixedResults[index].NewMean = newMean
-				fixedResults[index].NewSD = newSD
-			}
-		}
-	}
-
-	return true, fixedResults, nil
-}
-
-// fixOnePartV2 轻微地跳动可以根据左或者右去微调
-func (s SubTimelineFixer) fixOnePartV2(startIndex int, fixedResults []FixResult) (bool, float64, float64) {
-
-	/*
-		找到这样情况的进行修正
-	*/
-	// 先往左
-	if startIndex-1 >= 0 {
-		// 说明至少可以往左
-		// 如果左边的这个值,与当前值超过了 0.3 的绝对差值,那么是不适合的,就需要往右找
-		if math.Abs(fixedResults[startIndex-1].NewMean-fixedResults[startIndex].NewMean) < 0.3 {
-			// 差值在接受的范围内,那么就使用这个左边的值去校正当前的值
-			return true, fixedResults[startIndex-1].NewMean, fixedResults[startIndex-1].NewSD
-		}
-	}
-
-	// 如果上面的理想情况都没有进去,那么就是这个差值很大
-	if fixedResults[startIndex].NewSD > 1 {
-		// SD 比较大,可能当前的位置是值是错误的,那么直接就使用左边的值
-		/*
-			-6.3	0.06
-			-146.85	243.83
-		*/
-		if startIndex-1 >= 0 {
-			return true, fixedResults[startIndex-1].NewMean, fixedResults[startIndex-1].NewSD
-		}
-	} else {
-		// SD 不是很大,可能就是正常的字幕分段的时间轴偏移的 越接处 !
-		// 那么需要取,越接处,前三和后三,进行均值计算
-		/*
-			-6.21
-			-6.22
-			-6.29	0.06
-
-			-7.13	0.14		越接处
-
-			-7.32
-			-7.31
-			-7.44
-		*/
-		left3Mean := 0.0
-		right3Mean := 0.0
-		// 向左,三个或者三个位置
-		if startIndex-3 >= 0 {
-			left3Mean = float64(fixedResults[startIndex-1].NewMean+fixedResults[startIndex-2].NewMean+fixedResults[startIndex-3].NewMean) / 3.0
-		} else if startIndex-2 >= 0 {
-			left3Mean = float64(fixedResults[startIndex-1].NewMean+fixedResults[startIndex-2].NewMean) / 2.0
-		} else {
-			return false, 0, 0
-		}
-		// 向右,三个或者三个位置
-		if startIndex+3 >= 0 {
-			right3Mean = float64(fixedResults[startIndex+1].NewMean+fixedResults[startIndex+2].NewMean+fixedResults[startIndex+3].NewMean) / 3.0
-		} else if startIndex+2 >= 0 {
-			right3Mean = float64(fixedResults[startIndex+1].NewMean+fixedResults[startIndex+2].NewMean) / 2.0
-		} else {
-			return false, 0, 0
-		}
-		// 将这个匹配的段中的子分段的时间轴偏移都进行一次计算,推算出到底是怎么样的配比可以得到这样的偏移结论
-		for i, info := range fixedResults[startIndex].MatchWindowInfos {
-
-			perPartLen := info.EndVADIndex - info.StartVADIndex
-			op := OverParts{}
-			// xLen 计算公式见推到公式截图
-			xLen := (info.TimeDiffStartCorrelation*float64(perPartLen) - right3Mean*float64(perPartLen)) / (left3Mean - right3Mean)
-			yLen := float64(perPartLen) - xLen
-
-			op.XLen = xLen
-			op.YLen = yLen
-			op.XMean = left3Mean
-			op.YMean = right3Mean
-
-			fixedResults[startIndex].IsOverParts = true
-			fixedResults[startIndex].MatchWindowInfos[i].OP = op
-		}
-
-		return true, fixedResults[startIndex+1].NewMean, fixedResults[startIndex+1].NewSD
-	}
-
-	return false, 0, 0
-}
-
-// slidingWindowProcessorV2 滑动窗口计算时间轴偏移
-func (s *SubTimelineFixer) slidingWindowProcessorV2(windowInfo *WindowInfo) (*MatchInfo, error) {
-
-	// -------------------------------------------------
-	var bUseSubOrAudioAsBase = true
-	if windowInfo.BaseUnit == nil && windowInfo.BaseAudioFloatList != nil {
-		// 使用 音频 来进行匹配
-		bUseSubOrAudioAsBase = false
-	} else if windowInfo.BaseUnit != nil {
-		// 使用 字幕 来进行匹配
-		bUseSubOrAudioAsBase = true
-	} else {
-		return nil, errors.New("GetOffsetTimeV2 input baseUnit or AudioVad is nil")
-	}
-	// -------------------------------------------------
-	outMatchInfo := MatchInfo{
-		IndexMatchWindowInfoMap: make(map[int]MatchWindowInfo, 0),
-		StartDiffTimeList:       make([]float64, 0),
-		StartDiffTimeMap:        treemap.NewWith(utils.Float64Comparator),
-		StartDiffTimeListEx:     make(stat.Float64Slice, 0),
-	}
-	fixFunc := func(i interface{}) error {
-		inData := i.(InputData)
-		// -------------------------------------------------
-		// 开始匹配
-		// 这里的对白单元,当前的 Base 进行对比,详细示例见图解。Step 2 中橙色的区域
-		fffAligner := NewFFTAligner(DefaultMaxOffsetSeconds, SampleRate)
-		var bok = false
-		var nowBaseStartTime = 0.0
-		var offsetIndex = 0
-		var score = 0.0
-		srcMaxLen := 0
-		// 图解,参考 Step 3
-		if bUseSubOrAudioAsBase == false {
-			// 使用 音频 来进行匹配
-			// 去掉头和尾,具体百分之多少,见 V2_FrontAndEndPerBase
-			audioCutLen := int(float64(len(inData.BaseAudioVADList)) * s.FixerConfig.V2_FrontAndEndPerBase)
-
-			srcMaxLen = windowInfo.SrcWindowLen + inData.OffsetIndex
-			if srcMaxLen >= len(inData.SrcUnit.GetVADFloatSlice()) {
-				srcMaxLen = len(inData.SrcUnit.GetVADFloatSlice()) - 1
-			}
-			offsetIndex, score = fffAligner.Fit(inData.BaseAudioVADList[audioCutLen:len(inData.BaseAudioVADList)-audioCutLen], inData.SrcUnit.GetVADFloatSlice()[inData.OffsetIndex:srcMaxLen])
-			realOffsetIndex := offsetIndex + audioCutLen
-			if realOffsetIndex < 0 {
-				return nil
-			}
-			// offsetIndex 这里得到的是 10ms 为一个单位的 OffsetIndex
-			nowBaseStartTime = vad.GetAudioIndex2Time(realOffsetIndex)
-
-		} else {
-			// 使用 字幕 来进行匹配
-
-			srcMaxLen = inData.OffsetIndex + windowInfo.SrcWindowLen
-			if srcMaxLen >= len(inData.SrcUnit.GetVADFloatSlice()) {
-				srcMaxLen = len(inData.SrcUnit.GetVADFloatSlice()) - 1
-			}
-			offsetIndex, score = fffAligner.Fit(inData.BaseUnit.GetVADFloatSlice(), inData.SrcUnit.GetVADFloatSlice()[inData.OffsetIndex:srcMaxLen])
-			if offsetIndex < 0 {
-				return nil
-			}
-			bok, nowBaseStartTime = inData.BaseUnit.GetIndexTimeNumber(offsetIndex, true)
-			if bok == false {
-				return nil
-			}
-		}
-		// 需要校正的字幕
-		bok, nowSrcStartTime := inData.SrcUnit.GetIndexTimeNumber(inData.OffsetIndex, true)
-		if bok == false {
-			return nil
-		}
-		// 时间差值
-		TimeDiffStartCorrelation := nowBaseStartTime - nowSrcStartTime
-		log_helper.GetLogger().Debugln("------------")
-		log_helper.GetLogger().Debugln("OffsetTime:", fmt.Sprintf("%v", TimeDiffStartCorrelation),
-			"offsetIndex:", offsetIndex,
-			"score:", fmt.Sprintf("%v", score))
-
-		mutexFixV2.Lock()
-		// 这里的未必的顺序的,所以才有 IndexMatchWindowInfoMap 的存在的意义
-		outMatchInfo.IndexMatchWindowInfoMap[inData.Index] = MatchWindowInfo{TimeDiffStartCorrelation: TimeDiffStartCorrelation,
-			StartVADIndex: inData.OffsetIndex,
-			EndVADIndex:   srcMaxLen}
-		outMatchInfo.StartDiffTimeList = append(outMatchInfo.StartDiffTimeList, TimeDiffStartCorrelation)
-		outMatchInfo.StartDiffTimeListEx = append(outMatchInfo.StartDiffTimeListEx, TimeDiffStartCorrelation)
-		outMatchInfo.StartDiffTimeMap.Put(score, windowInfo.MatchedTimes)
-		windowInfo.MatchedTimes++
-		mutexFixV2.Unlock()
-		// -------------------------------------------------
-		return nil
-	}
-	// -------------------------------------------------
-	antPool, err := ants.NewPoolWithFunc(s.FixerConfig.V2_FixThreads, func(inData interface{}) {
-		data := inData.(InputData)
-		defer data.Wg.Done()
-		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.FixerConfig.V2_SubOneUnitProcessTimeOut)*time.Second)
-		defer cancel()
-
-		done := make(chan error, 1)
-		panicChan := make(chan interface{}, 1)
-		go func() {
-			defer func() {
-				if p := recover(); p != nil {
-					panicChan <- p
-				}
-			}()
-
-			done <- fixFunc(inData)
-		}()
-
-		select {
-		case err := <-done:
-			if err != nil {
-				log_helper.GetLogger().Errorln("GetOffsetTimeV2.NewPoolWithFunc done with Error", err.Error())
-			}
-			return
-		case p := <-panicChan:
-			log_helper.GetLogger().Errorln("GetOffsetTimeV2.NewPoolWithFunc got panic", p)
-			return
-		case <-ctx.Done():
-			log_helper.GetLogger().Errorln("GetOffsetTimeV2.NewPoolWithFunc got time out", ctx.Err())
-			return
-		}
-	})
-	if err != nil {
-		return nil, err
-	}
-	defer antPool.Release()
-	// -------------------------------------------------
-	wg := sync.WaitGroup{}
-	index := 0
-	for i := windowInfo.SrcSlideStartIndex; i < windowInfo.SrcSlideStartIndex+windowInfo.SrcSlideLen-1; {
-		wg.Add(1)
-
-		if bUseSubOrAudioAsBase == true {
-			// 使用字幕
-			err = antPool.Invoke(InputData{Index: index, BaseUnit: *windowInfo.BaseUnit, SrcUnit: *windowInfo.SrcUnit, OffsetIndex: i, Wg: &wg})
-		} else {
-			// 使用音频
-			err = antPool.Invoke(InputData{Index: index, BaseAudioVADList: windowInfo.BaseAudioFloatList, SrcUnit: *windowInfo.SrcUnit, OffsetIndex: i, Wg: &wg})
-		}
-
-		if err != nil {
-			log_helper.GetLogger().Errorln("GetOffsetTimeV2 ants.Invoke", err)
-		}
-
-		i += windowInfo.OneStep
-		index++
-	}
-	wg.Wait()
-
-	return &outMatchInfo, nil
-}
-
-func (s *SubTimelineFixer) calcMeanAndSDV2(startDiffTimeList stat.Float64Slice, tmpStartDiffTime []float64) FixResult {
-
-	oldMean := stat.Mean(startDiffTimeList)
-	oldSd := stat.Sd(startDiffTimeList)
-	newMean := MinValue
-	newSd := MinValue
-	per := 1.0
-
-	if len(tmpStartDiffTime) < 3 {
-		return FixResult{
-			0,
-			0,
-			oldMean,
-			oldSd,
-			oldMean,
-			oldSd,
-			per,
-			false,
-			make([]MatchWindowInfo, 0),
-		}
-	}
-
-	// 如果 SD 较大的时候才需要剔除
-	if oldSd > 0.1 {
-		var outliersMap = make(map[float64]int, 0)
-		outliers, _, _ := tukey.Outliers(0.3, tmpStartDiffTime)
-		for _, outlier := range outliers {
-			outliersMap[outlier] = 0
-		}
-		var newStartDiffTimeList = make([]float64, 0)
-		for _, f := range tmpStartDiffTime {
-
-			_, ok := outliersMap[f]
-			if ok == true {
-				continue
-			}
-
-			newStartDiffTimeList = append(newStartDiffTimeList, f)
-		}
-
-		orgLen := startDiffTimeList.Len()
-		startDiffTimeList = make(stat.Float64Slice, 0)
-		for _, f := range newStartDiffTimeList {
-			startDiffTimeList = append(startDiffTimeList, f)
-		}
-		newLen := startDiffTimeList.Len()
-
-		per = float64(newLen) / float64(orgLen)
-
-		newMean = stat.Mean(startDiffTimeList)
-		newSd = stat.Sd(startDiffTimeList)
-	}
-
-	if my_util.IsEqual(newMean, MinValue) == true {
-		newMean = oldMean
-	}
-	if my_util.IsEqual(newSd, MinValue) == true {
-		newSd = oldSd
-	}
-	return FixResult{
-		0,
-		0,
-		oldMean,
-		oldSd,
-		newMean,
-		newSd,
-		per,
-		false,
-		make([]MatchWindowInfo, 0),
-	}
-}
-
-// GetOffsetTimeV3 使用内置的字幕校正外置的字幕时间轴
-func (s *SubTimelineFixer) GetOffsetTimeV3(infoBase, infoSrc, orgFix *subparser.FileInfo, audioVadList []vad.VADInfo) error {
-
-	// -------------------------------------------------
-	var bUseSubOrAudioAsBase = true
-	if infoBase == nil && audioVadList != nil {
-		// 使用 音频 来进行匹配
-		bUseSubOrAudioAsBase = false
-	} else if infoBase != nil {
-		// 使用 字幕 来进行匹配
-		bUseSubOrAudioAsBase = true
-	} else {
-		return errors.New("GetOffsetTimeV2 input baseUnit or AudioVad is nil")
-	}
-	// -------------------------------------------------
-	audioFloatList := vad.GetFloatSlice(audioVadList)
-	baseUnitNew, err := sub_helper.GetVADInfoFeatureFromSubNew(infoBase, 0)
-	if err != nil {
-		return err
-	}
-	srcUnitNew, err := sub_helper.GetVADInfoFeatureFromSubNew(infoSrc, 0)
-	if err != nil {
-		return err
-	}
-	/*
-		上面直接得到所有的输入源,都是完整的一个文件,字幕 or 音频
-		然后根据字幕文件每一个对白进行匹配,这里就使用 V2_FrontAndEndPerSrc 进行字幕的选择,不打算从第一句话开始
-		那么假如 一共有 100 句话,V2_FrontAndEndPerSrc 是 0.2,那么就是从 20 - 80 句话进行匹配计算
-		然后 < 20 的就继承 20 的偏移,> 80 的就继承 80 的偏移即可
-		那么现在就需要从对白中开始遍历
-	*/
-	fffAligner := NewFFTAligner(DefaultMaxOffsetSeconds, SampleRate)
-	err2, done := s.caleOne(0.1, srcUnitNew, fffAligner, baseUnitNew)
-	if done {
-		return err2
-	}
-	err2, done = s.caleOne(0.2, srcUnitNew, fffAligner, baseUnitNew)
-	if done {
-		return err2
-	}
-
-	skipSubLen := int(float64(len(infoSrc.DialoguesFilter)) * s.FixerConfig.V2_FrontAndEndPerSrc)
-
-	sort.Sort(subparser.OneDialogueByStartTime(infoSrc.DialoguesFilter))
-	sort.Sort(subparser.OneDialogueByStartTime(orgFix.DialoguesFilter))
-
-	for i := 0; i < len(infoSrc.DialoguesFilter); i++ {
-
-		// 得到的是真实的时间
-		srcOneDialogueNow := infoSrc.DialoguesFilter[i]
-		srcTimeStartNow, err := my_util.ParseTime(srcOneDialogueNow.StartTime)
-		if err != nil {
-			return err
-		}
-		orgFixOneDialogueNow := orgFix.DialoguesFilter[i]
-		orgFixTimeStartNow, err := my_util.ParseTime(orgFixOneDialogueNow.StartTime)
-		if err != nil {
-			return err
-		}
-
-		println("Index:", i, "srcTimeStartOrg:", srcTimeStartNow.Format("15:04:05.000"),
-			"src-fix-offset:", my_util.Time2SecondNumber(orgFixTimeStartNow)-my_util.Time2SecondNumber(srcTimeStartNow))
-	}
-	println("------------------")
-	for i := skipSubLen; i < len(infoSrc.DialoguesFilter)-skipSubLen-1; i++ {
-
-		var bok = false
-		var nowBaseStartTime = 0.0
-		var offsetIndex = 0
-		var score = 0.0
-		const next = 20
-		const secondRange = 45
-		// -------------------------------------------------
-		srcOneDialogueNow := infoSrc.DialoguesFilter[i]
-		iNext := i + next
-		if iNext >= len(infoSrc.DialoguesFilter)-skipSubLen-1 {
-			iNext = len(infoSrc.DialoguesFilter) - skipSubLen - 1
-		}
-		srcOneDialogueNext := infoSrc.DialoguesFilter[iNext]
-		// 得到的是真实的时间
-		srcTimeStartNow, err := my_util.ParseTime(srcOneDialogueNow.StartTime)
-		if err != nil {
-			return err
-		}
-		srcTimeEndNext, err := my_util.ParseTime(srcOneDialogueNext.EndTime)
-		if err != nil {
-			return err
-		}
-		orgFixOneDialogueNow := orgFix.DialoguesFilter[i]
-		orgFixTimeStartNow, err := my_util.ParseTime(orgFixOneDialogueNow.StartTime)
-		if err != nil {
-			return err
-		}
-		// -------------------------------------------------
-		// 需要转换为 VAD 的对应 Index,需要减去 baseTime,然后根据 10ms 进行计算
-		// -------------------------------------------------
-		// Src
-		srcStartOffsetTimeNow := srcUnitNew.RealTimeToOffsetTime(srcTimeStartNow)
-		srcStartTimeVADIndexNow := int(my_util.Time2SecondNumber(srcStartOffsetTimeNow) * 100)
-
-		srcEndOffsetTimeNext := srcUnitNew.RealTimeToOffsetTime(srcTimeEndNext)
-		srcEndTimeVADIndexNext := int(my_util.Time2SecondNumber(srcEndOffsetTimeNext) * 100)
-		// -------------------------------------------------
-		if bUseSubOrAudioAsBase == false {
-			// 使用 音频 来进行匹配
-
-		} else {
-			// 使用 字幕 来进行匹配
-			// -------------------------------------------------
-			// Base
-			baseStartOffsetTimeNow := baseUnitNew.RealTimeToOffsetTime(srcTimeStartNow).Add(-secondRange * time.Second)
-			baseStartTimeVADIndexNow := int(my_util.Time2SecondNumber(baseStartOffsetTimeNow) * 100)
-
-			baseEndOffsetTimeNext := baseUnitNew.RealTimeToOffsetTime(srcTimeEndNext).Add(secondRange * time.Second)
-			baseEndTimeVADIndexNext := int(my_util.Time2SecondNumber(baseEndOffsetTimeNext) * 100)
-			if baseEndTimeVADIndexNext >= len(baseUnitNew.VADList)-1 {
-				baseEndTimeVADIndexNext = len(baseUnitNew.VADList) - 1
-			}
-			// -------------------------------------------------
-			offsetIndex, score = fffAligner.Fit(baseUnitNew.GetVADFloatSlice()[baseStartTimeVADIndexNow:baseEndTimeVADIndexNext], srcUnitNew.GetVADFloatSlice()[srcStartTimeVADIndexNow:srcEndTimeVADIndexNext])
-			if offsetIndex < 0 {
-				//return nil
-				continue
-			}
-			bok, nowBaseStartTime = baseUnitNew.GetIndexTimeNumber(baseStartTimeVADIndexNow+offsetIndex, true)
-			if bok == false {
-				return nil
-			}
-		}
-		// 需要校正的字幕
-		bok, nowSrcStartTime := srcUnitNew.GetIndexTimeNumber(srcStartTimeVADIndexNow, true)
-		if bok == false {
-			return nil
-		}
-		// 时间差值
-		TimeDiffStartCorrelation := nowBaseStartTime - nowSrcStartTime
-
-		println("Index:", i, "srcTimeStartOrg:", srcTimeStartNow.Format("15:04:05.000"),
-			"OffsetTime:", TimeDiffStartCorrelation, "Score:", score,
-			"ChangedTime:", srcTimeStartNow.Add(time.Duration(TimeDiffStartCorrelation*1000)*time.Millisecond).Format("15:04:05.000"),
-			"OrgFixTime:", orgFixTimeStartNow.Format("15:04:05.000"),
-			"src-fix-offset:", my_util.Time2SecondNumber(orgFixTimeStartNow)-my_util.Time2SecondNumber(srcTimeStartNow))
-	}
-
-	//if baseStartTimeVADIndexNow > 3600000 {
-	//	baseStartTimeVADIndexNow = 0
-	//}
-
-	println(len(audioFloatList))
-	println(len(baseUnitNew.VADList))
-	println(len(srcUnitNew.VADList))
-
-	return nil
-}
-
-func (s *SubTimelineFixer) caleOne(cutPer float64, srcUnitNew *sub_helper.SubUnit, fffAligner *FFTAligner, baseUnitNew *sub_helper.SubUnit) (error, bool) {
-	srcVADLen := len(srcUnitNew.VADList)
-	srcCutStartIndex := int(float64(srcVADLen) * cutPer)
-	offsetIndexAll, scoreAll := fffAligner.Fit(baseUnitNew.GetVADFloatSlice(), srcUnitNew.GetVADFloatSlice()[srcCutStartIndex:srcVADLen-srcCutStartIndex])
-	bok, nowBaseStartTime := baseUnitNew.GetIndexTimeNumber(0+offsetIndexAll, true)
-	if bok == false {
-		return nil, true
-	}
-	// 需要校正的字幕
-	bok, nowSrcStartTime := srcUnitNew.GetIndexTimeNumber(srcCutStartIndex, true)
-	if bok == false {
-		return nil, true
-	}
-	// 时间差值
-	TimeDiffStartCorrelation := nowBaseStartTime - nowSrcStartTime
-	bok, srcIndexCutTime := srcUnitNew.GetIndexTime(srcCutStartIndex, true)
-	if bok == false {
-		return nil, true
-	}
-	println(srcIndexCutTime.Format("15:04:05.000"), TimeDiffStartCorrelation, scoreAll)
-	return nil, false
-}
-
-const FixMask = "-fix"
-const MinValue = -9999.0
-
-var mutexFixV2 sync.Mutex
+//
+//import (
+//	"errors"
+//	"fmt"
+//	"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/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"
+//	"github.com/go-echarts/go-echarts/v2/opts"
+//	"github.com/grd/stat"
+//	"github.com/james-bowman/nlp/measures/pairwise"
+//	"github.com/mndrix/tukey"
+//	"github.com/panjf2000/ants/v2"
+//	"golang.org/x/net/context"
+//	"gonum.org/v1/gonum/mat"
+//	"math"
+//	"os"
+//	"sort"
+//	"strings"
+//	"sync"
+//	"time"
+//)
+//
+//type SubTimelineFixer struct {
+//	FixerConfig sub_timeline_fiexer.SubTimelineFixerConfig
+//}
+//
+//func NewSubTimelineFixer(fixerConfig sub_timeline_fiexer.SubTimelineFixerConfig) *SubTimelineFixer {
+//	return &SubTimelineFixer{
+//		FixerConfig: fixerConfig,
+//	}
+//}
+//
+//// StopWordCounter 停止词统计
+//func (s *SubTimelineFixer) StopWordCounter(inString string, per int) []string {
+//	statisticTimes := make(map[string]int)
+//	wordsLength := strings.Fields(inString)
+//
+//	for counts, word := range wordsLength {
+//		// 判断key是否存在,这个word是字符串,这个counts是统计的word的次数。
+//		word, ok := statisticTimes[word]
+//		if ok {
+//			word = word
+//			statisticTimes[wordsLength[counts]] = statisticTimes[wordsLength[counts]] + 1
+//		} else {
+//			statisticTimes[wordsLength[counts]] = 1
+//		}
+//	}
+//
+//	stopWords := make([]string, 0)
+//	mapByValue := sortMapByValue(statisticTimes)
+//
+//	breakIndex := len(mapByValue) * per / 100
+//	for index, wordInfo := range mapByValue {
+//		if index > breakIndex {
+//			break
+//		}
+//		stopWords = append(stopWords, wordInfo.Name)
+//	}
+//
+//	return stopWords
+//}
+//
+//// FixSubTimelineOneOffsetTime 校正整个字幕文件的时间轴,适用于一个偏移值的情况
+//func (s *SubTimelineFixer) FixSubTimelineOneOffsetTime(infoSrc *subparser.FileInfo, inOffsetTime float64, desSaveSubFileFullPath string) (string, error) {
+//
+//	/*
+//		从解析的实例中,正常来说是可以匹配出所有的 Dialogue 对话的 Start 和 End time 的信息
+//		然后找到对应的字幕的文件,进行文件内容的替换来做时间轴的校正
+//	*/
+//	// 偏移时间
+//	offsetTime := time.Duration(inOffsetTime*1000) * time.Millisecond
+//	fixContent := infoSrc.Content
+//	/*
+//		这里进行时间转字符串的时候有一点比较特殊
+//		正常来说输出的格式是类似 15:04:05.00
+//		那么有个问题,字幕的时间格式是 0:00:12.00, 小时,是个数,除非有跨度到 20 小时的视频,不然小时就应该是个数
+//		这就需要一个额外的函数去处理这些情况
+//	*/
+//	timeFormat := infoSrc.GetTimeFormat()
+//	for _, srcOneDialogue := range infoSrc.Dialogues {
+//
+//		timeStart, err := my_util.ParseTime(srcOneDialogue.StartTime)
+//		if err != nil {
+//			return "", err
+//		}
+//		timeEnd, err := my_util.ParseTime(srcOneDialogue.EndTime)
+//		if err != nil {
+//			return "", err
+//		}
+//
+//		fixTimeStart := timeStart.Add(offsetTime)
+//		fixTimeEnd := timeEnd.Add(offsetTime)
+//
+//		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.StartTime, my_util.Time2SubTimeString(fixTimeStart, timeFormat))
+//		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.EndTime, my_util.Time2SubTimeString(fixTimeEnd, timeFormat))
+//	}
+//
+//	dstFile, err := os.Create(desSaveSubFileFullPath)
+//	if err != nil {
+//		return "", err
+//	}
+//	defer func() {
+//		_ = dstFile.Close()
+//	}()
+//	_, err = dstFile.WriteString(fixContent)
+//	if err != nil {
+//		return "", err
+//	}
+//	return fixContent, nil
+//}
+//
+//// FixSubTimelineByFixResults V2 专用的时间校正函数
+//func (s SubTimelineFixer) FixSubTimelineByFixResults(infoSrc *subparser.FileInfo, srcUnitNew *sub_helper.SubUnit, fixedResults []FixResult, desSaveSubFileFullPath string) (string, error) {
+//
+//	startTime := srcUnitNew.GetStartTime(true)
+//	startTimeBaseDouble := my_util.Time2SecondNumber(startTime)
+//	/*
+//		这里拿到的 fixedResults ,是进行过 V2_FrontAndEndPerSrc 头尾去除
+//		那么调整目标字幕的时候,需要考虑截取掉的部分也要算进去
+//	*/
+//	/*
+//		从解析的实例中,正常来说是可以匹配出所有的 Dialogue 对话的 Start 和 End time 的信息
+//		然后找到对应的字幕的文件,进行文件内容的替换来做时间轴的校正
+//	*/
+//	fixContent := infoSrc.Content
+//	/*
+//		这里进行时间转字符串的时候有一点比较特殊
+//		正常来说输出的格式是类似 15:04:05.00
+//		那么有个问题,字幕的时间格式是 0:00:12.00, 小时,是个数,除非有跨度到 20 小时的视频,不然小时就应该是个数
+//		这就需要一个额外的函数去处理这些情况
+//	*/
+//	timeFormat := infoSrc.GetTimeFormat()
+//	cacheIndex := 0
+//	/*
+//		这里的理想情况是 Dialogues,每一句话都是递增的对白时间
+//		但是实际情况可能是,前面几个对白是特效、音乐的备注,那么他们的跨度可以很大
+//		然后才到正常的对话对白,这样就出现不是递增的时间对白情况
+//		那么就需要对 Dialogues 进行排序,然后再进行处理
+//	*/
+//	sort.Sort(subparser.OneDialogueByStartTime(infoSrc.Dialogues))
+//
+//	for index, srcOneDialogue := range infoSrc.Dialogues {
+//
+//		timeStart, err := my_util.ParseTime(srcOneDialogue.StartTime)
+//		if err != nil {
+//			return "", err
+//		}
+//		timeEnd, err := my_util.ParseTime(srcOneDialogue.EndTime)
+//		if err != nil {
+//			return "", err
+//		}
+//
+//		inOffsetTime := 0.0
+//		orgStartTimeDouble := my_util.Time2SecondNumber(timeStart)
+//		for cacheIndex < len(fixedResults) {
+//
+//			inRange, nowOffsetTime := fixedResults[cacheIndex].InRange(startTimeBaseDouble, orgStartTimeDouble)
+//			if inRange == false {
+//				// 大于当前的范围,递增一个区间进行再次的判断
+//				// 但是需要确定的是,递增出来的这个区间的 Index 是有效的,如果是无效的,那么就使用最后一个区间的偏移时间
+//				cacheIndex++
+//				continue
+//			} else {
+//				inOffsetTime = nowOffsetTime
+//				break
+//			}
+//		}
+//		if cacheIndex >= len(fixedResults) {
+//			// 下一个区间的 Index 已经越界了,那么就使用最后一个区间的偏移
+//			inOffsetTime = fixedResults[len(fixedResults)-1].NewMean
+//		}
+//		// 偏移时间
+//		println(index, inOffsetTime)
+//		offsetTime := time.Duration(inOffsetTime*1000) * time.Millisecond
+//		fixTimeStart := timeStart.Add(offsetTime)
+//		fixTimeEnd := timeEnd.Add(offsetTime)
+//
+//		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.StartTime, "Index:"+fmt.Sprintf("%d-", index)+my_util.Time2SubTimeString(fixTimeStart, timeFormat))
+//		fixContent = strings.ReplaceAll(fixContent, srcOneDialogue.EndTime, my_util.Time2SubTimeString(fixTimeEnd, timeFormat))
+//	}
+//
+//	dstFile, err := os.Create(desSaveSubFileFullPath)
+//	if err != nil {
+//		return "", err
+//	}
+//	defer func() {
+//		_ = dstFile.Close()
+//	}()
+//	_, err = dstFile.WriteString(fixContent)
+//	if err != nil {
+//		return "", err
+//	}
+//	return fixContent, nil
+//}
+//
+///*
+//	对于 V1 版本的字幕时间轴校正来说,是有特殊的前置要求的
+//	1. 视频要有英文字幕
+//	2. 外置的字幕必须是中文的双语字幕(简英、繁英)
+//*/
+//// GetOffsetTimeV1 暂时只支持英文的基准字幕,源字幕必须是双语中英字幕
+//func (s *SubTimelineFixer) GetOffsetTimeV1(infoBase, infoSrc *subparser.FileInfo, staticLineFileSavePath string, debugInfoFileSavePath string) (bool, float64, float64, error) {
+//
+//	var debugInfos = make([]string, 0)
+//	// 构建基准语料库,目前阶段只需要考虑是 En 的就行了
+//	var baseCorpus = make([]string, 0)
+//	var baseDialogueFilterMap = make(map[int]int, 0)
+//	/*
+//		这里原来的写法是所有的 base 的都放进去匹配,这样会带来一些不必要的对白
+//		需要剔除空白。那么就需要建立一个转换的字典
+//	*/
+//	for index, oneDialogueEx := range infoBase.DialoguesFilterEx {
+//		if oneDialogueEx.EnLine == "" {
+//			continue
+//		}
+//		baseCorpus = append(baseCorpus, oneDialogueEx.EnLine)
+//		baseDialogueFilterMap[len(baseCorpus)-1] = index
+//	}
+//	// 初始化
+//	pipLine, tfidf, err := NewTFIDF(baseCorpus)
+//	if err != nil {
+//		return false, 0, 0, err
+//	}
+//
+//	/*
+//		确认两个字幕间的偏移,暂定的方案是两边都连续匹配上 5 个索引,再抽取一个对话的时间进行修正计算
+//	*/
+//	maxCompareDialogue := s.FixerConfig.V1_MaxCompareDialogue
+//	// 基线的长度
+//	_, docsLength := tfidf.Dims()
+//	var matchIndexList = make([]MatchIndex, 0)
+//	sc := NewSubCompare(maxCompareDialogue)
+//	// 开始比较相似度,默认认为是 Ch_en 就行了
+//	for srcIndex := 0; srcIndex < len(infoSrc.DialoguesFilterEx); {
+//
+//		srcOneDialogueEx := infoSrc.DialoguesFilterEx[srcIndex]
+//		// 这里只考虑 英文 的语言
+//		if srcOneDialogueEx.EnLine == "" {
+//			srcIndex++
+//			continue
+//		}
+//		// run the query through the same pipeline that was fitted to the corpus and
+//		// to project it into the same dimensional space
+//		queryVector, err := pipLine.Transform(srcOneDialogueEx.EnLine)
+//		if err != nil {
+//			return false, 0, 0, err
+//		}
+//		// iterate over document feature vectors (columns) in the LSI matrix and compare
+//		// with the query vector for similarity.  Similarity is determined by the difference
+//		// between the angles of the vectors known as the cosine similarity
+//		highestSimilarity := -1.0
+//		// 匹配上的基准的索引
+//		var baseIndex int
+//		// 这里理论上需要把所有的基线遍历一次,但是,一般来说,两个字幕不可能差距在 50 行
+//		// 这样的好处是有助于提高搜索的性能
+//		// 那么就以当前的 src 的位置,向前、向后各 50 来遍历
+//		nowMaxScanLength := srcIndex + 50
+//		nowMinScanLength := srcIndex - 50
+//		if nowMinScanLength < 0 {
+//			nowMinScanLength = 0
+//		}
+//		if nowMaxScanLength > docsLength {
+//			nowMaxScanLength = docsLength
+//		}
+//		for i := nowMinScanLength; i < nowMaxScanLength; i++ {
+//			similarity := pairwise.CosineSimilarity(queryVector.(mat.ColViewer).ColView(0), tfidf.(mat.ColViewer).ColView(i))
+//			if similarity > highestSimilarity {
+//				baseIndex = i
+//				highestSimilarity = similarity
+//			}
+//		}
+//
+//		startBaseIndex, startSrcIndex := sc.GetStartIndex()
+//		if sc.Add(baseIndex, srcIndex) == false {
+//			sc.Clear()
+//			srcIndex = startSrcIndex + 1
+//			continue
+//			//sc.Add(baseIndex, srcIndex)
+//		}
+//		if sc.Check() == false {
+//			srcIndex++
+//			continue
+//		} else {
+//			sc.Clear()
+//		}
+//
+//		matchIndexList = append(matchIndexList, MatchIndex{
+//			BaseNowIndex: startBaseIndex,
+//			//BaseNowIndex: baseDialogueFilterMap[startBaseIndex],
+//			SrcNowIndex: startSrcIndex,
+//			Similarity:  highestSimilarity,
+//		})
+//
+//		//println(fmt.Sprintf("Similarity: %f Base[%d] %s-%s '%s' <--> Src[%d] %s-%s '%s'",
+//		//	highestSimilarity,
+//		//	baseIndex, infoBase.DialoguesFilterEx[baseIndex].relativelyStartTime, infoBase.DialoguesFilterEx[baseIndex].relativelyEndTime, baseCorpus[baseIndex],
+//		//	srcIndex, srcOneDialogueEx.relativelyStartTime, srcOneDialogueEx.relativelyEndTime, srcOneDialogueEx.EnLine))
+//
+//		srcIndex++
+//	}
+//
+//	var startDiffTimeLineData = make([]opts.LineData, 0)
+//	var endDiffTimeLineData = make([]opts.LineData, 0)
+//	var tmpStartDiffTime = make([]float64, 0)
+//	var tmpEndDiffTime = make([]float64, 0)
+//	var startDiffTimeList = make(stat.Float64Slice, 0)
+//	var endDiffTimeList = make(stat.Float64Slice, 0)
+//	var xAxis = make([]string, 0)
+//	// 上面找出了连续匹配 maxCompareDialogue:N 次的字幕语句块
+//	// 求出平均时间偏移
+//	for mIndex, matchIndexItem := range matchIndexList {
+//
+//		for i := 0; i < maxCompareDialogue; i++ {
+//			// 这里会统计连续的这 5 句话的时间差
+//			//tmpBaseIndex := matchIndexItem.BaseNowIndex + i
+//			tmpBaseIndex := baseDialogueFilterMap[matchIndexItem.BaseNowIndex+i]
+//			tmpSrcIndex := matchIndexItem.SrcNowIndex + i
+//
+//			baseTimeStart, err := my_util.ParseTime(infoBase.DialoguesFilterEx[tmpBaseIndex].StartTime)
+//			if err != nil {
+//				return false, 0, 0, err
+//			}
+//			baseTimeEnd, err := my_util.ParseTime(infoBase.DialoguesFilterEx[tmpBaseIndex].EndTime)
+//			if err != nil {
+//				return false, 0, 0, err
+//			}
+//			srtTimeStart, err := my_util.ParseTime(infoSrc.DialoguesFilterEx[tmpSrcIndex].StartTime)
+//			if err != nil {
+//				return false, 0, 0, err
+//			}
+//			srtTimeEnd, err := my_util.ParseTime(infoSrc.DialoguesFilterEx[tmpSrcIndex].EndTime)
+//			if err != nil {
+//				return false, 0, 0, err
+//			}
+//
+//			TimeDiffStart := baseTimeStart.Sub(srtTimeStart)
+//			TimeDiffEnd := baseTimeEnd.Sub(srtTimeEnd)
+//
+//			startDiffTimeLineData = append(startDiffTimeLineData, opts.LineData{Value: TimeDiffStart.Seconds()})
+//			endDiffTimeLineData = append(endDiffTimeLineData, opts.LineData{Value: TimeDiffEnd.Seconds()})
+//
+//			tmpStartDiffTime = append(tmpStartDiffTime, TimeDiffStart.Seconds())
+//			tmpEndDiffTime = append(tmpEndDiffTime, TimeDiffEnd.Seconds())
+//
+//			startDiffTimeList = append(startDiffTimeList, TimeDiffStart.Seconds())
+//			endDiffTimeList = append(endDiffTimeList, TimeDiffEnd.Seconds())
+//
+//			xAxis = append(xAxis, fmt.Sprintf("%d_%d", mIndex, i))
+//
+//			debugInfos = append(debugInfos, "bs "+infoBase.DialoguesFilterEx[tmpBaseIndex].StartTime+" <-> "+infoBase.DialoguesFilterEx[tmpBaseIndex].EndTime)
+//			debugInfos = append(debugInfos, "sc "+infoSrc.DialoguesFilterEx[tmpSrcIndex].StartTime+" <-> "+infoSrc.DialoguesFilterEx[tmpSrcIndex].EndTime)
+//			debugInfos = append(debugInfos, "StartDiffTime: "+fmt.Sprintf("%f", TimeDiffStart.Seconds()))
+//			//println(fmt.Sprintf("Diff Start-End: %s - %s Base[%d] %s-%s '%s' <--> Src[%d] %s-%s '%s'",
+//			//	TimeDiffStart, TimeDiffEnd,
+//			//	tmpBaseIndex, infoBase.DialoguesFilterEx[tmpBaseIndex].relativelyStartTime, infoBase.DialoguesFilterEx[tmpBaseIndex].relativelyEndTime, infoBase.DialoguesFilterEx[tmpBaseIndex].EnLine,
+//			//	tmpSrcIndex, infoSrc.DialoguesFilterEx[tmpSrcIndex].relativelyStartTime, infoSrc.DialoguesFilterEx[tmpSrcIndex].relativelyEndTime, infoSrc.DialoguesFilterEx[tmpSrcIndex].EnLine))
+//		}
+//		debugInfos = append(debugInfos, "---------------------------------------------")
+//		//println("---------------------------------------------")
+//	}
+//
+//	oldMean := stat.Mean(startDiffTimeList)
+//	oldSd := stat.Sd(startDiffTimeList)
+//	newMean := -1.0
+//	newSd := -1.0
+//	per := 1.0
+//
+//	// 如果 SD 较大的时候才需要剔除
+//	if oldSd > 0.1 {
+//		var outliersMap = make(map[float64]int, 0)
+//		outliers, _, _ := tukey.Outliers(0.3, tmpStartDiffTime)
+//		for _, outlier := range outliers {
+//			outliersMap[outlier] = 0
+//		}
+//		var newStartDiffTimeList = make([]float64, 0)
+//		for _, f := range tmpStartDiffTime {
+//
+//			_, ok := outliersMap[f]
+//			if ok == true {
+//				continue
+//			}
+//
+//			newStartDiffTimeList = append(newStartDiffTimeList, f)
+//		}
+//
+//		orgLen := startDiffTimeList.Len()
+//		startDiffTimeList = make(stat.Float64Slice, 0)
+//		for _, f := range newStartDiffTimeList {
+//			startDiffTimeList = append(startDiffTimeList, f)
+//		}
+//		newLen := startDiffTimeList.Len()
+//
+//		per = float64(newLen) / float64(orgLen)
+//
+//		newMean = stat.Mean(startDiffTimeList)
+//		newSd = stat.Sd(startDiffTimeList)
+//	}
+//
+//	if newMean == -1.0 {
+//		newMean = oldMean
+//	}
+//	if newSd == -1.0 {
+//		newSd = oldSd
+//	}
+//
+//	// 不为空的时候,生成调试文件
+//	if staticLineFileSavePath != "" {
+//		//staticLineFileSavePath = "bar.html"
+//		err = SaveStaticLineV1(staticLineFileSavePath, infoBase.Name, infoSrc.Name,
+//			per, oldMean, oldSd, newMean, newSd, xAxis,
+//			startDiffTimeLineData, endDiffTimeLineData)
+//		if err != nil {
+//			return false, 0, 0, err
+//		}
+//	}
+//
+//	// 跳过的逻辑是 mean 是 0 ,那么现在如果判断有问题,缓存的调试文件继续生成,然后强制返回 0 来跳过后续的逻辑
+//	// 这里需要考虑,找到的连续 5 句话匹配的有多少句,占比整体所有的 Dialogue 是多少,太低也需要跳过
+//	matchIndexLineCount := len(matchIndexList) * maxCompareDialogue
+//	//perMatch := float64(matchIndexLineCount) / float64(len(infoSrc.DialoguesFilterEx))
+//	perMatch := float64(matchIndexLineCount) / float64(len(baseCorpus))
+//	if perMatch < s.FixerConfig.V1_MinMatchedPercent {
+//		tmpContent := infoSrc.Name + fmt.Sprintf(" Sequence match %d dialogues (< %f%%), Skip,", s.FixerConfig.V1_MaxCompareDialogue, s.FixerConfig.V1_MinMatchedPercent*100) + fmt.Sprintf(" %f%% ", perMatch*100)
+//
+//		debugInfos = append(debugInfos, tmpContent)
+//
+//		log_helper.GetLogger().Infoln(tmpContent)
+//	} else {
+//		tmpContent := infoSrc.Name + fmt.Sprintf(" Sequence match %d dialogues,", s.FixerConfig.V1_MaxCompareDialogue) + fmt.Sprintf(" %f%% ", perMatch*100)
+//
+//		debugInfos = append(debugInfos, tmpContent)
+//
+//		log_helper.GetLogger().Infoln(tmpContent)
+//	}
+//
+//	// 输出调试的匹配时间轴信息的列表
+//	if debugInfoFileSavePath != "" {
+//		err = my_util.WriteStrings2File(debugInfoFileSavePath, debugInfos)
+//		if err != nil {
+//			return false, 0, 0, err
+//		}
+//	}
+//	// 虽然有条件判断是认为有问题的,但是返回值还是要填写除去的
+//	if perMatch < s.FixerConfig.V1_MinMatchedPercent {
+//		return false, newMean, newSd, nil
+//	}
+//
+//	return true, newMean, newSd, nil
+//}
+//
+//// GetOffsetTimeV2 使用内置的字幕校正外置的字幕时间轴
+//func (s *SubTimelineFixer) GetOffsetTimeV2(baseUnit, srcUnit *sub_helper.SubUnit, audioVadList []vad.VADInfo) (bool, []FixResult, error) {
+//
+//	// -------------------------------------------------
+//	/*
+//		开始针对对白单元进行匹配
+//		下面的逻辑需要参考 FFT识别流程.jpg 这个图示来理解
+//		实际实现的时候,会在上述 srcUnit 上,做一个滑动窗口来做匹配,80% 是窗口,20% 用于移动
+//		步长固定在 10 步
+//	*/
+//	audioFloatList := vad.GetFloatSlice(audioVadList)
+//
+//	srcVADLen := len(srcUnit.VADList)
+//	// 滑动窗口的长度
+//	srcWindowLen := int(float64(srcVADLen) * s.FixerConfig.V2_WindowMatchPer)
+//	// 划分为 4 个区域,每一个部分的长度
+//	const parts = 10
+//	perPartLen := srcVADLen / parts
+//	matchedInfos := make([]MatchInfo, 0)
+//
+//	subVADBlockInfos := make([]SubVADBlockInfo, 0)
+//	for i := 0; i < parts; i++ {
+//
+//		// 滑动窗体的起始 Index
+//		srcSlideStartIndex := i * perPartLen
+//		// 滑动的距离
+//		srcSlideLen := perPartLen
+//		// 一步的长度
+//		oneStep := perPartLen / s.FixerConfig.V2_CompareParts
+//		if srcSlideLen <= 0 {
+//			srcSlideLen = 1
+//		}
+//		if oneStep <= 0 {
+//			oneStep = 1
+//		}
+//		// -------------------------------------------------
+//		windowInfo := WindowInfo{
+//			BaseAudioFloatList: audioFloatList,
+//			BaseUnit:           baseUnit,
+//			SrcUnit:            srcUnit,
+//			MatchedTimes:       0,
+//			SrcWindowLen:       srcWindowLen,
+//			SrcSlideStartIndex: srcSlideStartIndex,
+//			SrcSlideLen:        srcSlideLen,
+//			OneStep:            oneStep,
+//		}
+//		subVADBlockInfos = append(subVADBlockInfos, SubVADBlockInfo{
+//			Index:      i,
+//			StartIndex: srcSlideStartIndex,
+//			EndIndex:   srcSlideStartIndex + srcSlideLen,
+//		})
+//		// 实际 FFT 的匹配逻辑函数
+//		// 时间轴差值数组
+//		matchInfo, err := s.slidingWindowProcessorV2(&windowInfo)
+//		if err != nil {
+//			return false, nil, err
+//		}
+//
+//		matchedInfos = append(matchedInfos, *matchInfo)
+//	}
+//
+//	fixedResults := make([]FixResult, 0)
+//	sdLessCount := 0
+//	// 这里的是 matchedInfos 是顺序的
+//	for index, matchInfo := range matchedInfos {
+//
+//		log_helper.GetLogger().Infoln(index, "------------------------------------")
+//		outCorrelationFixResult := s.calcMeanAndSDV2(matchInfo.StartDiffTimeListEx, matchInfo.StartDiffTimeList)
+//		log_helper.GetLogger().Infoln(fmt.Sprintf("FFTAligner Old Mean: %v SD: %f Per: %v", outCorrelationFixResult.OldMean, outCorrelationFixResult.OldSD, outCorrelationFixResult.Per))
+//		log_helper.GetLogger().Infoln(fmt.Sprintf("FFTAligner New Mean: %v SD: %f Per: %v", outCorrelationFixResult.NewMean, outCorrelationFixResult.NewSD, outCorrelationFixResult.Per))
+//
+//		value, indexMax := matchInfo.StartDiffTimeMap.Max()
+//		log_helper.GetLogger().Infoln("FFTAligner Max score:", fmt.Sprintf("%v", value.(float64)), "Time:", fmt.Sprintf("%v", matchInfo.StartDiffTimeList[indexMax.(int)]))
+//
+//		outCorrelationFixResult.StartVADIndex = index * perPartLen
+//		outCorrelationFixResult.EndVADIndex = index*perPartLen + perPartLen
+//		fixedResults = append(fixedResults, outCorrelationFixResult)
+//
+//		if outCorrelationFixResult.NewSD < 0.1 {
+//			sdLessCount++
+//		}
+//	}
+//
+//	// 如果 0.1 sd 以下的占比低于 70% 那么就认为字幕匹配失败
+//	perLess := float64(sdLessCount) / float64(len(matchedInfos))
+//	if perLess < 0.7 {
+//		return false, nil, nil
+//	}
+//
+//	// matchedInfos 与 fixedResults 是对等的关系,fixedResults 中是计算过 Mean 的值,而 matchedInfos 有原始的值
+//	for i, info := range matchedInfos {
+//		for j := 0; j < len(info.IndexMatchWindowInfoMap); j++ {
+//
+//			value, bFound := info.IndexMatchWindowInfoMap[j]
+//			if bFound == false {
+//				continue
+//			}
+//
+//			fixedResults[i].MatchWindowInfos = append(fixedResults[i].MatchWindowInfos, value)
+//		}
+//	}
+//	/*
+//		如果 outCorrelationFixResult 的 SD > 0.1,那么大概率这个时间轴的值匹配的有问题,需要向左或者向右找一个值进行继承
+//		-4 0.001
+//		-4 0.001
+//		-4 0.001
+//		-200 0.1
+//		-4 0.001
+//		比如这种情况,那么就需要向左找到 -4 去继承。
+//		具体的实现:
+//			找到一个 SD > 0.1 的项目,那么就需要从左边和右边同时对比
+//			首先是他们的差值要在 0.3s (绝对值)以内,优先往左边找,如果绝对值成立则判断 SD (SD 必须 < 0.1)
+//			如果只是 SD 不成立,那么就继续往左,继续判断差值和 SD。
+//			如果都找不到合适的,就要回到”起点“,从右开始找,逻辑一样
+//			直到没有找到合适的信息,就报错
+//	*/
+//	// 进行细节的修正
+//	for index, fixedResult := range fixedResults {
+//		// SD 大于 0.1 或者是 当前的 NewMean 与上一个点的 NewMean 差值大于 0.3
+//		if fixedResult.NewSD >= 0.1 || (index > 1 && math.Abs(fixedResult.NewMean-fixedResults[index-1].NewMean) > 0.3) {
+//			bok, newMean, newSD := s.fixOnePartV2(index, fixedResults)
+//			if bok == true {
+//				fixedResults[index].NewMean = newMean
+//				fixedResults[index].NewSD = newSD
+//			}
+//		}
+//	}
+//
+//	return true, fixedResults, nil
+//}
+//
+//// fixOnePartV2 轻微地跳动可以根据左或者右去微调
+//func (s SubTimelineFixer) fixOnePartV2(startIndex int, fixedResults []FixResult) (bool, float64, float64) {
+//
+//	/*
+//		找到这样情况的进行修正
+//	*/
+//	// 先往左
+//	if startIndex-1 >= 0 {
+//		// 说明至少可以往左
+//		// 如果左边的这个值,与当前值超过了 0.3 的绝对差值,那么是不适合的,就需要往右找
+//		if math.Abs(fixedResults[startIndex-1].NewMean-fixedResults[startIndex].NewMean) < 0.3 {
+//			// 差值在接受的范围内,那么就使用这个左边的值去校正当前的值
+//			return true, fixedResults[startIndex-1].NewMean, fixedResults[startIndex-1].NewSD
+//		}
+//	}
+//
+//	// 如果上面的理想情况都没有进去,那么就是这个差值很大
+//	if fixedResults[startIndex].NewSD > 1 {
+//		// SD 比较大,可能当前的位置是值是错误的,那么直接就使用左边的值
+//		/*
+//			-6.3	0.06
+//			-146.85	243.83
+//		*/
+//		if startIndex-1 >= 0 {
+//			return true, fixedResults[startIndex-1].NewMean, fixedResults[startIndex-1].NewSD
+//		}
+//	} else {
+//		// SD 不是很大,可能就是正常的字幕分段的时间轴偏移的 越接处 !
+//		// 那么需要取,越接处,前三和后三,进行均值计算
+//		/*
+//			-6.21
+//			-6.22
+//			-6.29	0.06
+//
+//			-7.13	0.14		越接处
+//
+//			-7.32
+//			-7.31
+//			-7.44
+//		*/
+//		left3Mean := 0.0
+//		right3Mean := 0.0
+//		// 向左,三个或者三个位置
+//		if startIndex-3 >= 0 {
+//			left3Mean = float64(fixedResults[startIndex-1].NewMean+fixedResults[startIndex-2].NewMean+fixedResults[startIndex-3].NewMean) / 3.0
+//		} else if startIndex-2 >= 0 {
+//			left3Mean = float64(fixedResults[startIndex-1].NewMean+fixedResults[startIndex-2].NewMean) / 2.0
+//		} else {
+//			return false, 0, 0
+//		}
+//		// 向右,三个或者三个位置
+//		if startIndex+3 >= 0 {
+//			right3Mean = float64(fixedResults[startIndex+1].NewMean+fixedResults[startIndex+2].NewMean+fixedResults[startIndex+3].NewMean) / 3.0
+//		} else if startIndex+2 >= 0 {
+//			right3Mean = float64(fixedResults[startIndex+1].NewMean+fixedResults[startIndex+2].NewMean) / 2.0
+//		} else {
+//			return false, 0, 0
+//		}
+//		// 将这个匹配的段中的子分段的时间轴偏移都进行一次计算,推算出到底是怎么样的配比可以得到这样的偏移结论
+//		for i, info := range fixedResults[startIndex].MatchWindowInfos {
+//
+//			perPartLen := info.EndVADIndex - info.StartVADIndex
+//			op := OverParts{}
+//			// xLen 计算公式见推到公式截图
+//			xLen := (info.TimeDiffStartCorrelation*float64(perPartLen) - right3Mean*float64(perPartLen)) / (left3Mean - right3Mean)
+//			yLen := float64(perPartLen) - xLen
+//
+//			op.XLen = xLen
+//			op.YLen = yLen
+//			op.XMean = left3Mean
+//			op.YMean = right3Mean
+//
+//			fixedResults[startIndex].IsOverParts = true
+//			fixedResults[startIndex].MatchWindowInfos[i].OP = op
+//		}
+//
+//		return true, fixedResults[startIndex+1].NewMean, fixedResults[startIndex+1].NewSD
+//	}
+//
+//	return false, 0, 0
+//}
+//
+//// slidingWindowProcessorV2 滑动窗口计算时间轴偏移
+//func (s *SubTimelineFixer) slidingWindowProcessorV2(windowInfo *WindowInfo) (*MatchInfo, error) {
+//
+//	// -------------------------------------------------
+//	var bUseSubOrAudioAsBase = true
+//	if windowInfo.BaseUnit == nil && windowInfo.BaseAudioFloatList != nil {
+//		// 使用 音频 来进行匹配
+//		bUseSubOrAudioAsBase = false
+//	} else if windowInfo.BaseUnit != nil {
+//		// 使用 字幕 来进行匹配
+//		bUseSubOrAudioAsBase = true
+//	} else {
+//		return nil, errors.New("GetOffsetTimeV2 input baseUnit or AudioVad is nil")
+//	}
+//	// -------------------------------------------------
+//	outMatchInfo := MatchInfo{
+//		IndexMatchWindowInfoMap: make(map[int]MatchWindowInfo, 0),
+//		StartDiffTimeList:       make([]float64, 0),
+//		StartDiffTimeMap:        treemap.NewWith(utils.Float64Comparator),
+//		StartDiffTimeListEx:     make(stat.Float64Slice, 0),
+//	}
+//	fixFunc := func(i interface{}) error {
+//		inData := i.(InputData)
+//		// -------------------------------------------------
+//		// 开始匹配
+//		// 这里的对白单元,当前的 Base 进行对比,详细示例见图解。Step 2 中橙色的区域
+//		fffAligner := NewFFTAligner(DefaultMaxOffsetSeconds, SampleRate)
+//		var bok = false
+//		var nowBaseStartTime = 0.0
+//		var offsetIndex = 0
+//		var score = 0.0
+//		srcMaxLen := 0
+//		// 图解,参考 Step 3
+//		if bUseSubOrAudioAsBase == false {
+//			// 使用 音频 来进行匹配
+//			// 去掉头和尾,具体百分之多少,见 V2_FrontAndEndPerBase
+//			audioCutLen := int(float64(len(inData.BaseAudioVADList)) * s.FixerConfig.V2_FrontAndEndPerBase)
+//
+//			srcMaxLen = windowInfo.SrcWindowLen + inData.OffsetIndex
+//			if srcMaxLen >= len(inData.SrcUnit.GetVADFloatSlice()) {
+//				srcMaxLen = len(inData.SrcUnit.GetVADFloatSlice()) - 1
+//			}
+//			offsetIndex, score = fffAligner.Fit(inData.BaseAudioVADList[audioCutLen:len(inData.BaseAudioVADList)-audioCutLen], inData.SrcUnit.GetVADFloatSlice()[inData.OffsetIndex:srcMaxLen])
+//			realOffsetIndex := offsetIndex + audioCutLen
+//			if realOffsetIndex < 0 {
+//				return nil
+//			}
+//			// offsetIndex 这里得到的是 10ms 为一个单位的 OffsetIndex
+//			nowBaseStartTime = vad.GetAudioIndex2Time(realOffsetIndex)
+//
+//		} else {
+//			// 使用 字幕 来进行匹配
+//
+//			srcMaxLen = inData.OffsetIndex + windowInfo.SrcWindowLen
+//			if srcMaxLen >= len(inData.SrcUnit.GetVADFloatSlice()) {
+//				srcMaxLen = len(inData.SrcUnit.GetVADFloatSlice()) - 1
+//			}
+//			offsetIndex, score = fffAligner.Fit(inData.BaseUnit.GetVADFloatSlice(), inData.SrcUnit.GetVADFloatSlice()[inData.OffsetIndex:srcMaxLen])
+//			if offsetIndex < 0 {
+//				return nil
+//			}
+//			bok, nowBaseStartTime = inData.BaseUnit.GetIndexTimeNumber(offsetIndex, true)
+//			if bok == false {
+//				return nil
+//			}
+//		}
+//		// 需要校正的字幕
+//		bok, nowSrcStartTime := inData.SrcUnit.GetIndexTimeNumber(inData.OffsetIndex, true)
+//		if bok == false {
+//			return nil
+//		}
+//		// 时间差值
+//		TimeDiffStartCorrelation := nowBaseStartTime - nowSrcStartTime
+//		log_helper.GetLogger().Debugln("------------")
+//		log_helper.GetLogger().Debugln("OffsetTime:", fmt.Sprintf("%v", TimeDiffStartCorrelation),
+//			"offsetIndex:", offsetIndex,
+//			"score:", fmt.Sprintf("%v", score))
+//
+//		mutexFixV2.Lock()
+//		// 这里的未必的顺序的,所以才有 IndexMatchWindowInfoMap 的存在的意义
+//		outMatchInfo.IndexMatchWindowInfoMap[inData.Index] = MatchWindowInfo{TimeDiffStartCorrelation: TimeDiffStartCorrelation,
+//			StartVADIndex: inData.OffsetIndex,
+//			EndVADIndex:   srcMaxLen}
+//		outMatchInfo.StartDiffTimeList = append(outMatchInfo.StartDiffTimeList, TimeDiffStartCorrelation)
+//		outMatchInfo.StartDiffTimeListEx = append(outMatchInfo.StartDiffTimeListEx, TimeDiffStartCorrelation)
+//		outMatchInfo.StartDiffTimeMap.Put(score, windowInfo.MatchedTimes)
+//		windowInfo.MatchedTimes++
+//		mutexFixV2.Unlock()
+//		// -------------------------------------------------
+//		return nil
+//	}
+//	// -------------------------------------------------
+//	antPool, err := ants.NewPoolWithFunc(s.FixerConfig.V2_FixThreads, func(inData interface{}) {
+//		data := inData.(InputData)
+//		defer data.Wg.Done()
+//		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.FixerConfig.V2_SubOneUnitProcessTimeOut)*time.Second)
+//		defer cancel()
+//
+//		done := make(chan error, 1)
+//		panicChan := make(chan interface{}, 1)
+//		go func() {
+//			defer func() {
+//				if p := recover(); p != nil {
+//					panicChan <- p
+//				}
+//			}()
+//
+//			done <- fixFunc(inData)
+//		}()
+//
+//		select {
+//		case err := <-done:
+//			if err != nil {
+//				log_helper.GetLogger().Errorln("GetOffsetTimeV2.NewPoolWithFunc done with Error", err.Error())
+//			}
+//			return
+//		case p := <-panicChan:
+//			log_helper.GetLogger().Errorln("GetOffsetTimeV2.NewPoolWithFunc got panic", p)
+//			return
+//		case <-ctx.Done():
+//			log_helper.GetLogger().Errorln("GetOffsetTimeV2.NewPoolWithFunc got time out", ctx.Err())
+//			return
+//		}
+//	})
+//	if err != nil {
+//		return nil, err
+//	}
+//	defer antPool.Release()
+//	// -------------------------------------------------
+//	wg := sync.WaitGroup{}
+//	index := 0
+//	for i := windowInfo.SrcSlideStartIndex; i < windowInfo.SrcSlideStartIndex+windowInfo.SrcSlideLen-1; {
+//		wg.Add(1)
+//
+//		if bUseSubOrAudioAsBase == true {
+//			// 使用字幕
+//			err = antPool.Invoke(InputData{Index: index, BaseUnit: *windowInfo.BaseUnit, SrcUnit: *windowInfo.SrcUnit, OffsetIndex: i, Wg: &wg})
+//		} else {
+//			// 使用音频
+//			err = antPool.Invoke(InputData{Index: index, BaseAudioVADList: windowInfo.BaseAudioFloatList, SrcUnit: *windowInfo.SrcUnit, OffsetIndex: i, Wg: &wg})
+//		}
+//
+//		if err != nil {
+//			log_helper.GetLogger().Errorln("GetOffsetTimeV2 ants.Invoke", err)
+//		}
+//
+//		i += windowInfo.OneStep
+//		index++
+//	}
+//	wg.Wait()
+//
+//	return &outMatchInfo, nil
+//}
+//
+//func (s *SubTimelineFixer) calcMeanAndSDV2(startDiffTimeList stat.Float64Slice, tmpStartDiffTime []float64) FixResult {
+//
+//	oldMean := stat.Mean(startDiffTimeList)
+//	oldSd := stat.Sd(startDiffTimeList)
+//	newMean := MinValue
+//	newSd := MinValue
+//	per := 1.0
+//
+//	if len(tmpStartDiffTime) < 3 {
+//		return FixResult{
+//			0,
+//			0,
+//			oldMean,
+//			oldSd,
+//			oldMean,
+//			oldSd,
+//			per,
+//			false,
+//			make([]MatchWindowInfo, 0),
+//		}
+//	}
+//
+//	// 如果 SD 较大的时候才需要剔除
+//	if oldSd > 0.1 {
+//		var outliersMap = make(map[float64]int, 0)
+//		outliers, _, _ := tukey.Outliers(0.3, tmpStartDiffTime)
+//		for _, outlier := range outliers {
+//			outliersMap[outlier] = 0
+//		}
+//		var newStartDiffTimeList = make([]float64, 0)
+//		for _, f := range tmpStartDiffTime {
+//
+//			_, ok := outliersMap[f]
+//			if ok == true {
+//				continue
+//			}
+//
+//			newStartDiffTimeList = append(newStartDiffTimeList, f)
+//		}
+//
+//		orgLen := startDiffTimeList.Len()
+//		startDiffTimeList = make(stat.Float64Slice, 0)
+//		for _, f := range newStartDiffTimeList {
+//			startDiffTimeList = append(startDiffTimeList, f)
+//		}
+//		newLen := startDiffTimeList.Len()
+//
+//		per = float64(newLen) / float64(orgLen)
+//
+//		newMean = stat.Mean(startDiffTimeList)
+//		newSd = stat.Sd(startDiffTimeList)
+//	}
+//
+//	if my_util.IsEqual(newMean, MinValue) == true {
+//		newMean = oldMean
+//	}
+//	if my_util.IsEqual(newSd, MinValue) == true {
+//		newSd = oldSd
+//	}
+//	return FixResult{
+//		0,
+//		0,
+//		oldMean,
+//		oldSd,
+//		newMean,
+//		newSd,
+//		per,
+//		false,
+//		make([]MatchWindowInfo, 0),
+//	}
+//}
+//
+//// GetOffsetTimeV3 使用内置的字幕校正外置的字幕时间轴
+//func (s *SubTimelineFixer) GetOffsetTimeV3(infoBase, infoSrc, orgFix *subparser.FileInfo, audioVadList []vad.VADInfo) error {
+//
+//	// -------------------------------------------------
+//	var bUseSubOrAudioAsBase = true
+//	if infoBase == nil && audioVadList != nil {
+//		// 使用 音频 来进行匹配
+//		bUseSubOrAudioAsBase = false
+//	} else if infoBase != nil {
+//		// 使用 字幕 来进行匹配
+//		bUseSubOrAudioAsBase = true
+//	} else {
+//		return errors.New("GetOffsetTimeV2 input baseUnit or AudioVad is nil")
+//	}
+//	// -------------------------------------------------
+//	audioFloatList := vad.GetFloatSlice(audioVadList)
+//	baseUnitNew, err := sub_helper.GetVADInfoFeatureFromSubNew(infoBase, 0)
+//	if err != nil {
+//		return err
+//	}
+//	srcUnitNew, err := sub_helper.GetVADInfoFeatureFromSubNew(infoSrc, 0)
+//	if err != nil {
+//		return err
+//	}
+//	/*
+//		上面直接得到所有的输入源,都是完整的一个文件,字幕 or 音频
+//		然后根据字幕文件每一个对白进行匹配,这里就使用 V2_FrontAndEndPerSrc 进行字幕的选择,不打算从第一句话开始
+//		那么假如 一共有 100 句话,V2_FrontAndEndPerSrc 是 0.2,那么就是从 20 - 80 句话进行匹配计算
+//		然后 < 20 的就继承 20 的偏移,> 80 的就继承 80 的偏移即可
+//		那么现在就需要从对白中开始遍历
+//	*/
+//	fffAligner := NewFFTAligner(DefaultMaxOffsetSeconds, SampleRate)
+//	err2, done := s.caleOne(0.1, srcUnitNew, fffAligner, baseUnitNew)
+//	if done {
+//		return err2
+//	}
+//	err2, done = s.caleOne(0.2, srcUnitNew, fffAligner, baseUnitNew)
+//	if done {
+//		return err2
+//	}
+//
+//	skipSubLen := int(float64(len(infoSrc.DialoguesFilter)) * s.FixerConfig.V2_FrontAndEndPerSrc)
+//
+//	sort.Sort(subparser.OneDialogueByStartTime(infoSrc.DialoguesFilter))
+//	sort.Sort(subparser.OneDialogueByStartTime(orgFix.DialoguesFilter))
+//
+//	for i := 0; i < len(infoSrc.DialoguesFilter); i++ {
+//
+//		// 得到的是真实的时间
+//		srcOneDialogueNow := infoSrc.DialoguesFilter[i]
+//		srcTimeStartNow, err := my_util.ParseTime(srcOneDialogueNow.StartTime)
+//		if err != nil {
+//			return err
+//		}
+//		orgFixOneDialogueNow := orgFix.DialoguesFilter[i]
+//		orgFixTimeStartNow, err := my_util.ParseTime(orgFixOneDialogueNow.StartTime)
+//		if err != nil {
+//			return err
+//		}
+//
+//		println("Index:", i, "srcTimeStartOrg:", srcTimeStartNow.Format("15:04:05.000"),
+//			"src-fix-offset:", my_util.Time2SecondNumber(orgFixTimeStartNow)-my_util.Time2SecondNumber(srcTimeStartNow))
+//	}
+//	println("------------------")
+//	for i := skipSubLen; i < len(infoSrc.DialoguesFilter)-skipSubLen-1; i++ {
+//
+//		var bok = false
+//		var nowBaseStartTime = 0.0
+//		var offsetIndex = 0
+//		var score = 0.0
+//		const next = 20
+//		const secondRange = 45
+//		// -------------------------------------------------
+//		srcOneDialogueNow := infoSrc.DialoguesFilter[i]
+//		iNext := i + next
+//		if iNext >= len(infoSrc.DialoguesFilter)-skipSubLen-1 {
+//			iNext = len(infoSrc.DialoguesFilter) - skipSubLen - 1
+//		}
+//		srcOneDialogueNext := infoSrc.DialoguesFilter[iNext]
+//		// 得到的是真实的时间
+//		srcTimeStartNow, err := my_util.ParseTime(srcOneDialogueNow.StartTime)
+//		if err != nil {
+//			return err
+//		}
+//		srcTimeEndNext, err := my_util.ParseTime(srcOneDialogueNext.EndTime)
+//		if err != nil {
+//			return err
+//		}
+//		orgFixOneDialogueNow := orgFix.DialoguesFilter[i]
+//		orgFixTimeStartNow, err := my_util.ParseTime(orgFixOneDialogueNow.StartTime)
+//		if err != nil {
+//			return err
+//		}
+//		// -------------------------------------------------
+//		// 需要转换为 VAD 的对应 Index,需要减去 baseTime,然后根据 10ms 进行计算
+//		// -------------------------------------------------
+//		// Src
+//		srcStartOffsetTimeNow := srcUnitNew.RealTimeToOffsetTime(srcTimeStartNow)
+//		srcStartTimeVADIndexNow := int(my_util.Time2SecondNumber(srcStartOffsetTimeNow) * 100)
+//
+//		srcEndOffsetTimeNext := srcUnitNew.RealTimeToOffsetTime(srcTimeEndNext)
+//		srcEndTimeVADIndexNext := int(my_util.Time2SecondNumber(srcEndOffsetTimeNext) * 100)
+//		// -------------------------------------------------
+//		if bUseSubOrAudioAsBase == false {
+//			// 使用 音频 来进行匹配
+//
+//		} else {
+//			// 使用 字幕 来进行匹配
+//			// -------------------------------------------------
+//			// Base
+//			baseStartOffsetTimeNow := baseUnitNew.RealTimeToOffsetTime(srcTimeStartNow).Add(-secondRange * time.Second)
+//			baseStartTimeVADIndexNow := int(my_util.Time2SecondNumber(baseStartOffsetTimeNow) * 100)
+//
+//			baseEndOffsetTimeNext := baseUnitNew.RealTimeToOffsetTime(srcTimeEndNext).Add(secondRange * time.Second)
+//			baseEndTimeVADIndexNext := int(my_util.Time2SecondNumber(baseEndOffsetTimeNext) * 100)
+//			if baseEndTimeVADIndexNext >= len(baseUnitNew.VADList)-1 {
+//				baseEndTimeVADIndexNext = len(baseUnitNew.VADList) - 1
+//			}
+//			// -------------------------------------------------
+//			offsetIndex, score = fffAligner.Fit(baseUnitNew.GetVADFloatSlice()[baseStartTimeVADIndexNow:baseEndTimeVADIndexNext], srcUnitNew.GetVADFloatSlice()[srcStartTimeVADIndexNow:srcEndTimeVADIndexNext])
+//			if offsetIndex < 0 {
+//				//return nil
+//				continue
+//			}
+//			bok, nowBaseStartTime = baseUnitNew.GetIndexTimeNumber(baseStartTimeVADIndexNow+offsetIndex, true)
+//			if bok == false {
+//				return nil
+//			}
+//		}
+//		// 需要校正的字幕
+//		bok, nowSrcStartTime := srcUnitNew.GetIndexTimeNumber(srcStartTimeVADIndexNow, true)
+//		if bok == false {
+//			return nil
+//		}
+//		// 时间差值
+//		TimeDiffStartCorrelation := nowBaseStartTime - nowSrcStartTime
+//
+//		println("Index:", i, "srcTimeStartOrg:", srcTimeStartNow.Format("15:04:05.000"),
+//			"OffsetTime:", TimeDiffStartCorrelation, "Score:", score,
+//			"ChangedTime:", srcTimeStartNow.Add(time.Duration(TimeDiffStartCorrelation*1000)*time.Millisecond).Format("15:04:05.000"),
+//			"OrgFixTime:", orgFixTimeStartNow.Format("15:04:05.000"),
+//			"src-fix-offset:", my_util.Time2SecondNumber(orgFixTimeStartNow)-my_util.Time2SecondNumber(srcTimeStartNow))
+//	}
+//
+//	//if baseStartTimeVADIndexNow > 3600000 {
+//	//	baseStartTimeVADIndexNow = 0
+//	//}
+//
+//	println(len(audioFloatList))
+//	println(len(baseUnitNew.VADList))
+//	println(len(srcUnitNew.VADList))
+//
+//	return nil
+//}
+//
+//func (s *SubTimelineFixer) caleOne(cutPer float64, srcUnitNew *sub_helper.SubUnit, fffAligner *FFTAligner, baseUnitNew *sub_helper.SubUnit) (error, bool) {
+//	srcVADLen := len(srcUnitNew.VADList)
+//	srcCutStartIndex := int(float64(srcVADLen) * cutPer)
+//	offsetIndexAll, scoreAll := fffAligner.Fit(baseUnitNew.GetVADFloatSlice(), srcUnitNew.GetVADFloatSlice()[srcCutStartIndex:srcVADLen-srcCutStartIndex])
+//	bok, nowBaseStartTime := baseUnitNew.GetIndexTimeNumber(0+offsetIndexAll, true)
+//	if bok == false {
+//		return nil, true
+//	}
+//	// 需要校正的字幕
+//	bok, nowSrcStartTime := srcUnitNew.GetIndexTimeNumber(srcCutStartIndex, true)
+//	if bok == false {
+//		return nil, true
+//	}
+//	// 时间差值
+//	TimeDiffStartCorrelation := nowBaseStartTime - nowSrcStartTime
+//	bok, srcIndexCutTime := srcUnitNew.GetIndexTime(srcCutStartIndex, true)
+//	if bok == false {
+//		return nil, true
+//	}
+//	println(srcIndexCutTime.Format("15:04:05.000"), TimeDiffStartCorrelation, scoreAll)
+//	return nil, false
+//}
+//
+//const FixMask = "-fix"
+//const MinValue = -9999.0
+//
+//var mutexFixV2 sync.Mutex

+ 24 - 12
internal/pkg/sub_timeline_fixer/restore.go

@@ -9,19 +9,31 @@ import (
 )
 
 // Restore 从备份还原自动校正的字幕文件
-func Restore(movieDir, seriesDir string) (int, error) {
-	// 搜索出所有的 csf-bk 文件
-	backUpSubMoviesFilePathList, err := searchBackUpSubFile(movieDir)
-	if err != nil {
-		return 0, err
+func Restore(movieDirs, seriesDirs []string) (int, error) {
+
+	var BackUpSubMoviesFilePathList = make([]string, 0)
+	var BackUpSubSeriesFilePathList = make([]string, 0)
+
+	for _, dir := range movieDirs {
+		// 搜索出所有的 csf-bk 文件
+		oneBackUpSubMoviesFilePathList, err := searchBackUpSubFile(dir)
+		if err != nil {
+			return 0, err
+		}
+		BackUpSubMoviesFilePathList = append(BackUpSubMoviesFilePathList, oneBackUpSubMoviesFilePathList...)
 	}
-	backUpSubSeriesFilePathList, err := searchBackUpSubFile(seriesDir)
-	if err != nil {
-		return 0, err
+	for _, dir := range seriesDirs {
+		// 搜索出所有的 csf-bk 文件
+		oneBackUpSubSeriesFilePathList, err := searchBackUpSubFile(dir)
+		if err != nil {
+			return 0, err
+		}
+		BackUpSubSeriesFilePathList = append(BackUpSubSeriesFilePathList, oneBackUpSubSeriesFilePathList...)
 	}
-	allBkFilesPath := make([]string, len(backUpSubMoviesFilePathList)+len(backUpSubSeriesFilePathList))
-	allBkFilesPath = append(allBkFilesPath, backUpSubMoviesFilePathList...)
-	allBkFilesPath = append(allBkFilesPath, backUpSubSeriesFilePathList...)
+
+	allBkFilesPath := make([]string, len(BackUpSubMoviesFilePathList)+len(BackUpSubSeriesFilePathList))
+	allBkFilesPath = append(allBkFilesPath, BackUpSubMoviesFilePathList...)
+	allBkFilesPath = append(allBkFilesPath, BackUpSubSeriesFilePathList...)
 	// 通过这些文件,判断当前每个 bk 下面是否有相应的文件,如果在则删除,然后再重命名 bk 文件回原来的文件名称
 	// Fargo - S04E04 - The Pretend War WEBDL-1080p.chinese(简英,shooter).default.ass.csf-bk
 	// Fargo - S04E04 - The Pretend War WEBDL-1080p.chinese(简英,shooter).default.ass
@@ -30,7 +42,7 @@ func Restore(movieDir, seriesDir string) (int, error) {
 
 		fixedFileName := strings.ReplaceAll(oneBkFile, BackUpExt, "")
 		if my_util.IsFile(fixedFileName) == true {
-			err = os.Remove(fixedFileName)
+			err := os.Remove(fixedFileName)
 			if err != nil {
 				return 0, err
 			}

+ 73 - 0
internal/pkg/url_connectedness_helper/url_connectedness_helper.go

@@ -0,0 +1,73 @@
+package url_connectedness_helper
+
+import (
+	"errors"
+	"net/http"
+	"net/url"
+	"regexp"
+	"time"
+)
+
+// UrlConnectednessTest 测试输入 url 的连通性
+func UrlConnectednessTest(testUrl, proxyAddr string) (bool, int, error) {
+
+	var httpClient http.Client
+	if proxyAddr == "" {
+		// 无需代理
+		// 创建连接客户端
+		httpClient = http.Client{
+			Timeout: time.Second * testUrlTimeOut,
+		}
+	} else {
+		// 需要代理
+		// 检测代理iP访问地址
+		if proxyAddressValidHttpFormat(proxyAddr) == false {
+			return false, 0, errors.New("proxy address illegal, only support http://xx:xx")
+		}
+		// 解析代理地址
+		proxy, err := url.Parse(proxyAddr)
+		if err != nil {
+			return false, 0, err
+		}
+		// 设置网络传输
+		netTransport := &http.Transport{
+			Proxy:                 http.ProxyURL(proxy),
+			MaxIdleConnsPerHost:   10,
+			ResponseHeaderTimeout: time.Second * time.Duration(testUrlTimeOut),
+		}
+		// 创建连接客户端
+		httpClient = http.Client{
+			Timeout:   time.Second * testUrlTimeOut,
+			Transport: netTransport,
+		}
+	}
+
+	begin := time.Now() //判断代理访问时间
+	// 使用代理IP访问测试地址
+	res, err := httpClient.Get(testUrl)
+	if err != nil {
+		return false, 0, err
+	}
+	defer func() {
+		_ = res.Body.Close()
+	}()
+	speed := int(time.Now().Sub(begin).Nanoseconds() / 1000 / 1000) //ms
+	// 判断是否成功访问,如果成功访问StatusCode应该为200
+	if res.StatusCode != http.StatusOK {
+		return false, 0, nil
+	}
+	return true, speed, nil
+}
+
+// proxyAddressValidHttpFormat 代理地址是否是有效的格式,必须是 http 的代理
+func proxyAddressValidHttpFormat(proxyAddr string) bool {
+	// 首先检测 proxyAddr 是否合法,必须是 http 的代理,不支持 https 代理
+	re := regexp.MustCompile(`(http):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`)
+	result := re.FindAllStringSubmatch(proxyAddr, -1)
+	if result == nil || len(result) < 1 {
+		return false
+	}
+	return true
+}
+
+const testUrlTimeOut = 5

+ 49 - 0
internal/pkg/url_connectedness_helper/url_connectedness_helper_test.go

@@ -0,0 +1,49 @@
+package url_connectedness_helper
+
+import "testing"
+
+func TestUrlConnectednessTest(t *testing.T) {
+	type args struct {
+		testUrl   string
+		proxyAddr string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    bool
+		wantErr bool
+	}{
+		{name: "0", args: args{
+			testUrl:   "https://google.com",
+			proxyAddr: "",
+		}, want: false, wantErr: true},
+		{name: "1", args: args{
+			testUrl:   "https://google.com",
+			proxyAddr: "",
+		}, want: false, wantErr: true},
+		{name: "2", args: args{
+			testUrl:   "https://google.com",
+			proxyAddr: "",
+		}, want: false, wantErr: true},
+		{name: "3", args: args{
+			testUrl:   "https://google.com",
+			proxyAddr: "",
+		}, want: false, wantErr: true},
+		{name: "4", args: args{
+			testUrl:   "https://google.com",
+			proxyAddr: "http://192.168.50.252:20172",
+		}, want: true, wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, _, err := UrlConnectednessTest(tt.args.testUrl, tt.args.proxyAddr)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("UrlConnectednessTest() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("UrlConnectednessTest() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}

+ 0 - 9
internal/types/emby/config.go

@@ -1,9 +0,0 @@
-package emby
-
-type EmbyConfig struct {
-	Url         string //	Emby 的地址,需要带上端口号 http://192.168.1.2:8089
-	ApiKey      string //	相应的 API Key
-	LimitCount  int    //	最多获取多少更新的内容
-	SkipWatched bool   // 	跳过看过的视频,这里会读取所有 Emby 的 User 看过的列表,默认 false
-	FixTimeLine bool   // 	开启校正字幕时间轴,默认 false
-}

+ 0 - 39
internal/types/reqparam.go

@@ -1,39 +0,0 @@
-package types
-
-import (
-	"github.com/allanpk716/ChineseSubFinder/internal/types/emby"
-	"github.com/allanpk716/ChineseSubFinder/internal/types/sub_timeline_fiexer"
-)
-
-// ReqParam 可选择传入的参数
-type ReqParam struct {
-	UserExtList                   []string        // 用户确认的视频后缀名支持列表
-	SaveMultiSub                  bool            // 存储每个网站 Top1 的字幕
-	DebugMode                     bool            // 调试标志位
-	Threads                       int             // 同时并发的线程数(准确来说在go中不是线程,是 goroutine)
-	SubTypePriority               int             // 字幕下载的优先级,0 是自动,1 是 srt 优先,2 是 ass/ssa 优先
-	WhenSubSupplierInvalidWebHook string          // 当字幕网站失效的时候,触发的 webhook 地址,默认是 get
-	EmbyConfig                    emby.EmbyConfig // Emby API 高阶设置参数
-	SaveOneSeasonSub              bool            // 保存整个季度的字幕
-
-	SubTimelineFixerConfig sub_timeline_fiexer.SubTimelineFixerConfig // 时间轴校正配置信息
-	FixTimeLine            bool                                       // 	开启校正字幕时间轴,默认 false
-
-	HttpProxy string // HttpClient 相关
-	UserAgent string // HttpClient 相关
-	Referer   string // HttpClient 相关
-	MediaType string // HttpClient 相关
-	Charset   string // HttpClient 相关
-	Topic     int    // 搜索结果的时候,返回 Topic N 以内的
-}
-
-func NewReqParam() *ReqParam {
-	r := ReqParam{
-		UserExtList:     make([]string, 0),
-		SaveMultiSub:    false,
-		DebugMode:       false,
-		Threads:         2,
-		SubTypePriority: 0,
-	}
-	return &r
-}

+ 0 - 65
internal/types/sub_timeline_fiexer/sub_timeline_fixer_config.go

@@ -1,65 +0,0 @@
-package sub_timeline_fiexer
-
-type SubTimelineFixerConfig struct {
-	// V1 的设置
-	V1_MaxCompareDialogue int     // 最大需要匹配的连续对白,默认3
-	V1_MaxStartTimeDiffSD float64 // 对白开始时间的统计 SD 最大误差,超过则不进行修正
-	V1_MinMatchedPercent  float64 // 两个文件的匹配百分比(src/base),高于这个才比例进行修正
-	V1_MinOffset          float64 // 超过这个(+-)偏移的时间轴才校正,否则跳过,单位秒
-	// V2 的设置
-	V2_SubOneUnitProcessTimeOut int     // 字幕时间轴校正一个单元的超时时间,单位秒
-	V2_FrontAndEndPerBase       float64 // 前百分之 15 和后百分之 15 都不进行识别
-	V2_FrontAndEndPerSrc        float64 // 前百分之 20 和后百分之 20 都不进行识别
-	V2_WindowMatchPer           float64 // SrcSub 滑动窗体的占比
-	V2_CompareParts             int     // 滑动窗体分段次数
-	V2_FixThreads               int     // 字幕校正的并发线程
-	V2_MaxStartTimeDiffSD       float64 // 对白开始时间的统计 SD 最大误差,超过则不进行修正
-	V2_MinOffset                float64 // 超过这个(+-)偏移的时间轴才校正,否则跳过,单位秒
-	V2_MaxOffsetTime            int     // 最大可以校正的时间偏移,时间是秒
-}
-
-// CheckDefault 检测默认值(比如某些之默认不能为0),不对就重置到默认值上
-func (s *SubTimelineFixerConfig) CheckDefault() {
-	// V1
-	if s.V1_MaxCompareDialogue <= 0 {
-		s.V1_MaxCompareDialogue = 3
-	}
-	if s.V1_MaxStartTimeDiffSD <= 0 {
-		s.V1_MaxStartTimeDiffSD = 0.1
-	}
-	if s.V1_MinMatchedPercent <= 0 {
-		s.V1_MinMatchedPercent = 0.1
-	}
-	if s.V1_MinOffset <= 0 {
-		s.V1_MinOffset = 0.1
-	}
-	// V2
-	if s.V2_SubOneUnitProcessTimeOut <= 0 {
-		s.V2_SubOneUnitProcessTimeOut = 30
-	}
-	if s.V2_FrontAndEndPerBase <= 0 || s.V2_FrontAndEndPerBase >= 1.0 {
-		s.V2_FrontAndEndPerBase = 0.15
-	}
-	if s.V2_FrontAndEndPerSrc <= 0 || s.V2_FrontAndEndPerSrc >= 1.0 {
-		s.V2_FrontAndEndPerSrc = 0.2
-	}
-	if s.V2_WindowMatchPer <= 0 || s.V2_WindowMatchPer >= 1.0 {
-		s.V2_WindowMatchPer = 0.7
-	}
-	if s.V2_CompareParts <= 0 {
-		s.V2_CompareParts = 5
-	}
-	if s.V2_FixThreads <= 0 {
-		s.V2_FixThreads = 3
-	}
-	if s.V2_MaxStartTimeDiffSD <= 0 {
-		s.V2_MaxStartTimeDiffSD = 0.1
-	}
-	if s.V2_MinOffset <= 0 {
-		s.V2_MinOffset = 0.1
-	}
-
-	if s.V2_MaxOffsetTime <= 0 {
-		s.V2_MaxOffsetTime = 120
-	}
-}

+ 1 - 1
scripts/temp-test.sh

@@ -20,7 +20,7 @@ go test ./internal/pkg/imdb_helper
 # go test ./internal/pkg/log_helper
 # go test ./internal/pkg/my_util # will produce Log dir
 echo "ERROR:  Proxy needed for proxy_helper, skipping.............."
-# go test ./internal/pkg/proxy_helper # fail
+# go test ./internal/pkg/url_connectedness_helper # fail
 # go test ./internal/pkg/random_useragent
 # go test ./internal/pkg/regex_things
 echo "ERROR:  Proxy needed for rod_helper,  skipping.............."