embyhelper.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. package emby_helper
  2. import (
  3. "fmt"
  4. "github.com/allanpk716/ChineseSubFinder/internal/common"
  5. embyHelper "github.com/allanpk716/ChineseSubFinder/internal/pkg/emby_api"
  6. "github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
  7. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  8. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  9. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
  10. "github.com/allanpk716/ChineseSubFinder/internal/types/emby"
  11. "github.com/allanpk716/ChineseSubFinder/internal/types/language"
  12. "github.com/panjf2000/ants/v2"
  13. "golang.org/x/net/context"
  14. "path"
  15. "path/filepath"
  16. "sort"
  17. "strings"
  18. "sync"
  19. "time"
  20. )
  21. type EmbyHelper struct {
  22. embyApi *embyHelper.EmbyApi
  23. EmbyConfig settings.EmbySettings
  24. threads int
  25. timeOut time.Duration
  26. listLock sync.Mutex
  27. }
  28. func NewEmbyHelper(embyConfig settings.EmbySettings) *EmbyHelper {
  29. em := EmbyHelper{EmbyConfig: embyConfig}
  30. em.embyApi = embyHelper.NewEmbyApi(embyConfig)
  31. em.threads = 6
  32. em.timeOut = 60 * time.Second
  33. return &em
  34. }
  35. func (em *EmbyHelper) GetRecentlyAddVideoList() ([]emby.EmbyMixInfo, map[string][]emby.EmbyMixInfo, error) {
  36. // 获取最近的影片列表
  37. items, err := em.embyApi.GetRecentlyItems()
  38. if err != nil {
  39. return nil, nil, err
  40. }
  41. // 获取电影和连续剧的文件夹名称
  42. var EpisodeIdList = make([]string, 0)
  43. var MovieIdList = make([]string, 0)
  44. log_helper.GetLogger().Debugln("-----------------")
  45. log_helper.GetLogger().Debugln("GetRecentlyAddVideoList - GetRecentlyItems Count", len(items.Items))
  46. // 分类
  47. for index, item := range items.Items {
  48. if item.Type == videoTypeEpisode {
  49. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  50. EpisodeIdList = append(EpisodeIdList, item.Id)
  51. log_helper.GetLogger().Debugln("Episode:", index, item.SeriesName, item.ParentIndexNumber, item.IndexNumber)
  52. } else if item.Type == videoTypeMovie {
  53. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  54. MovieIdList = append(MovieIdList, item.Id)
  55. log_helper.GetLogger().Debugln("Movie:", index, item.Name)
  56. } else {
  57. log_helper.GetLogger().Debugln("GetRecentlyItems - Is not a goal video type:", index, item.Name, item.Type)
  58. }
  59. }
  60. // 过滤出有效的电影、连续剧的资源出来
  61. filterMovieList, err := em.filterEmbyVideoList(MovieIdList, true)
  62. if err != nil {
  63. return nil, nil, err
  64. }
  65. filterSeriesList, err := em.filterEmbyVideoList(EpisodeIdList, false)
  66. if err != nil {
  67. return nil, nil, err
  68. }
  69. // 输出调试信息
  70. log_helper.GetLogger().Debugln("-----------------")
  71. log_helper.GetLogger().Debugln("filterEmbyVideoList found valid movie", len(filterMovieList))
  72. for index, info := range filterMovieList {
  73. log_helper.GetLogger().Debugln(index, info.VideoFileName)
  74. }
  75. log_helper.GetLogger().Debugln("-----------------")
  76. log_helper.GetLogger().Debugln("filterEmbyVideoList found valid series", len(filterSeriesList))
  77. for index, info := range filterSeriesList {
  78. log_helper.GetLogger().Debugln(index, info.VideoFileName)
  79. }
  80. log_helper.GetLogger().Debugln("-----------------")
  81. // 将没有字幕的找出来
  82. noSubMovieList, err := em.filterNoChineseSubVideoList(filterMovieList)
  83. if err != nil {
  84. return nil, nil, err
  85. }
  86. log_helper.GetLogger().Debugln("-----------------")
  87. noSubSeriesList, err := em.filterNoChineseSubVideoList(filterSeriesList)
  88. if err != nil {
  89. return nil, nil, err
  90. }
  91. // 输出调试信息
  92. log_helper.GetLogger().Debugln("-----------------")
  93. log_helper.GetLogger().Debugln("filterNoChineseSubVideoList found no chinese movie", len(noSubMovieList))
  94. for index, info := range filterMovieList {
  95. log_helper.GetLogger().Debugln(index, info.VideoFileName)
  96. }
  97. log_helper.GetLogger().Debugln("-----------------")
  98. log_helper.GetLogger().Debugln("filterNoChineseSubVideoList found no chinese series", len(noSubSeriesList))
  99. for index, info := range filterSeriesList {
  100. log_helper.GetLogger().Debugln(index, info.VideoFileName)
  101. }
  102. // 需要将连续剧零散的每一集,进行合并到一个连续剧下面,也就是这个连续剧有那些需要更新的
  103. var seriesMap = make(map[string][]emby.EmbyMixInfo)
  104. for _, info := range noSubSeriesList {
  105. _, ok := seriesMap[info.VideoFolderName]
  106. if ok == false {
  107. // 不存在则新建初始化
  108. seriesMap[info.VideoFolderName] = make([]emby.EmbyMixInfo, 0)
  109. }
  110. seriesMap[info.VideoFolderName] = append(seriesMap[info.VideoFolderName], info)
  111. }
  112. return noSubMovieList, seriesMap, nil
  113. }
  114. // RefreshEmbySubList 字幕下载完毕一次,就可以触发一次这个。并发 6 线程去刷新
  115. func (em *EmbyHelper) RefreshEmbySubList() (bool, error) {
  116. if em.embyApi == nil {
  117. return false, nil
  118. }
  119. err := em.embyApi.RefreshRecentlyVideoInfo()
  120. if err != nil {
  121. return false, err
  122. }
  123. return true, nil
  124. }
  125. // findMappingPath 从 Emby 内置路径匹配到物理路径
  126. // X:\电影 - /mnt/share1/电影
  127. // X:\连续剧 - /mnt/share1/连续剧
  128. func (em *EmbyHelper) findMappingPath(mixInfo *emby.EmbyMixInfo, isMovieOrSeries bool) bool {
  129. // 这里进行路径匹配的时候需要考虑嵌套路径的问题
  130. // 比如,映射了 /电影 以及 /电影/AA ,那么如果有一部电影 /电影/AA/xx/xx.mkv 那么,应该匹配的是最长的路径 /电影/AA
  131. matchedEmbyPaths := make([]string, 0)
  132. if isMovieOrSeries == true {
  133. // 电影的情况
  134. for _, embyPath := range em.EmbyConfig.MoviePathsMapping {
  135. if strings.HasPrefix(mixInfo.VideoInfo.Path, embyPath) == true {
  136. matchedEmbyPaths = append(matchedEmbyPaths, embyPath)
  137. }
  138. }
  139. } else {
  140. // 连续剧的情况
  141. for _, embyPath := range em.EmbyConfig.SeriesPathsMapping {
  142. if strings.HasPrefix(mixInfo.VideoInfo.Path, embyPath) == true {
  143. matchedEmbyPaths = append(matchedEmbyPaths, embyPath)
  144. }
  145. }
  146. }
  147. if len(matchedEmbyPaths) < 1 {
  148. return false
  149. }
  150. // 排序得到匹配上的路径,最长的那个
  151. pathSlices := sortStringSliceByLength(matchedEmbyPaths)
  152. // 然后还需要从这个最长的路径,从 map 中找到对应的物理路径
  153. // nowPhRootPath 这个路径是映射的根目录,如果里面再次嵌套 子文件夹 再到连续剧目录,则是个问题,会丢失子文件夹目录
  154. nowPhRootPath := ""
  155. if isMovieOrSeries == true {
  156. // 电影的情况
  157. for physicalPath, embyPath := range em.EmbyConfig.MoviePathsMapping {
  158. if embyPath == pathSlices[0].Path {
  159. nowPhRootPath = physicalPath
  160. break
  161. }
  162. }
  163. } else {
  164. // 连续剧的情况
  165. for physicalPath, embyPath := range em.EmbyConfig.SeriesPathsMapping {
  166. if embyPath == pathSlices[0].Path {
  167. nowPhRootPath = physicalPath
  168. break
  169. }
  170. }
  171. }
  172. // 如果匹配不上
  173. if nowPhRootPath == "" {
  174. return false
  175. }
  176. if isMovieOrSeries == true {
  177. // 电影
  178. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, pathSlices[0].Path, nowPhRootPath)
  179. // 因为电影搜索的时候使用的是完整的视频目录,所以这个字段并不重要
  180. //mixInfo.PhysicalRootPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, pathSlices[0].Path, nowPhRootPath)
  181. // 这个电影的文件夹
  182. mixInfo.VideoFolderName = filepath.Base(filepath.Dir(mixInfo.VideoInfo.Path))
  183. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path)
  184. } else {
  185. // 连续剧
  186. ancestorIndex := -1
  187. // 找到连续剧文件夹这一层
  188. for i, ancestor := range mixInfo.Ancestors {
  189. if ancestor.Type == "Series" {
  190. ancestorIndex = i
  191. break
  192. }
  193. }
  194. if ancestorIndex == -1 {
  195. // 说明没有找到连续剧文件夹的名称,那么就应该跳过
  196. return false
  197. }
  198. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, pathSlices[0].Path, nowPhRootPath)
  199. mixInfo.PhysicalRootPath = strings.ReplaceAll(mixInfo.Ancestors[ancestorIndex+1].Path, pathSlices[0].Path, nowPhRootPath)
  200. // 这个剧集的文件夹
  201. mixInfo.VideoFolderName = filepath.Base(mixInfo.Ancestors[ancestorIndex].Path)
  202. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path)
  203. }
  204. return true
  205. }
  206. func (em *EmbyHelper) filterEmbyVideoList(videoIdList []string, isMovieOrSeries bool) ([]emby.EmbyMixInfo, error) {
  207. var filterVideoEmbyInfo = make([]emby.EmbyMixInfo, 0)
  208. queryFunc := func(m string) (*emby.EmbyMixInfo, error) {
  209. info, err := em.embyApi.GetItemVideoInfo(m)
  210. if err != nil {
  211. return nil, err
  212. }
  213. ancs, err := em.embyApi.GetItemAncestors(m)
  214. if err != nil {
  215. return nil, err
  216. }
  217. mixInfo := emby.EmbyMixInfo{Ancestors: ancs, VideoInfo: info}
  218. if isMovieOrSeries == true {
  219. // 电影
  220. // 过滤掉不符合要求的,拼接绝对路径
  221. isFit := em.findMappingPath(&mixInfo, isMovieOrSeries)
  222. if isFit == false {
  223. return nil, err
  224. }
  225. } else {
  226. // 连续剧
  227. // 过滤掉不符合要求的,拼接绝对路径
  228. isFit := em.findMappingPath(&mixInfo, isMovieOrSeries)
  229. if isFit == false {
  230. return nil, err
  231. }
  232. }
  233. return &mixInfo, nil
  234. }
  235. p, err := ants.NewPoolWithFunc(em.threads, func(inData interface{}) {
  236. data := inData.(InputData)
  237. defer data.Wg.Done()
  238. ctx, cancel := context.WithTimeout(context.Background(), em.timeOut)
  239. defer cancel()
  240. done := make(chan OutData, 1)
  241. panicChan := make(chan interface{}, 1)
  242. go func() {
  243. defer func() {
  244. if p := recover(); p != nil {
  245. panicChan <- p
  246. }
  247. }()
  248. info, err := queryFunc(data.Id)
  249. outData := OutData{
  250. Info: info,
  251. Err: err,
  252. }
  253. done <- outData
  254. }()
  255. select {
  256. case outData := <-done:
  257. // 收到结果,需要加锁
  258. if outData.Err != nil {
  259. log_helper.GetLogger().Errorln("filterEmbyVideoList.NewPoolWithFunc got Err", outData.Err)
  260. return
  261. }
  262. if outData.Info == nil {
  263. return
  264. }
  265. em.listLock.Lock()
  266. filterVideoEmbyInfo = append(filterVideoEmbyInfo, *outData.Info)
  267. em.listLock.Unlock()
  268. return
  269. case p := <-panicChan:
  270. log_helper.GetLogger().Errorln("filterEmbyVideoList.NewPoolWithFunc got panic", p)
  271. case <-ctx.Done():
  272. log_helper.GetLogger().Errorln("filterEmbyVideoList.NewPoolWithFunc got time out", ctx.Err())
  273. return
  274. }
  275. })
  276. if err != nil {
  277. return nil, err
  278. }
  279. defer p.Release()
  280. wg := sync.WaitGroup{}
  281. // 获取视频的 Emby 信息
  282. for _, m := range videoIdList {
  283. wg.Add(1)
  284. err = p.Invoke(InputData{Id: m, Wg: &wg})
  285. if err != nil {
  286. log_helper.GetLogger().Errorln("filterEmbyVideoList ants.Invoke", err)
  287. }
  288. }
  289. wg.Wait()
  290. return filterVideoEmbyInfo, nil
  291. }
  292. func (em *EmbyHelper) filterNoChineseSubVideoList(videoList []emby.EmbyMixInfo) ([]emby.EmbyMixInfo, error) {
  293. currentTime := time.Now()
  294. dayRange3Months, _ := time.ParseDuration(common.DownloadSubDuring3Months)
  295. dayRange7Days, _ := time.ParseDuration(common.DownloadSubDuring7Days)
  296. var noSubVideoList = make([]emby.EmbyMixInfo, 0)
  297. // TODO 这里有一种情况需要考虑的,如果内置有中文的字幕,那么是否需要跳过,目前暂定的一定要有外置的字幕
  298. for _, info := range videoList {
  299. needDlSub3Month := false
  300. // 3个月内,或者没有字幕都要进行下载
  301. if info.VideoInfo.PremiereDate.Add(dayRange3Months).After(currentTime) == true {
  302. // 需要下载的
  303. needDlSub3Month = true
  304. }
  305. // 这个影片只要有一个符合字幕要求的,就可以跳过
  306. // 外置中文字幕
  307. haveExternalChineseSub := false
  308. for _, stream := range info.VideoInfo.MediaStreams {
  309. // 首先找到外置的字幕文件
  310. if stream.IsExternal == true && stream.IsTextSubtitleStream == true && stream.SupportsExternalStream == true {
  311. // 然后字幕的格式以及语言命名要符合本程序的定义,有字幕
  312. if sub_parser_hub.IsEmbySubCodecWanted(stream.Codec) == true &&
  313. sub_parser_hub.IsEmbySubChineseLangStringWanted(stream.Language) == true {
  314. haveExternalChineseSub = true
  315. break
  316. } else {
  317. continue
  318. }
  319. }
  320. }
  321. // 内置中文字幕
  322. haveInsideChineseSub := false
  323. for _, stream := range info.VideoInfo.MediaStreams {
  324. if stream.IsExternal == false &&
  325. sub_parser_hub.IsEmbySubChineseLangStringWanted(stream.Language) {
  326. haveInsideChineseSub = true
  327. break
  328. }
  329. }
  330. // 比如,创建的时间在3个月内,然后没有额外下载的中文字幕,都符合要求
  331. if haveExternalChineseSub == false {
  332. // 没有外置字幕
  333. // 如果创建了7天,且有内置的中文字幕,那么也不进行下载了
  334. if info.VideoInfo.DateCreated.Add(dayRange7Days).After(currentTime) == false && haveInsideChineseSub == true {
  335. log_helper.GetLogger().Debugln("Create Over 7 Days, And It Has Inside ChineseSub, Than Skip", info.VideoFileName)
  336. continue
  337. }
  338. //// 如果创建了三个月,还是没有字幕,那么也不进行下载了
  339. //if info.VideoInfo.DateCreated.Add(dayRange3Months).After(currentTime) == false {
  340. // continue
  341. //}
  342. // 没有中文字幕就加入下载列表
  343. noSubVideoList = append(noSubVideoList, info)
  344. } else {
  345. // 有外置字幕
  346. // 如果视频发布时间超过两年了,有字幕就直接跳过了,一般字幕稳定了
  347. if currentTime.Year()-2 > info.VideoInfo.PremiereDate.Year() {
  348. log_helper.GetLogger().Debugln("Create Over 2 Years, And It Has External ChineseSub, Than Skip", info.VideoFileName)
  349. continue
  350. }
  351. // 有中文字幕,且如果在三个月内,则需要继续下载字幕`
  352. if needDlSub3Month == true {
  353. noSubVideoList = append(noSubVideoList, info)
  354. }
  355. }
  356. }
  357. return noSubVideoList, nil
  358. }
  359. // GetInternalEngSubAndExChineseEnglishSub 获取对应 videoId 的内置英文字幕,外置中文字幕(只要是带有中文的都算,简体、繁体、简英、繁英,需要后续额外的判断)字幕
  360. func (em *EmbyHelper) GetInternalEngSubAndExChineseEnglishSub(videoId string) (bool, []emby.SubInfo, []emby.SubInfo, error) {
  361. // 先刷新以下这个资源,避免找到的字幕不存在了
  362. err := em.embyApi.UpdateVideoSubList(videoId)
  363. if err != nil {
  364. return false, nil, nil, err
  365. }
  366. // 获取这个资源的信息
  367. videoInfo, err := em.embyApi.GetItemVideoInfo(videoId)
  368. if err != nil {
  369. return false, nil, nil, err
  370. }
  371. // 获取 MediaSources ID,这里强制使用第一个视频源(因为 emby 运行有多个版本的视频指向到一个视频ID上,比如一个 web 一个 蓝光)
  372. mediaSourcesId := videoInfo.MediaSources[0].Id
  373. // 视频文件名称带后缀名
  374. videoFileName := filepath.Base(videoInfo.Path)
  375. videoFileNameWithOutExt := strings.ReplaceAll(videoFileName, path.Ext(videoFileName), "")
  376. // TODO 后续会新增一个功能,从视频中提取音频文件,然后识别转为字符,再进行与字幕的匹配
  377. // 获取是否有内置的英文字幕,如果没有则无需继续往下
  378. /*
  379. 这里有个梗,读取到的英文内置字幕很可能是残缺的,比如,基地 S01E04 Eng 第一个 Default Forced Sub,就不对,内容的 Dialogue 很少。
  380. 然后第二个 Eng 字幕才对。那么考虑到兼容性, 可能后续有短视频,也就不能简单的按 Dialogue 的多少去衡量。大概会做一个功能。方案有两个:
  381. 1. 读取到视频的总长度,然后再分析 Dialogue 的时间出现的部分与整体时间轴的占比,又或者是 Dialogue 之间的连续成都分析,这个有待测试。
  382. 2. 还有一个更加粗暴的方案,把所有的 Eng 都识别出来,然后找最多的 Dialogue 来做为正确的来使用(够粗暴吧)
  383. */
  384. var insideEngSUbIndexList = make([]int, 0)
  385. for _, stream := range videoInfo.MediaStreams {
  386. if stream.IsExternal == false && stream.Language == language.Emby_English_eng && stream.Codec == streamCodec {
  387. insideEngSUbIndexList = append(insideEngSUbIndexList, stream.Index)
  388. }
  389. }
  390. // 没有找到则跳过
  391. if len(insideEngSUbIndexList) == 0 {
  392. return false, nil, nil, nil
  393. }
  394. // 再内置英文字幕能找到的前提下,就可以先找中文的外置字幕,目前版本只能考虑双语字幕
  395. // 内置英文字幕,这里把 srt 和 ass 的都导出来
  396. var inSubList = make([]emby.SubInfo, 0)
  397. // 外置中文双语字幕
  398. var exSubList = make([]emby.SubInfo, 0)
  399. tmpFileNameWithOutExt := ""
  400. for _, stream := range videoInfo.MediaStreams {
  401. // 首先找到外置的字幕文件
  402. if stream.IsExternal == true && stream.IsTextSubtitleStream == true && stream.SupportsExternalStream == true {
  403. // 然后字幕的格式以及语言命名要符合本程序的定义,有字幕
  404. if sub_parser_hub.IsEmbySubCodecWanted(stream.Codec) == true &&
  405. sub_parser_hub.IsEmbySubChineseLangStringWanted(stream.Language) == true {
  406. tmpFileName := filepath.Base(stream.Path)
  407. // 去除 .default 或者 .forced
  408. //tmpFileName = strings.ReplaceAll(tmpFileName, subparser.Sub_Ext_Mark_Default, "")
  409. //tmpFileName = strings.ReplaceAll(tmpFileName, subparser.Sub_Ext_Mark_Forced, "")
  410. tmpFileNameWithOutExt = strings.ReplaceAll(tmpFileName, path.Ext(tmpFileName), "")
  411. exSubList = append(exSubList, *emby.NewSubInfo(tmpFileNameWithOutExt+"."+stream.Codec, "."+stream.Codec, stream.Index))
  412. } else {
  413. continue
  414. }
  415. }
  416. }
  417. // 没有找到则跳过
  418. if len(exSubList) == 0 {
  419. return false, nil, nil, nil
  420. }
  421. /*
  422. 把之前 Internal 英文字幕的 SubInfo 实例的信息补充完整
  423. 但是也不是绝对的,因为后续去 emby 下载字幕的时候,需要与外置字幕的后缀名一致
  424. 这里开始去下载字幕
  425. 先下载内置的文的
  426. 因为上面下载内置英文字幕的梗,所以,需要预先下载多个内置的英文字幕下来,用体积最大(相同后缀名)的那个来作为最后的输出
  427. */
  428. // 那么现在先下载相同格式(.srt)的两个字幕
  429. InsideEngSubIndex := 0
  430. if len(insideEngSUbIndexList) == 1 {
  431. // 如果就找到一个内置字幕,就默认这个
  432. InsideEngSubIndex = insideEngSUbIndexList[0]
  433. } else {
  434. // 如果找到不止一个就需要判断
  435. var tmpSubContentLenList = make([]int, 0)
  436. for _, index := range insideEngSUbIndexList {
  437. // TODO 这里默认是去 Emby 去拿字幕,但是其实可以缓存在视频文件同级的目录下,这样后续就无需多次下载了,毕竟每次下载都需要读取完整的视频
  438. subFileData, err := em.embyApi.GetSubFileData(videoId, mediaSourcesId, fmt.Sprintf("%d", index), common.SubExtSRT)
  439. if err != nil {
  440. return false, nil, nil, err
  441. }
  442. tmpSubContentLenList = append(tmpSubContentLenList, len(subFileData))
  443. }
  444. maxContentLen := -1
  445. for index, contentLen := range tmpSubContentLenList {
  446. if maxContentLen < contentLen {
  447. maxContentLen = contentLen
  448. InsideEngSubIndex = insideEngSUbIndexList[index]
  449. }
  450. }
  451. }
  452. // 这里才是下载最佳的那个字幕
  453. for i := 0; i < 2; i++ {
  454. tmpExt := common.SubExtSRT
  455. if i == 1 {
  456. tmpExt = common.SubExtASS
  457. }
  458. subFileData, err := em.embyApi.GetSubFileData(videoId, mediaSourcesId, fmt.Sprintf("%d", InsideEngSubIndex), tmpExt)
  459. if err != nil {
  460. return false, nil, nil, err
  461. }
  462. tmpInSubInfo := emby.NewSubInfo(videoFileNameWithOutExt+tmpExt, tmpExt, InsideEngSubIndex)
  463. tmpInSubInfo.Content = []byte(subFileData)
  464. inSubList = append(inSubList, *tmpInSubInfo)
  465. }
  466. // 再下载外置的
  467. for i, subInfo := range exSubList {
  468. subFileData, err := em.embyApi.GetSubFileData(videoId, mediaSourcesId, fmt.Sprintf("%d", subInfo.EmbyStreamIndex), subInfo.Ext)
  469. if err != nil {
  470. return false, nil, nil, err
  471. }
  472. exSubList[i].Content = []byte(subFileData)
  473. }
  474. return true, inSubList, exSubList, nil
  475. }
  476. func (em *EmbyHelper) CheckPath(pathType string) ([]string, error) {
  477. // 获取最近的影片列表
  478. items, err := em.embyApi.GetRecentlyItems()
  479. if err != nil {
  480. return nil, err
  481. }
  482. // 获取电影和连续剧的文件夹名称
  483. var EpisodeIdList = make([]string, 0)
  484. var MovieIdList = make([]string, 0)
  485. // 分类
  486. for index, item := range items.Items {
  487. if item.Type == videoTypeEpisode {
  488. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  489. EpisodeIdList = append(EpisodeIdList, item.Id)
  490. log_helper.GetLogger().Debugln("Episode:", index, item.SeriesName, item.ParentIndexNumber, item.IndexNumber)
  491. } else if item.Type == videoTypeMovie {
  492. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  493. MovieIdList = append(MovieIdList, item.Id)
  494. log_helper.GetLogger().Debugln("Movie:", index, item.Name)
  495. } else {
  496. log_helper.GetLogger().Debugln("GetRecentlyItems - Is not a goal video type:", index, item.Name, item.Type)
  497. }
  498. }
  499. outCount := 0
  500. outList := make([]string, 0)
  501. if pathType == "movie" {
  502. // 过滤出有效的电影、连续剧的资源出来
  503. filterMovieList, err := em.filterEmbyVideoList(MovieIdList, true)
  504. if err != nil {
  505. return nil, err
  506. }
  507. for _, info := range filterMovieList {
  508. if my_util.IsFile(info.PhysicalVideoFileFullPath) == true {
  509. outList = append(outList, info.PhysicalVideoFileFullPath)
  510. outCount++
  511. if outCount > 5 {
  512. break
  513. }
  514. }
  515. }
  516. } else {
  517. filterSeriesList, err := em.filterEmbyVideoList(EpisodeIdList, false)
  518. if err != nil {
  519. return nil, err
  520. }
  521. for _, info := range filterSeriesList {
  522. if my_util.IsFile(info.PhysicalVideoFileFullPath) == true {
  523. outList = append(outList, info.PhysicalVideoFileFullPath)
  524. outCount++
  525. if outCount > 5 {
  526. break
  527. }
  528. }
  529. }
  530. }
  531. return outList, nil
  532. }
  533. type InputData struct {
  534. Id string
  535. Wg *sync.WaitGroup
  536. }
  537. type OutData struct {
  538. Info *emby.EmbyMixInfo
  539. Err error
  540. }
  541. type PathSlice struct {
  542. Path string
  543. }
  544. type PathSlices []PathSlice
  545. func (a PathSlices) Len() int { return len(a) }
  546. func (a PathSlices) Less(i, j int) bool { return len(a[i].Path) < len(a[j].Path) }
  547. func (a PathSlices) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  548. func sortStringSliceByLength(m []string) PathSlices {
  549. p := make(PathSlices, len(m))
  550. i := 0
  551. for _, v := range m {
  552. p[i] = PathSlice{v}
  553. i++
  554. }
  555. sort.Sort(sort.Reverse(p))
  556. return p
  557. }
  558. const (
  559. videoTypeEpisode = "Episode"
  560. videoTypeMovie = "Movie"
  561. streamCodec = "subrip"
  562. )