| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- package sub_helper
- import (
- "github.com/allanpk716/ChineseSubFinder/internal/common"
- "github.com/allanpk716/ChineseSubFinder/internal/pkg"
- "github.com/allanpk716/ChineseSubFinder/internal/pkg/archive_helper"
- "github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
- "github.com/allanpk716/ChineseSubFinder/internal/pkg/language"
- "github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
- "github.com/allanpk716/ChineseSubFinder/internal/types"
- "github.com/allanpk716/ChineseSubFinder/internal/types/subparser"
- "github.com/allanpk716/ChineseSubFinder/internal/types/supplier"
- "github.com/go-rod/rod/lib/utils"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- )
- // OrganizeDlSubFiles 需要从汇总来是网站字幕中,解压对应的压缩包中的字幕出来
- func OrganizeDlSubFiles(tmpFolderName string, subInfos []supplier.SubInfo) (map[string][]string, error) {
- // 缓存列表,整理后的字幕列表
- // SxEx - []string 字幕的路径
- var siteSubInfoDict = make(map[string][]string)
- tmpFolderFullPath, err := pkg.GetTmpFolder(tmpFolderName)
- if err != nil {
- return nil, err
- }
- // 把后缀名给改好
- ChangeVideoExt2SubExt(subInfos)
- // 第三方的解压库,首先不支持 io.Reader 的操作,也就是得缓存到本地硬盘再读取解压
- // 且使用 walk 会无法解压 rar,得指定具体的实例,太麻烦了,直接用通用的接口得了,就是得都缓存下来再判断
- // 基于以上两点,写了一堆啰嗦的逻辑···
- for i := range subInfos {
- // 先存下来,保存是时候需要前缀,前缀就是从那个网站下载来的
- nowFileSaveFullPath := path.Join(tmpFolderFullPath, GetFrontNameAndOrgName(&subInfos[i]))
- err = utils.OutputFile(nowFileSaveFullPath, subInfos[i].Data)
- if err != nil {
- log_helper.GetLogger().Errorln("getFrontNameAndOrgName - OutputFile", subInfos[i].FromWhere, subInfos[i].Name, subInfos[i].TopN, err)
- continue
- }
- nowExt := strings.ToLower(subInfos[i].Ext)
- epsKey := pkg.GetEpisodeKeyName(subInfos[i].Season, subInfos[i].Episode)
- _, ok := siteSubInfoDict[epsKey]
- if ok == false {
- // 不存在则实例化
- siteSubInfoDict[epsKey] = make([]string, 0)
- }
- if nowExt != ".zip" && nowExt != ".tar" && nowExt != ".rar" && nowExt != ".7z" {
- // 是否是受支持的字幕类型
- if IsSubExtWanted(nowExt) == false {
- continue
- }
- // 加入缓存列表
- siteSubInfoDict[epsKey] = append(siteSubInfoDict[epsKey], nowFileSaveFullPath)
- } else {
- // 那么就是需要解压的文件了
- // 解压,给一个单独的文件夹
- unzipTmpFolder := path.Join(tmpFolderFullPath, subInfos[i].FromWhere)
- err = os.MkdirAll(unzipTmpFolder, os.ModePerm)
- if err != nil {
- return nil, err
- }
- err = archive_helper.UnArchiveFile(nowFileSaveFullPath, unzipTmpFolder)
- // 解压完成后,遍历受支持的字幕列表,加入缓存列表
- if err != nil {
- log_helper.GetLogger().Errorln("archiver.UnArchive", subInfos[i].FromWhere, subInfos[i].Name, subInfos[i].TopN, err)
- continue
- }
- // 搜索这个目录下的所有符合字幕格式的文件
- subFileFullPaths, err := SearchMatchedSubFile(unzipTmpFolder)
- if err != nil {
- log_helper.GetLogger().Errorln("searchMatchedSubFile", subInfos[i].FromWhere, subInfos[i].Name, subInfos[i].TopN, err)
- continue
- }
- // 这里需要给这些下载到的文件进行改名,加是从那个网站来的前缀,后续好查找
- for _, fileFullPath := range subFileFullPaths {
- newSubName := AddFrontName(subInfos[i], filepath.Base(fileFullPath))
- newSubNameFullPath := path.Join(tmpFolderFullPath, newSubName)
- // 改名
- err = os.Rename(fileFullPath, newSubNameFullPath)
- if err != nil {
- log_helper.GetLogger().Errorln("os.Rename", subInfos[i].FromWhere, subInfos[i].Name, subInfos[i].TopN, err)
- continue
- }
- // 加入缓存列表
- siteSubInfoDict[epsKey] = append(siteSubInfoDict[epsKey], newSubNameFullPath)
- }
- }
- }
- return siteSubInfoDict, nil
- }
- // ChangeVideoExt2SubExt 检测 Name,如果是视频的后缀名就改为字幕的后缀名
- func ChangeVideoExt2SubExt(subInfos []supplier.SubInfo) {
- for x, info := range subInfos {
- tmpSubFileName := info.Name
- // 如果后缀名是下载字幕目标的后缀名 或者 是压缩包格式的,则跳过
- if strings.Contains(tmpSubFileName, info.Ext) == true || archive_helper.IsWantedArchiveExtName(tmpSubFileName) == true {
- } else {
- subInfos[x].Name = tmpSubFileName + info.Ext
- }
- }
- }
- // SelectChineseBestBilingualSubtitle 找到合适的双语中文字幕,简体->繁体,以及 字幕类型的优先级选择
- func SelectChineseBestBilingualSubtitle(subs []subparser.FileInfo, subTypePriority int) *subparser.FileInfo {
- // 先傻一点实现优先双语的,之前的写法有 bug
- for _, info := range subs {
- // 找到了中文字幕
- if language.HasChineseLang(info.Lang) == true {
- // 字幕的优先级 0 - 原样, 1 - srt , 2 - ass/ssa
- if subTypePriority == 1 {
- // 1 - srt
- if strings.ToLower(info.Ext) == common.SubExtSRT {
- // 优先双语
- if language.IsBilingualSubtitle(info.Lang) == true {
- return &info
- }
- }
- } else if subTypePriority == 2 {
- // 2 - ass/ssa
- if strings.ToLower(info.Ext) == common.SubExtASS || strings.ToLower(info.Ext) == common.SubExtSSA {
- // 优先双语
- if language.IsBilingualSubtitle(info.Lang) == true {
- return &info
- }
- }
- } else {
- // 优先双语
- if language.IsBilingualSubtitle(info.Lang) == true {
- return &info
- }
- }
- }
- }
- return nil
- }
- // SelectChineseBestSubtitle 找到合适的中文字幕,简体->繁体,以及 字幕类型的优先级选择
- func SelectChineseBestSubtitle(subs []subparser.FileInfo, subTypePriority int) *subparser.FileInfo {
- // 先傻一点实现优先双语的,之前的写法有 bug
- for _, info := range subs {
- // 找到了中文字幕
- if language.HasChineseLang(info.Lang) == true {
- // 字幕的优先级 0 - 原样, 1 - srt , 2 - ass/ssa
- if subTypePriority == 1 {
- // 1 - srt
- if strings.ToLower(info.Ext) == common.SubExtSRT {
- return &info
- }
- } else if subTypePriority == 2 {
- // 2 - ass/ssa
- if strings.ToLower(info.Ext) == common.SubExtASS || strings.ToLower(info.Ext) == common.SubExtSSA {
- return &info
- }
- } else {
- return &info
- }
- }
- }
- return nil
- }
- // GetFrontNameAndOrgName 返回的名称包含,那个网站下载的,这个网站中排名第几,文件名
- func GetFrontNameAndOrgName(info *supplier.SubInfo) string {
- infoName := ""
- fileName, err := decode.GetVideoInfoFromFileName(info.Name)
- if err != nil {
- log_helper.GetLogger().Warnln("", err)
- infoName = info.Name
- } else {
- infoName = fileName.Title + "_S" + strconv.Itoa(fileName.Season) + "E" + strconv.Itoa(fileName.Episode) + filepath.Ext(info.Name)
- }
- info.Name = infoName
- return "[" + info.FromWhere + "]_" + strconv.FormatInt(info.TopN, 10) + "_" + infoName
- }
- // AddFrontName 添加文件的前缀
- func AddFrontName(info supplier.SubInfo, orgName string) string {
- return "[" + info.FromWhere + "]_" + strconv.FormatInt(info.TopN, 10) + "_" + orgName
- }
- // SearchMatchedSubFile 搜索符合后缀名的视频文件,排除 Sub_SxE0 这样的文件夹中的文件
- func SearchMatchedSubFile(dir string) ([]string, error) {
- // 这里有个梗,会出现 __MACOSX 这类文件夹,那么里面会有一样的文件,需要用文件大小排除一下,至少大于 1 kb 吧
- var fileFullPathList = make([]string, 0)
- pathSep := string(os.PathSeparator)
- files, err := ioutil.ReadDir(dir)
- if err != nil {
- return nil, err
- }
- for _, curFile := range files {
- fullPath := dir + pathSep + curFile.Name()
- if curFile.IsDir() {
- // 需要排除 Sub_S1E0、Sub_S2E0 这样的整季的字幕文件夹,这里仅仅是缓存,不会被加载的
- matched := regOneSeasonSubFolderNameMatch.FindAllStringSubmatch(curFile.Name(), -1)
- if len(matched) > 0 {
- continue
- }
- // 内层的错误就无视了
- oneList, _ := SearchMatchedSubFile(fullPath)
- if oneList != nil {
- fileFullPathList = append(fileFullPathList, oneList...)
- }
- } else {
- // 这里就是文件了
- if curFile.Size() < 1000 {
- continue
- }
- if IsSubExtWanted(filepath.Ext(curFile.Name())) == true {
- fileFullPathList = append(fileFullPathList, fullPath)
- }
- }
- }
- return fileFullPathList, nil
- }
- // SearchVideoMatchSubFileAndRemoveDefaultMark 找到找个视频目录下相匹配的字幕,同时去除这些字幕中 .default 的标记
- func SearchVideoMatchSubFileAndRemoveDefaultMark(oneVideoFullPath string) error {
- dir := filepath.Dir(oneVideoFullPath)
- fileName := filepath.Base(oneVideoFullPath)
- fileName = strings.ToLower(fileName)
- fileName = strings.ReplaceAll(fileName, filepath.Ext(fileName), "")
- pathSep := string(os.PathSeparator)
- files, err := ioutil.ReadDir(dir)
- if err != nil {
- return err
- }
- for _, curFile := range files {
- if curFile.IsDir() {
- continue
- } else {
- // 这里就是文件了
- if curFile.Size() < 1000 {
- continue
- }
- // 判断的时候用小写的,后续重命名的时候用原有的名称
- nowFileName := strings.ToLower(curFile.Name())
- // 后缀名得对
- if IsSubExtWanted(filepath.Ext(nowFileName)) == false {
- continue
- }
- // 字幕文件名应该包含 视频文件名(无后缀)
- if strings.Contains(nowFileName, fileName) == false {
- continue
- }
- // 得包含 .default. 找个关键词
- if strings.Contains(nowFileName, types.Emby_default+".") == false {
- continue
- }
- oldPath := dir + pathSep + curFile.Name()
- newPath := dir + pathSep + strings.ReplaceAll(curFile.Name(), types.Emby_default+".", ".")
- err = os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- // IsOldVersionSubPrefixName 是否是老版本的字幕命名 .chs_en[shooter] ,符合也返回这个部分+字幕格式后缀名 .chs_en[shooter].ass, 修改后的名称
- func IsOldVersionSubPrefixName(subFileName string) (bool, string, string) {
- /*
- 传入的必须是字幕格式的文件,这个就再之前判断,不要在这里再判断
- 传入的文件名可能有一下几种情况
- 无罪之最 - S01E01 - 重建生活.chs[shooter].ass
- 无罪之最 - S01E03 - 初见端倪.zh.srt
- Loki - S01E01 - Glorious Purpose WEBDL-1080p Proper.chs_en.ass
- 那么就需要先剔除,字幕的格式后缀名,然后再向后取后缀名就是 .chs[shooter] or .zh
- 再判断即可
- */
- // 无罪之最 - S01E01 - 重建生活.chs[shooter].ass -> 无罪之最 - S01E01 - 重建生活.chs[shooter]
- subTypeExt := filepath.Ext(subFileName)
- subFileNameWithOutExt := strings.ReplaceAll(subFileName, subTypeExt, "")
- // .chs[shooter]
- nowExt := filepath.Ext(subFileNameWithOutExt)
- // .chs_en[shooter].ass
- orgMixExt := nowExt + subTypeExt
- orgFileNameWithOutOrgMixExt := strings.ReplaceAll(subFileName, orgMixExt, "")
- // 这里也有两种情况,一种是单字幕 SaveMultiSub: false
- // 一种的保存了多字幕 SaveMultiSub: true
- // 先判断 单字幕
- switch nowExt {
- case types.Emby_chs:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangChs, subTypeExt, "", true)
- case types.Emby_cht:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangCht, subTypeExt, "", false)
- case types.Emby_chs_en:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangChsEn, subTypeExt, "", true)
- case types.Emby_cht_en:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangChtEn, subTypeExt, "", false)
- case types.Emby_chs_jp:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangChsJp, subTypeExt, "", true)
- case types.Emby_cht_jp:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangChtJp, subTypeExt, "", false)
- case types.Emby_chs_kr:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangChsKr, subTypeExt, "", true)
- case types.Emby_cht_kr:
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, types.MatchLangChtKr, subTypeExt, "", false)
- }
- // 再判断 多字幕情况
- spStrings := strings.Split(nowExt, "[")
- if len(spStrings) != 2 {
- return false, "", ""
- }
- // 分两段来判断是否符合标准
- // 第一段
- firstOk := true
- lang := types.MatchLangChs
- site := ""
- switch spStrings[0] {
- case types.Emby_chs:
- lang = types.MatchLangChs
- case types.Emby_cht:
- lang = types.MatchLangCht
- case types.Emby_chs_en:
- lang = types.MatchLangChsEn
- case types.Emby_cht_en:
- lang = types.MatchLangChtEn
- case types.Emby_chs_jp:
- lang = types.MatchLangChsJp
- case types.Emby_cht_jp:
- lang = types.MatchLangChtJp
- case types.Emby_chs_kr:
- lang = types.MatchLangChsKr
- case types.Emby_cht_kr:
- lang = types.MatchLangChtKr
- default:
- firstOk = false
- }
- // 第二段
- secondOk := true
- tmpSecond := strings.ReplaceAll(spStrings[1], "]", "")
- switch tmpSecond {
- case common.SubSiteZiMuKu:
- site = common.SubSiteZiMuKu
- case common.SubSiteSubHd:
- site = common.SubSiteSubHd
- case common.SubSiteShooter:
- site = common.SubSiteShooter
- case common.SubSiteXunLei:
- site = common.SubSiteXunLei
- default:
- secondOk = false
- }
- // 都要符合条件
- if firstOk == true && secondOk == true {
- return true, orgMixExt, makeMixSubExtString(orgFileNameWithOutOrgMixExt, lang, subTypeExt, site, false)
- }
- return false, "", ""
- }
- // GenerateMixSubName 这里会生成类似的文件名 xxxx.chinese(中英,shooter)
- func GenerateMixSubName(videoFileName, subExt string, subLang types.Language, extraSubPreName string) (string, string, string) {
- videoFileNameWithOutExt := strings.ReplaceAll(filepath.Base(videoFileName),
- filepath.Ext(videoFileName), "")
- note := ""
- // extraSubPreName 那个字幕网站下载的
- if extraSubPreName != "" {
- note = "," + extraSubPreName
- }
- defaultString := ".default"
- forcedString := ".forced"
- subNewName := videoFileNameWithOutExt + ".chinese" + "(" + language.Lang2ChineseString(subLang) + note + ")" + subExt
- subNewNameWithDefault := videoFileNameWithOutExt + ".chinese" + "(" + language.Lang2ChineseString(subLang) + note + ")" + defaultString + subExt
- subNewNameWithForced := videoFileNameWithOutExt + ".chinese" + "(" + language.Lang2ChineseString(subLang) + note + ")" + forcedString + subExt
- return subNewName, subNewNameWithDefault, subNewNameWithForced
- }
- func makeMixSubExtString(orgFileNameWithOutExt, lang string, ext, site string, beDefault bool) string {
- tmpDefault := ""
- if beDefault == true {
- tmpDefault = types.Emby_default
- }
- if site == "" {
- return orgFileNameWithOutExt + types.Emby_chinese + "(" + lang + ")" + tmpDefault + ext
- }
- return orgFileNameWithOutExt + types.Emby_chinese + "(" + lang + "," + site + ")" + tmpDefault + ext
- }
- // DeleteOneSeasonSubCacheFolder 删除一个连续剧中的所有一季字幕的缓存文件夹
- func DeleteOneSeasonSubCacheFolder(seriesDir string) error {
- files, err := ioutil.ReadDir(seriesDir)
- if err != nil {
- return err
- }
- pathSep := string(os.PathSeparator)
- for _, curFile := range files {
- if curFile.IsDir() == true {
- matched := regOneSeasonSubFolderNameMatch.FindAllStringSubmatch(curFile.Name(), -1)
- if matched == nil || len(matched) < 1 {
- continue
- }
- fullPath := seriesDir + pathSep + curFile.Name()
- err = os.RemoveAll(fullPath)
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- var (
- regOneSeasonSubFolderNameMatch = regexp.MustCompile(`(?m)^Sub_S\dE0`)
- )
|