Selaa lähdekoodia

重构完 ScrabbleUpVideoList 相关并发逻辑,待测试

Signed-off-by: allan716 <[email protected]>
allan716 3 vuotta sitten
vanhempi
sitoutus
cda2c35fc4

+ 11 - 1
internal/logic/movie_helper/moviehelper.go

@@ -92,7 +92,17 @@ func MovieHasChineseSub(log *logrus.Logger, videoFilePath string) (bool, []strin
 			}
 			// 字幕文件是否包含中文
 			subFileFullPath := filepath.Join(dir, curFile.Name())
-			if sub_parser_hub.NewSubParserHub(log, ass.NewParser(log), srt.NewParser(log)).IsSubHasChinese(subFileFullPath) == true {
+			subParserHub := sub_parser_hub.NewSubParserHub(log, ass.NewParser(log), srt.NewParser(log))
+			bFind, subParserFileInfo, err := subParserHub.DetermineFileTypeFromFile(subFileFullPath)
+			if err != nil {
+				log.Errorln("DetermineFileTypeFromFile", subFileFullPath, err)
+				continue
+			}
+			if bFind == false {
+				log.Warnln("DetermineFileTypeFromFile", subFileFullPath, "not support SubType")
+				continue
+			}
+			if subParserHub.IsSubHasChinese(subParserFileInfo) == true {
 				if bFoundChineseSub == false {
 					bFoundChineseSub = true
 				}

+ 5 - 4
internal/logic/series_helper/seriesHelper.go

@@ -46,10 +46,7 @@ func readSeriesInfo(log *logrus.Logger, seriesDir string, need2AnalyzeSub bool)
 		return nil, nil, err
 	}
 	for _, subFile := range subFiles {
-		// 判断这个字幕是否包含中文
-		if subParserHub.IsSubHasChinese(subFile) == false {
-			continue
-		}
+
 		info, _, err := decode.GetVideoInfoFromFileFullPath(subFile)
 		if err != nil {
 			log.Errorln(err)
@@ -64,6 +61,10 @@ func readSeriesInfo(log *logrus.Logger, seriesDir string, need2AnalyzeSub bool)
 			log.Warnln("DetermineFileTypeFromFile", subFile, "not support SubType")
 			continue
 		}
+		// 判断这个字幕是否包含中文
+		if subParserHub.IsSubHasChinese(subParserFileInfo) == false {
+			continue
+		}
 		epsKey := my_util.GetEpisodeKeyName(info.Season, info.Episode)
 		oneFileSubInfo := series.SubInfo{
 			Title:        info.Title,

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

@@ -46,7 +46,7 @@ func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubF
 	*/
 	// 不管是不是保存多个字幕,都要先扫描本地的字幕,进行 .Default .Forced 去除
 	// 这个视频的所有字幕,去除 .default .Forced 标记
-	err = sub_helper.SearchVideoMatchSubFileAndRemoveExtMark(oneVideoFullPath)
+	err = sub_helper.SearchVideoMatchSubFileAndRemoveExtMark(d.log, oneVideoFullPath)
 	if err != nil {
 		// 找个错误可以忍
 		d.log.Errorln("SearchVideoMatchSubFileAndRemoveExtMark,", oneVideoFullPath, err)

+ 1 - 1
internal/pkg/ffmpeg_helper/ffmpeg_info.go

@@ -181,7 +181,7 @@ func (f *FFMPEGInfo) isSubExported(nowCacheFolder string) bool {
 
 // GetExternalSubInfos 获取外置的字幕信息
 func (f *FFMPEGInfo) GetExternalSubInfos(subParserHub *sub_parser_hub.SubParserHub) error {
-	subFiles, err := sub_helper.SearchMatchedSubFileByOneVideo(f.VideoFullPath)
+	subFiles, err := sub_helper.SearchMatchedSubFileByOneVideo(f.log, f.VideoFullPath)
 	if err != nil {
 		return err
 	}

+ 35 - 0
internal/pkg/filter/filter.go

@@ -0,0 +1,35 @@
+package filter
+
+import (
+	"github.com/sirupsen/logrus"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+func SkipFileInfo(l *logrus.Logger, curFile os.DirEntry) bool {
+
+	// 跳过不符合的文件,比如 MAC OS 下可能有缓存文件,见 #138
+	fi, err := curFile.Info()
+	if err != nil {
+		l.Errorln("curFile.Info:", curFile.Name(), err)
+		return true
+	}
+
+	if fi.Size() < 1000 {
+		l.Debugln("curFile.Size() < 1000:", curFile.Name())
+		return true
+	}
+
+	if fi.Size() == 4096 && strings.HasPrefix(curFile.Name(), "._") == true {
+		l.Debugln("curFile.Size() == 4096 && Prefix Name == ._*", curFile.Name())
+		return true
+	}
+	// 跳过预告片,见 #315
+	if strings.HasSuffix(strings.ReplaceAll(curFile.Name(), filepath.Ext(curFile.Name()), ""), "-trailer") == true {
+		l.Debugln("curFile Name has -trailer:", curFile.Name())
+		return true
+	}
+
+	return false
+}

+ 3 - 28
internal/pkg/my_util/util.go

@@ -10,6 +10,7 @@ import (
 	"encoding/hex"
 	"fmt"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/filter"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/regex_things"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sort_things"
@@ -302,20 +303,7 @@ func SearchMatchedVideoFile(l *logrus.Logger, dir string) ([]string, error) {
 					continue
 				}
 
-				// 跳过不符合的文件,比如 MAC OS 下可能有缓存文件,见 #138
-				fi, err := curFile.Info()
-				if err != nil {
-					l.Debugln("SearchMatchedVideoFile, file.Info:", fullPath, err)
-					continue
-				}
-
-				if fi.Size() == 4096 && strings.HasPrefix(curFile.Name(), "._") == true {
-					l.Debugln("SearchMatchedVideoFile file.Size() == 4096 && Prefix Name == ._*", fullPath)
-					continue
-				}
-				// 跳过预告片,见 #315
-				if strings.HasSuffix(strings.ReplaceAll(curFile.Name(), filepath.Ext(curFile.Name()), ""), "-trailer") == true {
-					l.Debugln("SearchMatchedVideoFile, Skip -trailer:", fullPath)
+				if filter.SkipFileInfo(l, curFile) == true {
 					continue
 				}
 
@@ -373,22 +361,9 @@ func SearchTVNfo(l *logrus.Logger, dir string) ([]string, error) {
 				continue
 			} else {
 
-				// 跳过不符合的文件,比如 MAC OS 下可能有缓存文件,见 #138
-				fi, err := curFile.Info()
-				if err != nil {
-					l.Debugln("SearchTVNfo, file.Info:", fullPath, err)
+				if filter.SkipFileInfo(l, curFile) == true {
 					continue
 				}
-				if fi.Size() == 4096 && strings.HasPrefix(curFile.Name(), "._") == true {
-					l.Debugln("SearchTVNfo file.Size() == 4096 && Prefix Name == ._*", fullPath)
-					continue
-				}
-				// 跳过预告片,见 #315
-				if strings.HasSuffix(strings.ReplaceAll(curFile.Name(), filepath.Ext(curFile.Name()), ""), "-trailer") == true {
-					l.Debugln("SearchTVNfo, Skip -trailer:", fullPath)
-					continue
-				}
-
 				fileFullPathList = append(fileFullPathList, fullPath)
 			}
 		}

+ 12 - 31
internal/pkg/sub_helper/sub_helper.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/archive_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/filter"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/language"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
@@ -222,22 +223,7 @@ func SearchMatchedSubFileByDir(log *logrus.Logger, dir string) ([]string, error)
 			}
 		} else {
 			// 这里就是文件了
-			info, err := curFile.Info()
-			if err != nil {
-				return nil, err
-			}
-			if info.Size() < 1000 {
-				continue
-			}
-
-			if info.Size() == 4096 && strings.HasPrefix(curFile.Name(), "._") == true {
-				log.Debugln("SearchMatchedSubFileByDir file.Size() == 4096 && Prefix Name == ._*", fullPath)
-				continue
-			}
-
-			// 跳过预告片,见 #315
-			if strings.HasSuffix(strings.ReplaceAll(curFile.Name(), filepath.Ext(curFile.Name()), ""), "-trailer") == true {
-				log.Debugln("SearchMatchedSubFileByDir, Skip -trailer:", fullPath)
+			if filter.SkipFileInfo(log, curFile) == true {
 				continue
 			}
 
@@ -250,7 +236,7 @@ func SearchMatchedSubFileByDir(log *logrus.Logger, dir string) ([]string, error)
 }
 
 // SearchMatchedSubFileByOneVideo 搜索这个视频当前目录下匹配的字幕
-func SearchMatchedSubFileByOneVideo(oneVideoFullPath string) ([]string, error) {
+func SearchMatchedSubFileByOneVideo(l *logrus.Logger, oneVideoFullPath string) ([]string, error) {
 	dir := filepath.Dir(oneVideoFullPath)
 	fileName := filepath.Base(oneVideoFullPath)
 	fileName = strings.ToLower(fileName)
@@ -268,13 +254,10 @@ func SearchMatchedSubFileByOneVideo(oneVideoFullPath string) ([]string, error) {
 			continue
 		}
 		// 这里就是文件了
-		info, err := curFile.Info()
-		if err != nil {
-			return nil, err
-		}
-		if info.Size() < 1000 {
+		if filter.SkipFileInfo(l, curFile) == true {
 			continue
 		}
+
 		// 判断的时候用小写的,后续重命名的时候用原有的名称
 		nowFileName := strings.ToLower(curFile.Name())
 		// 后缀名得对
@@ -282,7 +265,7 @@ func SearchMatchedSubFileByOneVideo(oneVideoFullPath string) ([]string, error) {
 			continue
 		}
 		// 字幕文件名应该包含 视频文件名(无后缀)
-		if strings.Contains(nowFileName, fileName) == false {
+		if strings.HasPrefix(nowFileName, fileName) == false {
 			continue
 		}
 
@@ -294,7 +277,7 @@ func SearchMatchedSubFileByOneVideo(oneVideoFullPath string) ([]string, error) {
 }
 
 // SearchVideoMatchSubFileAndRemoveExtMark 找到找个视频目录下相匹配的字幕,同时去除这些字幕中 .default 或者 .forced 的标记。注意这两个标记不应该同时出现,否则无法正确去除
-func SearchVideoMatchSubFileAndRemoveExtMark(oneVideoFullPath string) error {
+func SearchVideoMatchSubFileAndRemoveExtMark(l *logrus.Logger, oneVideoFullPath string) error {
 
 	dir := filepath.Dir(oneVideoFullPath)
 	fileName := filepath.Base(oneVideoFullPath)
@@ -310,11 +293,7 @@ func SearchVideoMatchSubFileAndRemoveExtMark(oneVideoFullPath string) error {
 			continue
 		} else {
 			// 这里就是文件了
-			info, err := curFile.Info()
-			if err != nil {
-				return err
-			}
-			if info.Size() < 1000 {
+			if filter.SkipFileInfo(l, curFile) == true {
 				continue
 			}
 			// 判断的时候用小写的,后续重命名的时候用原有的名称
@@ -324,11 +303,13 @@ func SearchVideoMatchSubFileAndRemoveExtMark(oneVideoFullPath string) error {
 				continue
 			}
 			// 字幕文件名应该包含 视频文件名(无后缀)
-			if strings.Contains(nowFileName, fileName) == false {
+			if strings.HasPrefix(nowFileName, fileName) == false {
 				continue
 			}
-			// 得包含 .default. 找个关键词
+
 			if strings.Contains(nowFileName, subparser.Sub_Ext_Mark_Default+".") == true {
+				// 得包含 .default. 找个关键词
+				// 去除 .default.
 				oldPath := dir + pathSep + curFile.Name()
 				newPath := dir + pathSep + strings.ReplaceAll(curFile.Name(), subparser.Sub_Ext_Mark_Default+".", ".")
 				err = os.Rename(oldPath, newPath)

+ 5 - 25
internal/pkg/sub_parser_hub/subParserHub.go

@@ -2,6 +2,7 @@ package sub_parser_hub
 
 import (
 	"github.com/allanpk716/ChineseSubFinder/internal/ifaces"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/filter"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/language"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
 	languageConst "github.com/allanpk716/ChineseSubFinder/internal/types/language"
@@ -74,20 +75,11 @@ func (p SubParserHub) DetermineFileTypeFromBytes(inBytes []byte, nowExt string)
 }
 
 // IsSubHasChinese 字幕文件是否包含中文
-func (p SubParserHub) IsSubHasChinese(fileFPath string) bool {
+func (p SubParserHub) IsSubHasChinese(fileInfo *subparser.FileInfo) bool {
 
 	// 增加判断已存在的字幕是否有中文
-	bFind, file, err := p.DetermineFileTypeFromFile(fileFPath)
-	if err != nil {
-		p.log.Errorln("IsSubHasChinese.DetermineFileTypeFromFile", fileFPath, err)
-		return false
-	}
-	if bFind == false {
-		p.log.Warnln("IsSubHasChinese.DetermineFileTypeFromFile", fileFPath, "not support SubType")
-		return false
-	}
-	if language.HasChineseLang(file.Lang) == false {
-		p.log.Warnln("IsSubHasChinese.HasChineseLang", fileFPath, "not chinese sub, is ", file.Lang.String())
+	if language.HasChineseLang(fileInfo.Lang) == false {
+		p.log.Warnln("IsSubHasChinese.HasChineseLang", fileInfo.FileFullPath, "not chinese sub, is ", fileInfo.Lang.String())
 		return false
 	}
 
@@ -205,19 +197,7 @@ func SearchMatchedSubFile(log *logrus.Logger, dir string) ([]string, error) {
 			} else {
 
 				// 跳过不符合的文件,比如 MAC OS 下可能有缓存文件,见 #138
-				fi, err := curFile.Info()
-				if err != nil {
-					log.Debugln("SearchMatchedSubFile, file.Info:", fullPath, err)
-					continue
-				}
-				if fi.Size() == 4096 && strings.HasPrefix(curFile.Name(), "._") == true {
-					log.Debugln("SearchMatchedSubFile file.Size() == 4096 && Prefix Name == ._*", fullPath)
-					continue
-				}
-
-				// 跳过预告片,见 #315
-				if strings.HasSuffix(strings.ReplaceAll(curFile.Name(), filepath.Ext(curFile.Name()), ""), "-trailer") == true {
-					log.Debugln("SearchMatchedSubFile, Skip -trailer:", fullPath)
+				if filter.SkipFileInfo(log, curFile) == true {
 					continue
 				}
 

+ 13 - 2
internal/pkg/sub_parser_hub/subParserHub_test.go

@@ -3,6 +3,7 @@ package sub_parser_hub
 import (
 	"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/unit_test_helper"
 	"path/filepath"
 	"testing"
@@ -30,12 +31,22 @@ func TestSubParserHubIsSubHasChinese(t *testing.T) {
 		{name: "8", args: args{filePath: filepath.Join(testRootDir, "苍穹浩瀚 - S02E06 - 范式转换.chinese(简英,xunlei).default.srt")}, want: true},
 	}
 
-	subParserHub := NewSubParserHub(ass.NewParser(), srt.NewParser())
+	test4Log := log_helper.GetLogger4Tester()
+	subParserHub := NewSubParserHub(test4Log, ass.NewParser(test4Log), srt.NewParser(test4Log))
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 
-			if got := subParserHub.IsSubHasChinese(tt.args.filePath); got != tt.want {
+			bFind, subParserFileInfo, err := subParserHub.DetermineFileTypeFromFile(tt.args.filePath)
+			if err != nil {
+				t.Error("DetermineFileTypeFromFile", tt.args.filePath, err)
+				return
+			}
+			if bFind == false {
+				t.Error("DetermineFileTypeFromFile", tt.args.filePath, "not support SubType")
+				return
+			}
+			if got := subParserHub.IsSubHasChinese(subParserFileInfo); got != tt.want {
 				t.Errorf("IsSubHasChinese() = %v, want %v", got, tt.want)
 			}
 		})

+ 322 - 95
internal/pkg/video_scan_and_refresh_helper/video_scan_and_refresh_helper.go

@@ -14,6 +14,7 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sort_things"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
 	subTimelineFixerPKG "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/task_control"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/task_queue"
@@ -40,6 +41,8 @@ type VideoScanAndRefreshHelper struct {
 	downloadQueue            *task_queue.TaskQueue           // 需要下载的视频的队列
 	subSupplierHub           *subSupplier.SubSupplierHub     // 字幕提供源的集合,仅仅是 check 是否需要下载字幕是足够的,如果要下载则需要额外的初始化和检查
 	taskControl              *task_control.TaskControl       // 任务控制器
+
+	processLocker sync.Mutex
 }
 
 func NewVideoScanAndRefreshHelper(fileDownloader *file_downloader.FileDownloader, downloadQueue *task_queue.TaskQueue) *VideoScanAndRefreshHelper {
@@ -86,7 +89,7 @@ func (v *VideoScanAndRefreshHelper) Start() error {
 	return nil
 }
 
-func (v VideoScanAndRefreshHelper) Cancel() {
+func (v *VideoScanAndRefreshHelper) Cancel() {
 	v.taskControl.Release()
 	v.taskControl.Reboot()
 }
@@ -256,76 +259,168 @@ func (v *VideoScanAndRefreshHelper) scrabbleUpVideoListNormal(normal *NormalScan
 		return movieInfos, seasonInfos
 	}
 	// 电影
-	normal.MoviesDirMap.Each(func(movieDirRootPath interface{}, movieFPath interface{}) {
+	movieProcess := func(ctx context.Context, inData interface{}) error {
+
+		scrabbleUpVideoMovieNormalInput := inData.(*ScrabbleUpVideoMovieNormalInput)
+		oneMovieDirRootPath := scrabbleUpVideoMovieNormalInput.OneMovieDirRootPath
+		oneMovieFPath := scrabbleUpVideoMovieNormalInput.OneMovieFPath
+
+		v.processLocker.Lock()
+		desUrl, found := pathUrlMap[oneMovieDirRootPath]
+		if found == false {
+			v.processLocker.Unlock()
+			// 没有找到对应的 URL
+			return nil
+		}
+		v.processLocker.Unlock()
+
+		// 匹配上了前缀就替换这个,并记录
+		movieFUrl := strings.ReplaceAll(oneMovieFPath, oneMovieDirRootPath, desUrl)
+		oneMovieInfo := backend.MovieInfo{
+			Name:         filepath.Base(movieFUrl),
+			DirRootUrl:   filepath.Dir(movieFUrl),
+			VideoFPath:   oneMovieFPath,
+			VideoUrl:     movieFUrl,
+			SubFPathList: make([]string, 0),
+		}
+		// 搜索字幕
+		matchedSubFileByOneVideo, err := sub_helper.SearchMatchedSubFileByOneVideo(v.log, oneMovieFPath)
+		if err != nil {
+			v.log.Errorln("SearchMatchedSubFileByOneVideo", err)
+		}
+		matchedSubFileByOneVideoUrl := make([]string, 0)
+		for _, oneSubFPath := range matchedSubFileByOneVideo {
+			oneSubFUrl := strings.ReplaceAll(oneSubFPath, oneMovieDirRootPath, desUrl)
+			matchedSubFileByOneVideoUrl = append(matchedSubFileByOneVideoUrl, oneSubFUrl)
+		}
+		oneMovieInfo.SubFPathList = append(oneMovieInfo.SubFPathList, matchedSubFileByOneVideoUrl...)
+
+		v.processLocker.Lock()
+		movieInfos = append(movieInfos, oneMovieInfo)
+		v.processLocker.Unlock()
+
+		return nil
+	}
+	// ----------------------------------------
+	v.taskControl.SetCtxProcessFunc("updateLocalVideoCacheInfo", movieProcess, common.ScanPlayedSubTimeOut)
+	// ----------------------------------------
+	normal.MoviesDirMap.Any(func(movieDirRootPath interface{}, moviesFPath interface{}) bool {
 
 		oneMovieDirRootPath := movieDirRootPath.(string)
-		for _, oneMovieFPath := range movieFPath.([]string) {
+		for i, oneMovieFPath := range moviesFPath.([]string) {
 
-			desUrl, found := pathUrlMap[oneMovieDirRootPath]
-			if found == false {
-				// 没有找到对应的 URL
-				continue
-			}
-			// 匹配上了前缀就替换这个,并记录
-			movieFUrl := strings.ReplaceAll(oneMovieFPath, oneMovieDirRootPath, desUrl)
-			oneMovieInfo := backend.MovieInfo{
-				Name:       filepath.Base(movieFUrl),
-				DirRootUrl: filepath.Dir(movieFUrl),
-				VideoFPath: oneMovieFPath,
-				VideoUrl:   movieFUrl,
+			// 放入队列
+			err := v.taskControl.Invoke(&task_control.TaskData{
+				Index: i,
+				Count: len(moviesFPath.([]string)),
+				DataEx: ScrabbleUpVideoMovieNormalInput{
+					OneMovieDirRootPath: oneMovieDirRootPath,
+					OneMovieFPath:       oneMovieFPath,
+				},
+			})
+			if err != nil {
+				v.log.Errorln(err)
+				return true
 			}
-			movieInfos = append(movieInfos, oneMovieInfo)
 		}
+
+		return false
 	})
+	v.taskControl.Hold()
+	// ----------------------------------------
 	// 连续剧
 	// seriesDirMap: dir <--> seriesList
-	normal.SeriesDirMap.Each(func(seriesRootPathName interface{}, seriesNames interface{}) {
+	seriesProcess := func(ctx context.Context, inData interface{}) error {
 
-		oneSeriesRootPathName := seriesRootPathName.(string)
-		for _, oneSeriesRootDir := range seriesNames.([]string) {
+		scrabbleUpVideoSeriesNormalInput := inData.(*ScrabbleUpVideoSeriesNormalInput)
+		oneSeriesRootPathName := scrabbleUpVideoSeriesNormalInput.OneSeriesRootPathName
+		oneSeriesRootDir := scrabbleUpVideoSeriesNormalInput.OneSeriesRootDir
 
-			desUrl, found := pathUrlMap[oneSeriesRootPathName]
-			if found == false {
-				// 没有找到对应的 URL
-				continue
-			}
-			bNeedDlSub, seriesInfo, err := v.subSupplierHub.SeriesNeedDlSub(oneSeriesRootDir,
-				v.NeedForcedScanAndDownSub, false)
-			if err != nil {
-				v.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.SeriesNeedDlSub", err)
-				continue
-			}
-			if bNeedDlSub == false {
-				continue
-			}
-			seriesDirRootFUrl := strings.ReplaceAll(oneSeriesRootDir, oneSeriesRootPathName, desUrl)
-			oneSeasonInfo := backend.SeasonInfo{
-				Name:          filepath.Base(oneSeriesRootDir),
-				RootDirPath:   oneSeriesRootDir,
-				DirRootUrl:    seriesDirRootFUrl,
-				OneVideoInfos: make([]backend.OneVideoInfo, 0),
+		v.processLocker.Lock()
+		desUrl, found := pathUrlMap[oneSeriesRootPathName]
+		if found == false {
+			v.processLocker.Unlock()
+			// 没有找到对应的 URL
+			return nil
+		}
+		v.processLocker.Unlock()
+
+		bNeedDlSub, seriesInfo, err := v.subSupplierHub.SeriesNeedDlSub(oneSeriesRootDir,
+			v.NeedForcedScanAndDownSub, false)
+		if err != nil {
+			v.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.SeriesNeedDlSub", err)
+			return err
+		}
+		if bNeedDlSub == false {
+			return nil
+		}
+		seriesDirRootFUrl := strings.ReplaceAll(oneSeriesRootDir, oneSeriesRootPathName, desUrl)
+		oneSeasonInfo := backend.SeasonInfo{
+			Name:          filepath.Base(oneSeriesRootDir),
+			RootDirPath:   oneSeriesRootDir,
+			DirRootUrl:    seriesDirRootFUrl,
+			OneVideoInfos: make([]backend.OneVideoInfo, 0),
+		}
+		for _, epsInfo := range seriesInfo.EpList {
+
+			videoFUrl := strings.ReplaceAll(epsInfo.FileFullPath, oneSeriesRootPathName, desUrl)
+			oneVideoInfo := backend.OneVideoInfo{
+				Name:         epsInfo.Title,
+				VideoFPath:   epsInfo.FileFullPath,
+				VideoUrl:     videoFUrl,
+				Season:       epsInfo.Season,
+				Episode:      epsInfo.Episode,
+				SubFPathList: make([]string, 0),
 			}
-			for _, epsInfo := range seriesInfo.EpList {
+			// 替换原始字幕的 FPath 为 Url 路径
+			matchedSubFileByOneVideoUrl := make([]string, 0)
+			for _, info := range epsInfo.SubAlreadyDownloadedList {
 
-				videoFUrl := strings.ReplaceAll(epsInfo.FileFullPath, oneSeriesRootPathName, desUrl)
-				oneVideoInfo := backend.OneVideoInfo{
-					Name:       epsInfo.Title,
-					VideoFPath: epsInfo.FileFullPath,
-					VideoUrl:   videoFUrl,
-					Season:     epsInfo.Season,
-					Episode:    epsInfo.Episode,
-				}
-				oneSeasonInfo.OneVideoInfos = append(oneSeasonInfo.OneVideoInfos, oneVideoInfo)
+				oneSubFUrl := strings.ReplaceAll(info.FileFullPath, oneSeriesRootPathName, desUrl)
+				matchedSubFileByOneVideoUrl = append(matchedSubFileByOneVideoUrl, oneSubFUrl)
 			}
+			oneVideoInfo.SubFPathList = append(oneVideoInfo.SubFPathList, matchedSubFileByOneVideoUrl...)
+
+			oneSeasonInfo.OneVideoInfos = append(oneSeasonInfo.OneVideoInfos, oneVideoInfo)
+		}
 
-			seasonInfos = append(seasonInfos, oneSeasonInfo)
+		v.processLocker.Lock()
+		seasonInfos = append(seasonInfos, oneSeasonInfo)
+		v.processLocker.Unlock()
+
+		return nil
+	}
+	// ----------------------------------------
+	v.taskControl.SetCtxProcessFunc("updateLocalVideoCacheInfo", seriesProcess, common.ScanPlayedSubTimeOut)
+	// ----------------------------------------
+	normal.SeriesDirMap.Any(func(seriesRootPathName interface{}, seriesNames interface{}) bool {
+
+		oneSeriesRootPathName := seriesRootPathName.(string)
+		for i, oneSeriesRootDir := range seriesNames.([]string) {
+			// 放入队列
+			err := v.taskControl.Invoke(&task_control.TaskData{
+				Index: i,
+				Count: len(seriesNames.([]string)),
+				DataEx: ScrabbleUpVideoSeriesNormalInput{
+					OneSeriesRootDir:      oneSeriesRootDir,
+					OneSeriesRootPathName: oneSeriesRootPathName,
+				},
+			})
+			if err != nil {
+				v.log.Errorln(err)
+				return true
+			}
 		}
+
+		return false
 	})
+	v.taskControl.Hold()
+	// ----------------------------------------
 
 	return movieInfos, seasonInfos
 }
 
-func (v VideoScanAndRefreshHelper) scrabbleUpVideoListEmby(emby *EmbyScanVideoResult, pathUrlMap map[string]string) ([]backend.MovieInfo, []backend.SeasonInfo) {
+func (v *VideoScanAndRefreshHelper) scrabbleUpVideoListEmby(emby *EmbyScanVideoResult, pathUrlMap map[string]string) ([]backend.MovieInfo, []backend.SeasonInfo) {
 
 	movieInfos := make([]backend.MovieInfo, 0)
 	seasonInfos := make([]backend.SeasonInfo, 0)
@@ -338,22 +433,24 @@ func (v VideoScanAndRefreshHelper) scrabbleUpVideoListEmby(emby *EmbyScanVideoRe
 	sortSeriesPaths := sort_things.SortStringSliceByLength(v.settings.CommonSettings.SeriesPaths)
 	// ----------------------------------------
 	// Emby 过滤,电影
-	for _, oneMovieMixInfo := range emby.MovieSubNeedDlEmbyMixInfoList {
 
-		if oneMovieMixInfo.PhysicalVideoFileFullPath == "" {
-			continue
-		}
+	movieProcess := func(ctx context.Context, inData interface{}) error {
 
+		scrabbleUpVideoMovieEmbyInput := inData.(ScrabbleUpVideoMovieEmbyInput)
+		oneMovieMixInfo := scrabbleUpVideoMovieEmbyInput.OneMovieMixInfo
 		// 首先需要找到对应的最长的视频媒体库路径,x://ABC  x://ABC/DEF
 		for _, oneMovieDirPath := range sortMoviePaths {
 
 			if strings.HasPrefix(oneMovieMixInfo.PhysicalVideoFileFullPath, oneMovieDirPath.Path) {
 				// 匹配上了
+				v.processLocker.Lock()
 				desUrl, found := pathUrlMap[oneMovieDirPath.Path]
 				if found == false {
+					v.processLocker.Unlock()
 					// 没有找到对应的 URL
-					continue
+					return nil
 				}
+				v.processLocker.Unlock()
 				// 匹配上了前缀就替换这个,并记录
 				movieFUrl := strings.ReplaceAll(oneMovieMixInfo.PhysicalVideoFileFullPath, oneMovieDirPath.Path, desUrl)
 				oneMovieInfo := backend.MovieInfo{
@@ -362,65 +459,176 @@ func (v VideoScanAndRefreshHelper) scrabbleUpVideoListEmby(emby *EmbyScanVideoRe
 					VideoFPath:               oneMovieMixInfo.PhysicalVideoFileFullPath,
 					VideoUrl:                 movieFUrl,
 					MediaServerInsideVideoID: oneMovieMixInfo.VideoInfo.Id,
+					SubFPathList:             make([]string, 0),
+				}
+
+				// 搜索字幕
+				matchedSubFileByOneVideo, err := sub_helper.SearchMatchedSubFileByOneVideo(v.log, oneMovieMixInfo.PhysicalVideoFileFullPath)
+				if err != nil {
+					v.log.Errorln("SearchMatchedSubFileByOneVideo", err)
+				}
+				matchedSubFileByOneVideoUrl := make([]string, 0)
+				for _, oneSubFPath := range matchedSubFileByOneVideo {
+					oneSubFUrl := strings.ReplaceAll(oneSubFPath, oneMovieDirPath.Path, desUrl)
+					matchedSubFileByOneVideoUrl = append(matchedSubFileByOneVideoUrl, oneSubFUrl)
 				}
+				oneMovieInfo.SubFPathList = append(oneMovieInfo.SubFPathList, matchedSubFileByOneVideoUrl...)
+
+				v.processLocker.Lock()
 				movieInfos = append(movieInfos, oneMovieInfo)
+				v.processLocker.Unlock()
+
 				break
 			}
 		}
+
+		return nil
+	}
+	// ----------------------------------------
+	v.taskControl.SetCtxProcessFunc("updateLocalVideoCacheInfo", movieProcess, common.ScanPlayedSubTimeOut)
+	// ----------------------------------------
+	for i, oneMovieMixInfo := range emby.MovieSubNeedDlEmbyMixInfoList {
+
+		if oneMovieMixInfo.PhysicalVideoFileFullPath == "" {
+			continue
+		}
+
+		// 放入队列
+		err := v.taskControl.Invoke(&task_control.TaskData{
+			Index: i,
+			Count: len(emby.MovieSubNeedDlEmbyMixInfoList),
+			DataEx: ScrabbleUpVideoMovieEmbyInput{
+				OneMovieMixInfo: oneMovieMixInfo,
+			},
+		})
+		if err != nil {
+			v.log.Errorln(err)
+			break
+		}
 	}
+	v.taskControl.Hold()
 	// ----------------------------------------
 	// Emby 过滤,连续剧
+	seriesProcess := func(ctx context.Context, inData interface{}) error {
+
+		scrabbleUpVideoSeriesEmbyInput := inData.(ScrabbleUpVideoSeriesEmbyInput)
+
+		oneSeasonInfo := scrabbleUpVideoSeriesEmbyInput.OneSeasonInfo
+		oneEpsMixInfo := scrabbleUpVideoSeriesEmbyInput.OneEpsMixInfo
+		// 首先需要找到对应的最长的视频媒体库路径,x://ABC  x://ABC/DEF
+		for _, oneSeriesDirPath := range sortSeriesPaths {
+
+			if strings.HasPrefix(oneEpsMixInfo.PhysicalVideoFileFullPath, oneSeriesDirPath.Path) {
+				// 匹配上了
+				v.processLocker.Lock()
+				desUrl, found := pathUrlMap[oneSeriesDirPath.Path]
+				if found == false {
+					v.processLocker.Unlock()
+					// 没有找到对应的 URL
+					continue
+				}
+				v.processLocker.Unlock()
+
+				videoFileName := filepath.Base(oneEpsMixInfo.PhysicalVideoFileFullPath)
+				infoFromFileName, err := decode.GetVideoInfoFromFileName(videoFileName)
+				if err != nil {
+					v.log.Errorln("GetVideoInfoFromFileName", err)
+					break
+				}
+				// 匹配上了前缀就替换这个,并记录
+				epsFUrl := strings.ReplaceAll(oneEpsMixInfo.PhysicalVideoFileFullPath, oneSeriesDirPath.Path, desUrl)
+				oneVideoInfo := backend.OneVideoInfo{
+					Name:                     videoFileName,
+					VideoFPath:               oneEpsMixInfo.PhysicalVideoFileFullPath,
+					VideoUrl:                 epsFUrl,
+					Season:                   infoFromFileName.Season,
+					Episode:                  infoFromFileName.Episode,
+					MediaServerInsideVideoID: oneEpsMixInfo.VideoInfo.Id,
+					SubFPathList:             make([]string, 0),
+				}
+
+				// 搜索字幕
+				matchedSubFileByOneVideo, err := sub_helper.SearchMatchedSubFileByOneVideo(v.log, oneEpsMixInfo.PhysicalVideoFileFullPath)
+				if err != nil {
+					v.log.Errorln("SearchMatchedSubFileByOneVideo", err)
+				}
+				matchedSubFileByOneVideoUrl := make([]string, 0)
+				for _, oneSubFPath := range matchedSubFileByOneVideo {
+					oneSubFUrl := strings.ReplaceAll(oneSubFPath, oneSeriesDirPath.Path, desUrl)
+					matchedSubFileByOneVideoUrl = append(matchedSubFileByOneVideoUrl, oneSubFUrl)
+				}
+				oneVideoInfo.SubFPathList = append(oneVideoInfo.SubFPathList, matchedSubFileByOneVideoUrl...)
+
+				v.processLocker.Lock()
+				oneSeasonInfo.OneVideoInfos = append(oneSeasonInfo.OneVideoInfos, oneVideoInfo)
+				v.processLocker.Unlock()
+
+				break
+			}
+		}
+		return nil
+	}
+	// ----------------------------------------
+	v.taskControl.SetCtxProcessFunc("updateLocalVideoCacheInfo", seriesProcess, common.ScanPlayedSubTimeOut)
+	// ----------------------------------------
 	for seriesName, oneSeriesMixInfo := range emby.SeriesSubNeedDlEmbyMixInfoMap {
 
-		firstTime := true
 		var oneSeasonInfo backend.SeasonInfo
+		// 需要先得到 oneSeasonInfo 的信息
 		for _, oneEpsMixInfo := range oneSeriesMixInfo {
 
 			if oneEpsMixInfo.PhysicalVideoFileFullPath == "" {
 				continue
 			}
-
 			// 首先需要找到对应的最长的视频媒体库路径,x://ABC  x://ABC/DEF
 			for _, oneSeriesDirPath := range sortSeriesPaths {
 
-				if strings.HasPrefix(oneEpsMixInfo.PhysicalVideoFileFullPath, oneSeriesDirPath.Path) {
-					// 匹配上了
-					desUrl, found := pathUrlMap[oneSeriesDirPath.Path]
-					if found == false {
-						// 没有找到对应的 URL
-						continue
-					}
-
-					dirRootUrl := strings.ReplaceAll(oneEpsMixInfo.PhysicalSeriesRootDir, oneSeriesDirPath.Path, desUrl)
-					if firstTime == true {
-						oneSeasonInfo = backend.SeasonInfo{
-							Name:          seriesName,
-							RootDirPath:   oneEpsMixInfo.PhysicalSeriesRootDir,
-							DirRootUrl:    dirRootUrl,
-							OneVideoInfos: make([]backend.OneVideoInfo, 0),
-						}
-						firstTime = false
-					}
-
-					videoFileName := filepath.Base(oneEpsMixInfo.PhysicalVideoFileFullPath)
-					infoFromFileName, err := decode.GetVideoInfoFromFileName(videoFileName)
-					if err != nil {
-						v.log.Errorln("GetVideoInfoFromFileName", err)
-						break
-					}
-					// 匹配上了前缀就替换这个,并记录
-					epsFUrl := strings.ReplaceAll(oneEpsMixInfo.PhysicalVideoFileFullPath, oneSeriesDirPath.Path, desUrl)
-					oneVideoInfo := backend.OneVideoInfo{
-						Name:                     videoFileName,
-						VideoFPath:               oneEpsMixInfo.PhysicalVideoFileFullPath,
-						VideoUrl:                 epsFUrl,
-						Season:                   infoFromFileName.Season,
-						Episode:                  infoFromFileName.Episode,
-						MediaServerInsideVideoID: oneEpsMixInfo.VideoInfo.Id,
-					}
-					oneSeasonInfo.OneVideoInfos = append(oneSeasonInfo.OneVideoInfos, oneVideoInfo)
-					break
+				// 匹配上了
+				desUrl, found := pathUrlMap[oneSeriesDirPath.Path]
+				if found == false {
+					// 没有找到对应的 URL
+					continue
+				}
+				dirRootUrl := strings.ReplaceAll(oneEpsMixInfo.PhysicalSeriesRootDir, oneSeriesDirPath.Path, desUrl)
+
+				oneSeasonInfo = backend.SeasonInfo{
+					Name:          seriesName,
+					RootDirPath:   oneEpsMixInfo.PhysicalSeriesRootDir,
+					DirRootUrl:    dirRootUrl,
+					OneVideoInfos: make([]backend.OneVideoInfo, 0),
 				}
+				break
+			}
+			if oneSeasonInfo.Name != "" {
+				// 这个结构初始化过了
+				break
+			}
+		}
+
+		if oneSeasonInfo.Name == "" {
+			// 说明找了一圈没有找到匹配的,那么后续的也没必要继续
+			continue
+		}
+
+		// 然后再开始处理每一集的信息
+		for i, oneEpsMixInfo := range oneSeriesMixInfo {
+
+			if oneEpsMixInfo.PhysicalVideoFileFullPath == "" {
+				continue
+			}
+
+			// 放入队列
+			err := v.taskControl.Invoke(&task_control.TaskData{
+				Index: i,
+				Count: len(oneSeriesMixInfo),
+				DataEx: ScrabbleUpVideoSeriesEmbyInput{
+					OneSeasonInfo: &oneSeasonInfo,
+					OneEpsMixInfo: oneEpsMixInfo,
+				},
+			})
+			if err != nil {
+				v.log.Errorln(err)
+				break
 			}
 		}
 	}
@@ -803,3 +1011,22 @@ type TaskInputData struct {
 	Index     int
 	InputPath string
 }
+
+type ScrabbleUpVideoMovieNormalInput struct {
+	OneMovieDirRootPath string
+	OneMovieFPath       string
+}
+
+type ScrabbleUpVideoSeriesNormalInput struct {
+	OneSeriesRootDir      string
+	OneSeriesRootPathName string
+}
+
+type ScrabbleUpVideoMovieEmbyInput struct {
+	OneMovieMixInfo emby.EmbyMixInfo
+}
+
+type ScrabbleUpVideoSeriesEmbyInput struct {
+	OneSeasonInfo *backend.SeasonInfo
+	OneEpsMixInfo emby.EmbyMixInfo
+}

+ 6 - 5
internal/types/backend/reply_movie_list.go

@@ -1,9 +1,10 @@
 package backend
 
 type MovieInfo struct {
-	Name                     string `json:"name"`
-	DirRootUrl               string `json:"dir_root_url"`
-	VideoFPath               string `json:"video_f_path"`
-	VideoUrl                 string `json:"video_url"`
-	MediaServerInsideVideoID string `json:"media_server_inside_video_id"`
+	Name                     string   `json:"name"`
+	DirRootUrl               string   `json:"dir_root_url"`
+	VideoFPath               string   `json:"video_f_path"`
+	VideoUrl                 string   `json:"video_url"`
+	MediaServerInsideVideoID string   `json:"media_server_inside_video_id"`
+	SubFPathList             []string `json:"sub_f_path_list"`
 }

+ 7 - 6
internal/types/backend/reply_series_list.go

@@ -8,10 +8,11 @@ type SeasonInfo struct {
 }
 
 type OneVideoInfo struct {
-	Name                     string `json:"name"`
-	VideoFPath               string `json:"video_f_path"`
-	VideoUrl                 string `json:"video_url"`
-	Season                   int    `json:"season"`
-	Episode                  int    `json:"episode"`
-	MediaServerInsideVideoID string `json:"media_server_inside_video_id"`
+	Name                     string   `json:"name"`
+	VideoFPath               string   `json:"video_f_path"`
+	VideoUrl                 string   `json:"video_url"`
+	Season                   int      `json:"season"`
+	Episode                  int      `json:"episode"`
+	SubFPathList             []string `json:"sub_f_path_list"`
+	MediaServerInsideVideoID string   `json:"media_server_inside_video_id"`
 }