embyhelper.go 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. package emby_helper
  2. import (
  3. "errors"
  4. "fmt"
  5. embyHelper "github.com/allanpk716/ChineseSubFinder/internal/pkg/emby_api"
  6. "github.com/allanpk716/ChineseSubFinder/internal/pkg/imdb_helper"
  7. "github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
  8. "github.com/allanpk716/ChineseSubFinder/internal/pkg/path_helper"
  9. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  10. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sort_things"
  11. "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_parser_hub"
  12. "github.com/allanpk716/ChineseSubFinder/internal/types"
  13. common2 "github.com/allanpk716/ChineseSubFinder/internal/types/common"
  14. "github.com/allanpk716/ChineseSubFinder/internal/types/emby"
  15. "github.com/allanpk716/ChineseSubFinder/internal/types/language"
  16. "github.com/panjf2000/ants/v2"
  17. "github.com/sirupsen/logrus"
  18. "golang.org/x/net/context"
  19. "path"
  20. "path/filepath"
  21. "strings"
  22. "sync"
  23. "time"
  24. )
  25. type EmbyHelper struct {
  26. EmbyApi *embyHelper.EmbyApi
  27. log *logrus.Logger
  28. settings *settings.Settings
  29. timeOut time.Duration
  30. listLock sync.Mutex
  31. }
  32. func NewEmbyHelper(_log *logrus.Logger, _settings *settings.Settings) *EmbyHelper {
  33. em := EmbyHelper{log: _log, settings: _settings}
  34. em.EmbyApi = embyHelper.NewEmbyApi(_log, _settings.EmbySettings)
  35. em.timeOut = 60 * time.Second
  36. return &em
  37. }
  38. // GetRecentlyAddVideoListWithNoChineseSubtitle 获取最近新添加的视频,且没有中文字幕的
  39. func (em *EmbyHelper) GetRecentlyAddVideoListWithNoChineseSubtitle(needForcedScanAndDownSub ...bool) ([]emby.EmbyMixInfo, map[string][]emby.EmbyMixInfo, error) {
  40. filterMovieList, filterSeriesList, err := em.GetRecentlyAddVideoList()
  41. if err != nil {
  42. return nil, nil, err
  43. }
  44. var noSubMovieList, noSubSeriesList []emby.EmbyMixInfo
  45. var seriesMap = make(map[string][]emby.EmbyMixInfo)
  46. if len(needForcedScanAndDownSub) > 0 && needForcedScanAndDownSub[0] == true {
  47. // 强制扫描,无需过滤
  48. // 输出调试信息
  49. em.log.Debugln("-----------------")
  50. em.log.Debugln("GetRecentlyAddVideoList movie", len(filterMovieList))
  51. for index, info := range filterMovieList {
  52. em.log.Debugln(index, info.VideoFileName)
  53. }
  54. em.log.Debugln("-----------------")
  55. em.log.Debugln("GetRecentlyAddVideoList series", len(filterSeriesList))
  56. for index, info := range filterSeriesList {
  57. em.log.Debugln(index, info.VideoFileName)
  58. }
  59. em.log.Debugln("-----------------")
  60. // 需要将连续剧零散的每一集,进行合并到一个连续剧下面,也就是这个连续剧有那些需要更新的
  61. for _, info := range filterSeriesList {
  62. _, ok := seriesMap[info.VideoFolderName]
  63. if ok == false {
  64. // 不存在则新建初始化
  65. seriesMap[info.VideoFolderName] = make([]emby.EmbyMixInfo, 0)
  66. }
  67. seriesMap[info.VideoFolderName] = append(seriesMap[info.VideoFolderName], info)
  68. }
  69. return filterMovieList, seriesMap, nil
  70. } else {
  71. // 将没有字幕的找出来
  72. noSubMovieList, err = em.filterNoChineseSubVideoList(filterMovieList)
  73. if err != nil {
  74. return nil, nil, err
  75. }
  76. em.log.Debugln("-----------------")
  77. noSubSeriesList, err = em.filterNoChineseSubVideoList(filterSeriesList)
  78. if err != nil {
  79. return nil, nil, err
  80. }
  81. // 输出调试信息
  82. em.log.Debugln("-----------------")
  83. em.log.Debugln("filterNoChineseSubVideoList found no chinese movie", len(noSubMovieList))
  84. for index, info := range noSubMovieList {
  85. em.log.Debugln(index, info.VideoFileName)
  86. }
  87. em.log.Debugln("-----------------")
  88. em.log.Debugln("filterNoChineseSubVideoList found no chinese series", len(noSubSeriesList))
  89. for index, info := range noSubSeriesList {
  90. em.log.Debugln(index, info.VideoFileName)
  91. }
  92. em.log.Debugln("-----------------")
  93. // 需要将连续剧零散的每一集,进行合并到一个连续剧下面,也就是这个连续剧有那些需要更新的
  94. for _, info := range noSubSeriesList {
  95. _, ok := seriesMap[info.VideoFolderName]
  96. if ok == false {
  97. // 不存在则新建初始化
  98. seriesMap[info.VideoFolderName] = make([]emby.EmbyMixInfo, 0)
  99. }
  100. seriesMap[info.VideoFolderName] = append(seriesMap[info.VideoFolderName], info)
  101. }
  102. return noSubMovieList, seriesMap, nil
  103. }
  104. }
  105. // GetRecentlyAddVideoList 获取最近新添加的视频
  106. func (em *EmbyHelper) GetRecentlyAddVideoList() ([]emby.EmbyMixInfo, []emby.EmbyMixInfo, error) {
  107. // 获取最近的影片列表
  108. items, err := em.EmbyApi.GetRecentlyItems()
  109. if err != nil {
  110. return nil, nil, err
  111. }
  112. // 获取电影和连续剧的文件夹名称
  113. var EpisodeIdList = make([]string, 0)
  114. var MovieIdList = make([]string, 0)
  115. em.log.Debugln("-----------------")
  116. em.log.Debugln("GetRecentlyAddVideoListWithNoChineseSubtitle - GetRecentlyItems Count", len(items.Items))
  117. // 分类
  118. for index, item := range items.Items {
  119. if item.Type == videoTypeEpisode {
  120. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  121. EpisodeIdList = append(EpisodeIdList, item.Id)
  122. em.log.Debugln("Episode:", index, item.SeriesName, item.ParentIndexNumber, item.IndexNumber)
  123. } else if item.Type == videoTypeMovie {
  124. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  125. MovieIdList = append(MovieIdList, item.Id)
  126. em.log.Debugln("Movie:", index, item.Name)
  127. } else {
  128. em.log.Debugln("GetRecentlyItems - Is not a goal video type:", index, item.Name, item.Type)
  129. }
  130. }
  131. // 过滤出有效的电影、连续剧的资源出来
  132. filterMovieList, err := em.getMoreVideoInfoList(MovieIdList, true)
  133. if err != nil {
  134. return nil, nil, err
  135. }
  136. filterSeriesList, err := em.getMoreVideoInfoList(EpisodeIdList, false)
  137. if err != nil {
  138. return nil, nil, err
  139. }
  140. // 输出调试信息
  141. em.log.Debugln("-----------------")
  142. em.log.Debugln("getMoreVideoInfoList found valid movie", len(filterMovieList))
  143. for index, info := range filterMovieList {
  144. em.log.Debugln(index, info.VideoFileName)
  145. }
  146. em.log.Debugln("-----------------")
  147. em.log.Debugln("getMoreVideoInfoList found valid series", len(filterSeriesList))
  148. for index, info := range filterSeriesList {
  149. em.log.Debugln(index, info.VideoFileName)
  150. }
  151. em.log.Debugln("-----------------")
  152. return filterMovieList, filterSeriesList, nil
  153. }
  154. // GetVideoIDPlayedMap 获取已经播放过的视频的ID
  155. func (em *EmbyHelper) GetVideoIDPlayedMap() map[string]bool {
  156. videoIDPlayedMap := make(map[string]bool)
  157. // 获取有那些用户
  158. var userIds emby.EmbyUsers
  159. userIds, err := em.EmbyApi.GetUserIdList()
  160. if err != nil {
  161. em.log.Errorln("IsVideoIDPlayed - GetUserIdList error:", err)
  162. return videoIDPlayedMap
  163. }
  164. // 所有用户观看过的视频有那些
  165. for _, item := range userIds.Items {
  166. tmpRecItems, err := em.EmbyApi.GetRecentItemsByUserID(item.Id)
  167. if err != nil {
  168. em.log.Errorln("IsVideoIDPlayed - GetRecentItemsByUserID, UserID:", item.Id, "error:", err)
  169. return videoIDPlayedMap
  170. }
  171. // 相同的视频项目,需要判断是否已经看过了
  172. // 项目是否相同可以通过 Id 判断
  173. for _, recentlyItem := range tmpRecItems.Items {
  174. if recentlyItem.UserData.Played == true {
  175. videoIDPlayedMap[recentlyItem.Id] = true
  176. }
  177. }
  178. }
  179. return videoIDPlayedMap
  180. }
  181. // GetPlayedItemsSubtitle 所有用户标记播放过的视频,返回 电影、连续剧, 视频全路径 -- 对应字幕全路径(经过转换的)
  182. func (em *EmbyHelper) GetPlayedItemsSubtitle() (map[string]string, map[string]string, error) {
  183. // 这个用户看过那些视频
  184. var userPlayedItemsList = make([]emby.UserPlayedItems, 0)
  185. // 获取有那些用户
  186. var userIds emby.EmbyUsers
  187. userIds, err := em.EmbyApi.GetUserIdList()
  188. if err != nil {
  189. return nil, nil, err
  190. }
  191. // 所有用户观看过的视频有那些,需要分用户统计出来
  192. for _, item := range userIds.Items {
  193. tmpRecItems, err := em.EmbyApi.GetRecentItemsByUserID(item.Id)
  194. if err != nil {
  195. return nil, nil, err
  196. }
  197. // 相同的视频项目,需要判断是否已经看过了,看过的需要排除
  198. // 项目是否相同可以通过 Id 判断
  199. oneUserPlayedItems := emby.UserPlayedItems{
  200. UserName: item.Name,
  201. UserID: item.Id,
  202. Items: make([]emby.EmbyRecentlyItem, 0),
  203. }
  204. for _, recentlyItem := range tmpRecItems.Items {
  205. if recentlyItem.UserData.Played == true {
  206. oneUserPlayedItems.Items = append(oneUserPlayedItems.Items, recentlyItem)
  207. }
  208. }
  209. userPlayedItemsList = append(userPlayedItemsList, oneUserPlayedItems)
  210. }
  211. // 把这些用户看过的视频根据 userID 和 videoID 进行查询,使用的是第几个字幕
  212. // 这里需要区分是 Movie 还是 Series,这样后续的路径映射才能够生效
  213. // 视频 emby 路径 - 字幕 emby 路径
  214. movieEmbyFPathMap := make(map[string]string)
  215. seriesEmbyFPathMap := make(map[string]string)
  216. for _, playedItems := range userPlayedItemsList {
  217. for _, item := range playedItems.Items {
  218. videoInfoByUserId, err := em.EmbyApi.GetItemVideoInfoByUserId(playedItems.UserID, item.Id)
  219. if err != nil {
  220. return nil, nil, err
  221. }
  222. videoInfo, err := em.EmbyApi.GetItemVideoInfo(item.Id)
  223. if err != nil {
  224. return nil, nil, err
  225. }
  226. // 首先不能越界
  227. if videoInfoByUserId.GetDefaultSubIndex() < 0 || len(videoInfo.MediaStreams)-1 < videoInfoByUserId.GetDefaultSubIndex() {
  228. em.log.Debugln("GetPlayedItemsSubtitle", videoInfo.Name, "SubIndex Out Of Range")
  229. continue
  230. }
  231. // 然后找出来的字幕必须是外置字幕,内置还导出个啥子
  232. if videoInfo.MediaStreams[videoInfoByUserId.GetDefaultSubIndex()].IsExternal == false {
  233. em.log.Debugln("GetPlayedItemsSubtitle", videoInfo.Name,
  234. "Get Played SubIndex", videoInfoByUserId.GetDefaultSubIndex(),
  235. "is IsExternal == false, Skip")
  236. continue
  237. }
  238. // 将这个字幕的 Emby 内部路径保存下来,后续还需要进行一次路径转换才能使用,转换到本程序的路径上
  239. if item.Type == videoTypeEpisode {
  240. seriesEmbyFPathMap[videoInfo.Path] = videoInfo.MediaStreams[videoInfoByUserId.GetDefaultSubIndex()].Path
  241. } else if item.Type == videoTypeMovie {
  242. movieEmbyFPathMap[videoInfo.Path] = videoInfo.MediaStreams[videoInfoByUserId.GetDefaultSubIndex()].Path
  243. }
  244. }
  245. }
  246. // 转换 Emby 内部路径到本程序识别的视频目录上
  247. moviePhyFPathMap := make(map[string]string)
  248. seriesPhyFPathMap := make(map[string]string)
  249. // movie
  250. for key, value := range movieEmbyFPathMap {
  251. bok, prefixOldPath, prefixNewPath := em.findMappingPath(key, true)
  252. if bok == false {
  253. em.log.Warningln("GetPlayedItemsSubtitle.findMappingPath miss matched,", key)
  254. continue
  255. }
  256. phyVideoPath := strings.ReplaceAll(key, prefixOldPath, prefixNewPath)
  257. phySubPath := strings.ReplaceAll(value, prefixOldPath, prefixNewPath)
  258. moviePhyFPathMap[phyVideoPath] = phySubPath
  259. }
  260. // series
  261. for key, value := range seriesEmbyFPathMap {
  262. bok, prefixOldPath, prefixNewPath := em.findMappingPath(key, false)
  263. if bok == false {
  264. em.log.Warningln("GetPlayedItemsSubtitle.findMappingPath miss matched,", key)
  265. continue
  266. }
  267. phyVideoPath := strings.ReplaceAll(key, prefixOldPath, prefixNewPath)
  268. phySubPath := strings.ReplaceAll(value, prefixOldPath, prefixNewPath)
  269. seriesPhyFPathMap[phyVideoPath] = phySubPath
  270. }
  271. return moviePhyFPathMap, seriesPhyFPathMap, nil
  272. }
  273. // RefreshEmbySubList 字幕下载完毕一次,就可以触发一次这个。并发 6 线程去刷新
  274. func (em *EmbyHelper) RefreshEmbySubList() (bool, error) {
  275. if em.EmbyApi == nil {
  276. return false, nil
  277. }
  278. err := em.EmbyApi.RefreshRecentlyVideoInfo()
  279. if err != nil {
  280. return false, err
  281. }
  282. return true, nil
  283. }
  284. func (em *EmbyHelper) IsVideoPlayed(videoID string) (bool, error) {
  285. // 获取有那些用户
  286. var userIds emby.EmbyUsers
  287. userIds, err := em.EmbyApi.GetUserIdList()
  288. if err != nil {
  289. return false, err
  290. }
  291. // 所有用户观看过的视频有那些,需要分用户统计出来
  292. for _, item := range userIds.Items {
  293. videoInfo, err := em.EmbyApi.GetItemVideoInfoByUserId(item.Id, videoID)
  294. if err != nil {
  295. return false, err
  296. }
  297. if videoInfo.UserData.Played == true {
  298. return true, nil
  299. }
  300. }
  301. return false, nil
  302. }
  303. // findMappingPath 从 Emby 内置路径匹配到物理路径,返回,需要替换的前缀,以及替换到的前缀
  304. // X:\电影 - /mnt/share1/电影
  305. // X:\连续剧 - /mnt/share1/连续剧
  306. func (em *EmbyHelper) findMappingPath(fileFPathWithEmby string, isMovieOrSeries bool) (bool, string, string) {
  307. // 这里进行路径匹配的时候需要考虑嵌套路径的问题
  308. // 比如,映射了 /电影 以及 /电影/AA ,那么如果有一部电影 /电影/AA/xx/xx.mkv 那么,应该匹配的是最长的路径 /电影/AA
  309. matchedEmbyPaths := make([]string, 0)
  310. if isMovieOrSeries == true {
  311. // 电影的情况
  312. for _, embyPath := range em.settings.EmbySettings.MoviePathsMapping {
  313. if strings.HasPrefix(fileFPathWithEmby, embyPath) == true {
  314. matchedEmbyPaths = append(matchedEmbyPaths, embyPath)
  315. }
  316. }
  317. } else {
  318. // 连续剧的情况
  319. for _, embyPath := range em.settings.EmbySettings.SeriesPathsMapping {
  320. if strings.HasPrefix(fileFPathWithEmby, embyPath) == true {
  321. matchedEmbyPaths = append(matchedEmbyPaths, embyPath)
  322. }
  323. }
  324. }
  325. if len(matchedEmbyPaths) < 1 {
  326. return false, "", ""
  327. }
  328. // 排序得到匹配上的路径,最长的那个
  329. pathSlices := sort_things.SortStringSliceByLength(matchedEmbyPaths)
  330. // 然后还需要从这个最长的路径,从 map 中找到对应的物理路径
  331. // nowPhRootPath 这个路径是映射的根目录,如果里面再次嵌套 子文件夹 再到连续剧目录,则是个问题,会丢失子文件夹目录
  332. nowPhRootPath := ""
  333. if isMovieOrSeries == true {
  334. // 电影的情况
  335. for physicalPath, embyPath := range em.settings.EmbySettings.MoviePathsMapping {
  336. if embyPath == pathSlices[0].Path {
  337. nowPhRootPath = physicalPath
  338. break
  339. }
  340. }
  341. } else {
  342. // 连续剧的情况
  343. for physicalPath, embyPath := range em.settings.EmbySettings.SeriesPathsMapping {
  344. if embyPath == pathSlices[0].Path {
  345. nowPhRootPath = physicalPath
  346. break
  347. }
  348. }
  349. }
  350. // 如果匹配不上
  351. if nowPhRootPath == "" {
  352. return false, "", ""
  353. }
  354. return true, pathSlices[0].Path, nowPhRootPath
  355. }
  356. // getVideoIMDBId 从视频的内部 ID 找到 IMDB id
  357. func (em *EmbyHelper) getMoreVideoInfo(videoID string, isMovieOrSeries bool) (*emby.EmbyMixInfo, error) {
  358. if isMovieOrSeries == true {
  359. // 电影的情况
  360. info, err := em.EmbyApi.GetItemVideoInfo(videoID)
  361. if err != nil {
  362. return nil, err
  363. }
  364. ancs, err := em.EmbyApi.GetItemAncestors(videoID)
  365. if err != nil {
  366. return nil, err
  367. }
  368. mixInfo := emby.EmbyMixInfo{IMDBId: info.ProviderIds.Imdb, Ancestors: ancs, VideoInfo: info}
  369. return &mixInfo, nil
  370. } else {
  371. // 连续剧的情况,需要从一集对算到 series 目录,得到内部 series 的 ID,然后再得到 IMDB ID
  372. ancs, err := em.EmbyApi.GetItemAncestors(videoID)
  373. if err != nil {
  374. return nil, err
  375. }
  376. // 暂时不支持蓝光,因为没有下载到对应的连续剧蓝光视频
  377. ancestorIndex := -1
  378. // 找到连续剧文件夹这一层
  379. for i, ancestor := range ancs {
  380. if ancestor.Type == "Series" {
  381. ancestorIndex = i
  382. break
  383. }
  384. }
  385. if ancestorIndex == -1 {
  386. // 说明没有找到连续剧文件夹的名称,那么就应该跳过
  387. return nil, nil
  388. }
  389. // 这里的目标是从 Emby 获取 IMDB ID
  390. info, err := em.EmbyApi.GetItemVideoInfo(ancs[ancestorIndex].ID)
  391. if err != nil {
  392. return nil, err
  393. }
  394. nowSeriesIMDBID := info.ProviderIds.Imdb
  395. // 然后还是要跟电影一样的使用 Video ID 去获取 Ancestors 和 VideoInfo,而上面一步获取的是这个 Series 的 ID
  396. info, err = em.EmbyApi.GetItemVideoInfo(videoID)
  397. if err != nil {
  398. return nil, err
  399. }
  400. ancs, err = em.EmbyApi.GetItemAncestors(videoID)
  401. if err != nil {
  402. return nil, err
  403. }
  404. mixInfo := emby.EmbyMixInfo{IMDBId: nowSeriesIMDBID, Ancestors: ancs, VideoInfo: info}
  405. return &mixInfo, nil
  406. }
  407. }
  408. // 根据 IMDB ID 自动转换路径
  409. func (em *EmbyHelper) autoFindMappingPathWithMixInfoByIMDBId(mixInfo *emby.EmbyMixInfo, isMovieOrSeries bool) bool {
  410. if mixInfo.IMDBId == "" {
  411. em.log.Debugln("autoFindMappingPathWithMixInfoByIMDBId", " mixInfo.IMDBId == \"\"")
  412. return false
  413. }
  414. // 获取 IMDB 信息
  415. localIMDBInfo, err := imdb_helper.GetVideoIMDBInfoFromLocal(em.log, types.VideoIMDBInfo{ImdbId: mixInfo.IMDBId}, true)
  416. if err != nil {
  417. if errors.Is(err, common2.SkipCreateInDB) == true {
  418. em.log.Debugln("autoFindMappingPathWithMixInfoByIMDBId.GetVideoIMDBInfoFromLocal", err)
  419. return false
  420. }
  421. em.log.Errorln("autoFindMappingPathWithMixInfoByIMDBId.GetVideoIMDBInfoFromLocal", err)
  422. return false
  423. }
  424. if localIMDBInfo.RootDirPath == "" {
  425. // 说明这个不是从本程序挂在的视频目录中正常扫描出来的视频,这里可能是因为 Emby 新建出来的
  426. em.log.Debugln("autoFindMappingPathWithMixInfoByIMDBId", " localIMDBInfo.RootDirPath == \"\"")
  427. return false
  428. }
  429. // 下面开始实际的路径替换,从 emby 的内部路径转换为 本程序读取到视频的路径
  430. if isMovieOrSeries == true {
  431. // 电影
  432. // 这里需要考虑蓝光的情况,这种目录比较特殊,在 emby 获取的时候,可以知道这个是否是蓝光,是的话,需要特殊处理
  433. // 伪造一个虚假不存在的 .mp4 文件向后提交给电影的下载函数
  434. /*
  435. 举例:失控玩家(2021) 是一个蓝光电影
  436. 那么下面的 mixInfo.VideoInfo.Path 从 emby 拿到应该是 /mnt/share1/电影/失控玩家(2021)
  437. 就需要再次基础上进行视频的伪造
  438. */
  439. if len(mixInfo.VideoInfo.MediaSources) > 0 && mixInfo.VideoInfo.MediaSources[0].Container == "bluray" {
  440. // 这个就是蓝光了
  441. // 先替换再拼接,不然会出现拼接完成后,在 Windows 下会把 /mnt/share1/电影 变为这样了 \mnt\share1\电影\失控玩家 (2021)\失控玩家 (2021).mp4
  442. videoReplacedDirFPath := strings.ReplaceAll(mixInfo.VideoInfo.Path, mixInfo.VideoInfo.Path, localIMDBInfo.RootDirPath)
  443. fakeVideoFPath := filepath.Join(videoReplacedDirFPath, filepath.Base(mixInfo.VideoInfo.Path)+common2.VideoExtMp4)
  444. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(fakeVideoFPath, filepath.Dir(mixInfo.VideoInfo.Path), localIMDBInfo.RootDirPath)
  445. // 这个电影的文件夹
  446. mixInfo.VideoFolderName = filepath.Base(mixInfo.VideoInfo.Path)
  447. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path) + common2.VideoExtMp4
  448. } else {
  449. // 常规的电影情况,也就是有一个具体的视频文件 .mp4 or .mkv
  450. // 在 Windows 下,如果操作 linux 的路径 /mnt/abc/123 filepath.Dir 那么得到的是 \mnt\abc
  451. // 那么临时方案是使用替换掉方法去得到 Dir 路径,比如是 /mnt/abc/123.mp4 replace 123.mp4 那么得到 /mnt/abc/ 还需要把最后一个字符删除
  452. videoDirPath := strings.ReplaceAll(mixInfo.VideoInfo.Path, filepath.Base(mixInfo.VideoInfo.Path), "")
  453. videoDirPath = videoDirPath[:len(videoDirPath)-1]
  454. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, videoDirPath, localIMDBInfo.RootDirPath)
  455. // 这个电影的文件夹
  456. mixInfo.VideoFolderName = filepath.Base(filepath.Dir(mixInfo.VideoInfo.Path))
  457. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path)
  458. }
  459. } else {
  460. // 连续剧
  461. // 暂时不支持蓝光,因为没有下载到对应的连续剧蓝光视频
  462. ancestorIndex := -1
  463. // 找到连续剧文件夹这一层
  464. for i, ancestor := range mixInfo.Ancestors {
  465. if ancestor.Type == "Series" {
  466. ancestorIndex = i
  467. break
  468. }
  469. }
  470. if ancestorIndex == -1 {
  471. // 说明没有找到连续剧文件夹的名称,那么就应该跳过
  472. return false
  473. }
  474. mixInfo.PhysicalSeriesRootDir = localIMDBInfo.RootDirPath
  475. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, mixInfo.Ancestors[ancestorIndex].Path, localIMDBInfo.RootDirPath)
  476. mixInfo.PhysicalRootPath = filepath.Dir(localIMDBInfo.RootDirPath)
  477. // 这个剧集的文件夹
  478. mixInfo.VideoFolderName = filepath.Base(mixInfo.Ancestors[ancestorIndex].Path)
  479. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path)
  480. }
  481. return true
  482. }
  483. // findMappingPathWithMixInfo 从 Emby 内置路径匹配到物理路径
  484. // X:\电影 - /mnt/share1/电影
  485. // X:\连续剧 - /mnt/share1/连续剧
  486. func (em *EmbyHelper) findMappingPathWithMixInfo(mixInfo *emby.EmbyMixInfo, isMovieOrSeries bool) bool {
  487. defer func() {
  488. // 见 https://github.com/allanpk716/ChineseSubFinder/issues/278
  489. // 进行字符串操作的时候,可能会把 smb://123 转义为 smb:/123
  490. // 修复了个寂寞,没用,这个逻辑保留,但是没用哈。因为就不支持 SMB 的客户端协议
  491. if mixInfo != nil {
  492. mixInfo.PhysicalRootPath = path_helper.FixShareFileProtocolsPath(mixInfo.PhysicalRootPath)
  493. mixInfo.PhysicalVideoFileFullPath = path_helper.FixShareFileProtocolsPath(mixInfo.PhysicalVideoFileFullPath)
  494. }
  495. }()
  496. // 这里进行路径匹配的时候需要考虑嵌套路径的问题
  497. // 比如,映射了 /电影 以及 /电影/AA ,那么如果有一部电影 /电影/AA/xx/xx.mkv 那么,应该匹配的是最长的路径 /电影/AA
  498. matchedEmbyPaths := make([]string, 0)
  499. if isMovieOrSeries == true {
  500. // 电影的情况
  501. for _, embyPath := range em.settings.EmbySettings.MoviePathsMapping {
  502. if strings.HasPrefix(mixInfo.VideoInfo.Path, embyPath) == true {
  503. matchedEmbyPaths = append(matchedEmbyPaths, embyPath)
  504. }
  505. }
  506. } else {
  507. // 连续剧的情况
  508. for _, embyPath := range em.settings.EmbySettings.SeriesPathsMapping {
  509. if strings.HasPrefix(mixInfo.VideoInfo.Path, embyPath) == true {
  510. matchedEmbyPaths = append(matchedEmbyPaths, embyPath)
  511. }
  512. }
  513. }
  514. if len(matchedEmbyPaths) < 1 {
  515. return false
  516. }
  517. // 排序得到匹配上的路径,最长的那个
  518. pathSlices := sort_things.SortStringSliceByLength(matchedEmbyPaths)
  519. // 然后还需要从这个最长的路径,从 map 中找到对应的物理路径
  520. // nowPhRootPath 这个路径是映射的根目录,如果里面再次嵌套 子文件夹 再到连续剧目录,则是个问题,会丢失子文件夹目录
  521. nowPhRootPath := ""
  522. if isMovieOrSeries == true {
  523. // 电影的情况
  524. for physicalPath, embyPath := range em.settings.EmbySettings.MoviePathsMapping {
  525. if embyPath == pathSlices[0].Path {
  526. nowPhRootPath = physicalPath
  527. break
  528. }
  529. }
  530. } else {
  531. // 连续剧的情况
  532. for physicalPath, embyPath := range em.settings.EmbySettings.SeriesPathsMapping {
  533. if embyPath == pathSlices[0].Path {
  534. nowPhRootPath = physicalPath
  535. break
  536. }
  537. }
  538. }
  539. // 如果匹配不上
  540. if nowPhRootPath == "" {
  541. return false
  542. }
  543. // 下面开始实际的路径替换,从 emby 的内部路径转换为 本程序读取到视频的路径
  544. if isMovieOrSeries == true {
  545. // 电影
  546. // 这里需要考虑蓝光的情况,这种目录比较特殊,在 emby 获取的时候,可以知道这个是否是蓝光,是的话,需要特殊处理
  547. // 伪造一个虚假不存在的 .mp4 文件向后提交给电影的下载函数
  548. /*
  549. 举例:失控玩家(2021) 是一个蓝光电影
  550. 那么下面的 mixInfo.VideoInfo.Path 从 emby 拿到应该是 /mnt/share1/电影/失控玩家(2021)
  551. 就需要再次基础上进行视频的伪造
  552. */
  553. if len(mixInfo.VideoInfo.MediaSources) > 0 && mixInfo.VideoInfo.MediaSources[0].Container == "bluray" {
  554. // 这个就是蓝光了
  555. // 先替换再拼接,不然会出现拼接完成后,在 Windows 下会把 /mnt/share1/电影 变为这样了 \mnt\share1\电影\失控玩家 (2021)\失控玩家 (2021).mp4
  556. videoReplacedDirFPath := strings.ReplaceAll(mixInfo.VideoInfo.Path, pathSlices[0].Path, nowPhRootPath)
  557. fakeVideoFPath := filepath.Join(videoReplacedDirFPath, filepath.Base(mixInfo.VideoInfo.Path)+common2.VideoExtMp4)
  558. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(fakeVideoFPath, pathSlices[0].Path, nowPhRootPath)
  559. // 这个电影的文件夹
  560. mixInfo.VideoFolderName = filepath.Base(mixInfo.VideoInfo.Path)
  561. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path) + common2.VideoExtMp4
  562. } else {
  563. // 常规的电影情况,也就是有一个具体的视频文件 .mp4 or .mkv
  564. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, pathSlices[0].Path, nowPhRootPath)
  565. // 因为电影搜索的时候使用的是完整的视频目录,所以这个字段并不重要,连续剧的时候才需要关注
  566. //mixInfo.PhysicalRootPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, pathSlices[0].Path, nowPhRootPath)
  567. // 这个电影的文件夹
  568. mixInfo.VideoFolderName = filepath.Base(filepath.Dir(mixInfo.VideoInfo.Path))
  569. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path)
  570. }
  571. } else {
  572. // 连续剧
  573. // 暂时不支持蓝光,因为没有下载到对应的连续剧蓝光视频
  574. ancestorIndex := -1
  575. // 找到连续剧文件夹这一层
  576. for i, ancestor := range mixInfo.Ancestors {
  577. if ancestor.Type == "Series" {
  578. ancestorIndex = i
  579. break
  580. }
  581. }
  582. if ancestorIndex == -1 {
  583. // 说明没有找到连续剧文件夹的名称,那么就应该跳过
  584. return false
  585. }
  586. mixInfo.PhysicalSeriesRootDir = strings.ReplaceAll(mixInfo.Ancestors[ancestorIndex].Path, pathSlices[0].Path, nowPhRootPath)
  587. mixInfo.PhysicalVideoFileFullPath = strings.ReplaceAll(mixInfo.VideoInfo.Path, pathSlices[0].Path, nowPhRootPath)
  588. mixInfo.PhysicalRootPath = strings.ReplaceAll(mixInfo.Ancestors[ancestorIndex+1].Path, pathSlices[0].Path, nowPhRootPath)
  589. // 这个剧集的文件夹
  590. mixInfo.VideoFolderName = filepath.Base(mixInfo.Ancestors[ancestorIndex].Path)
  591. mixInfo.VideoFileName = filepath.Base(mixInfo.VideoInfo.Path)
  592. }
  593. return true
  594. }
  595. // getMoreVideoInfoList 把视频的更多信息查询出来,需要并发去做
  596. func (em *EmbyHelper) getMoreVideoInfoList(videoIdList []string, isMovieOrSeries bool) ([]emby.EmbyMixInfo, error) {
  597. var filterVideoEmbyInfo = make([]emby.EmbyMixInfo, 0)
  598. // 这个方法是使用两边的路径映射表来实现的转换,使用的体验不佳,很多人搞不定
  599. queryFuncByMatchPath := func(m string) (*emby.EmbyMixInfo, error) {
  600. oneMixInfo, err := em.getMoreVideoInfo(m, isMovieOrSeries)
  601. if err != nil {
  602. return nil, err
  603. }
  604. if em.settings.EmbySettings.AutoOrManual == true {
  605. // 通过 IMDB ID 自动转换路径
  606. if isMovieOrSeries == true {
  607. // 电影
  608. // 过滤掉不符合要求的,拼接绝对路径
  609. isFit := em.autoFindMappingPathWithMixInfoByIMDBId(oneMixInfo, isMovieOrSeries)
  610. if isFit == false {
  611. return nil, err
  612. }
  613. } else {
  614. // 连续剧
  615. // 过滤掉不符合要求的,拼接绝对路径
  616. isFit := em.autoFindMappingPathWithMixInfoByIMDBId(oneMixInfo, isMovieOrSeries)
  617. if isFit == false {
  618. return nil, err
  619. }
  620. }
  621. } else {
  622. // 通过手动的路径映射
  623. if isMovieOrSeries == true {
  624. // 电影
  625. // 过滤掉不符合要求的,拼接绝对路径
  626. isFit := em.findMappingPathWithMixInfo(oneMixInfo, isMovieOrSeries)
  627. if isFit == false {
  628. return nil, err
  629. }
  630. } else {
  631. // 连续剧
  632. // 过滤掉不符合要求的,拼接绝对路径
  633. isFit := em.findMappingPathWithMixInfo(oneMixInfo, isMovieOrSeries)
  634. if isFit == false {
  635. return nil, err
  636. }
  637. }
  638. }
  639. return oneMixInfo, nil
  640. }
  641. // em.threads
  642. p, err := ants.NewPoolWithFunc(em.settings.EmbySettings.Threads, func(inData interface{}) {
  643. data := inData.(InputData)
  644. defer data.Wg.Done()
  645. ctx, cancel := context.WithTimeout(context.Background(), em.timeOut)
  646. defer cancel()
  647. done := make(chan OutData, 1)
  648. panicChan := make(chan interface{}, 1)
  649. go func() {
  650. defer func() {
  651. if p := recover(); p != nil {
  652. panicChan <- p
  653. }
  654. close(done)
  655. close(panicChan)
  656. }()
  657. info, err := queryFuncByMatchPath(data.Id)
  658. outData := OutData{
  659. Info: info,
  660. Err: err,
  661. }
  662. done <- outData
  663. }()
  664. select {
  665. case outData := <-done:
  666. // 收到结果,需要加锁
  667. if outData.Err != nil {
  668. em.log.Errorln("getMoreVideoInfoList.NewPoolWithFunc got Err", outData.Err)
  669. return
  670. }
  671. if outData.Info == nil {
  672. return
  673. }
  674. em.listLock.Lock()
  675. filterVideoEmbyInfo = append(filterVideoEmbyInfo, *outData.Info)
  676. em.listLock.Unlock()
  677. return
  678. case p := <-panicChan:
  679. em.log.Errorln("getMoreVideoInfoList.NewPoolWithFunc got panic", p)
  680. case <-ctx.Done():
  681. em.log.Errorln("getMoreVideoInfoList.NewPoolWithFunc got time out", ctx.Err())
  682. return
  683. }
  684. })
  685. if err != nil {
  686. return nil, err
  687. }
  688. defer p.Release()
  689. wg := sync.WaitGroup{}
  690. // 获取视频的 Emby 信息
  691. for _, m := range videoIdList {
  692. wg.Add(1)
  693. err = p.Invoke(InputData{Id: m, Wg: &wg})
  694. if err != nil {
  695. em.log.Errorln("getMoreVideoInfoList ants.Invoke", err)
  696. }
  697. }
  698. wg.Wait()
  699. return filterVideoEmbyInfo, nil
  700. }
  701. // filterNoChineseSubVideoList 将没有中文字幕的视频找出来
  702. func (em *EmbyHelper) filterNoChineseSubVideoList(videoList []emby.EmbyMixInfo) ([]emby.EmbyMixInfo, error) {
  703. currentTime := time.Now()
  704. var noSubVideoList = make([]emby.EmbyMixInfo, 0)
  705. // TODO 这里有一种情况需要考虑的,如果内置有中文的字幕,那么是否需要跳过,目前暂定的一定要有外置的字幕
  706. for _, info := range videoList {
  707. needDlSub3Month := false
  708. // 3个月内,或者没有字幕都要进行下载
  709. if info.VideoInfo.PremiereDate.AddDate(0, 0, em.settings.AdvancedSettings.TaskQueue.ExpirationTime).After(currentTime) == true {
  710. // 需要下载的
  711. needDlSub3Month = true
  712. }
  713. // 这个影片只要有一个符合字幕要求的,就可以跳过
  714. // 外置中文字幕
  715. haveExternalChineseSub := false
  716. for _, stream := range info.VideoInfo.MediaStreams {
  717. // 首先找到外置的字幕文件
  718. if stream.IsExternal == true && stream.IsTextSubtitleStream == true && stream.SupportsExternalStream == true {
  719. // 然后字幕的格式以及语言命名要符合本程序的定义,有字幕
  720. if sub_parser_hub.IsEmbySubCodecWanted(stream.Codec) == true &&
  721. sub_parser_hub.IsEmbySubChineseLangStringWanted(stream.Language) == true {
  722. haveExternalChineseSub = true
  723. break
  724. } else {
  725. continue
  726. }
  727. }
  728. }
  729. // 内置中文字幕
  730. haveInsideChineseSub := false
  731. for _, stream := range info.VideoInfo.MediaStreams {
  732. if stream.IsExternal == false &&
  733. sub_parser_hub.IsEmbySubChineseLangStringWanted(stream.Language) {
  734. haveInsideChineseSub = true
  735. break
  736. }
  737. }
  738. // 比如,创建的时间在3个月内,然后没有额外下载的中文字幕,都符合要求
  739. if haveExternalChineseSub == false {
  740. // 没有外置字幕
  741. // 如果创建了7天,且有内置的中文字幕,那么也不进行下载了
  742. if info.VideoInfo.DateCreated.AddDate(0, 0, em.settings.AdvancedSettings.TaskQueue.DownloadSubDuringXDays).After(currentTime) == false && haveInsideChineseSub == true {
  743. em.log.Debugln("Create Over 7 Days, And It Has Inside ChineseSub, Than Skip", info.VideoFileName)
  744. continue
  745. }
  746. //// 如果创建了三个月,还是没有字幕,那么也不进行下载了
  747. //if info.VideoInfo.DateCreated.Add(dayRange3Months).After(currentTime) == false {
  748. // continue
  749. //}
  750. // 没有中文字幕就加入下载列表
  751. noSubVideoList = append(noSubVideoList, info)
  752. } else {
  753. // 有外置字幕
  754. // 如果视频发布时间超过两年了,有字幕就直接跳过了,一般字幕稳定了
  755. if currentTime.Year()-2 > info.VideoInfo.PremiereDate.Year() {
  756. em.log.Debugln("Create Over 2 Years, And It Has External ChineseSub, Than Skip", info.VideoFileName)
  757. continue
  758. }
  759. // 有中文字幕,且如果在三个月内,则需要继续下载字幕`
  760. if needDlSub3Month == true {
  761. noSubVideoList = append(noSubVideoList, info)
  762. }
  763. }
  764. }
  765. return noSubVideoList, nil
  766. }
  767. // GetInternalEngSubAndExChineseEnglishSub 获取对应 videoId 的内置英文字幕,外置中文字幕(只要是带有中文的都算,简体、繁体、简英、繁英,需要后续额外的判断)字幕
  768. func (em *EmbyHelper) GetInternalEngSubAndExChineseEnglishSub(videoId string) (bool, []emby.SubInfo, []emby.SubInfo, error) {
  769. // 先刷新以下这个资源,避免找到的字幕不存在了
  770. err := em.EmbyApi.UpdateVideoSubList(videoId)
  771. if err != nil {
  772. return false, nil, nil, err
  773. }
  774. // 获取这个资源的信息
  775. videoInfo, err := em.EmbyApi.GetItemVideoInfo(videoId)
  776. if err != nil {
  777. return false, nil, nil, err
  778. }
  779. // 获取 MediaSources ID,这里强制使用第一个视频源(因为 emby 运行有多个版本的视频指向到一个视频ID上,比如一个 web 一个 蓝光)
  780. mediaSourcesId := videoInfo.MediaSources[0].Id
  781. // 视频文件名称带后缀名
  782. videoFileName := filepath.Base(videoInfo.Path)
  783. videoFileNameWithOutExt := strings.ReplaceAll(videoFileName, path.Ext(videoFileName), "")
  784. // TODO 后续会新增一个功能,从视频中提取音频文件,然后识别转为字符,再进行与字幕的匹配
  785. // 获取是否有内置的英文字幕,如果没有则无需继续往下
  786. /*
  787. 这里有个梗,读取到的英文内置字幕很可能是残缺的,比如,基地 S01E04 Eng 第一个 Default Forced Sub,就不对,内容的 Dialogue 很少。
  788. 然后第二个 Eng 字幕才对。那么考虑到兼容性, 可能后续有短视频,也就不能简单的按 Dialogue 的多少去衡量。大概会做一个功能。方案有两个:
  789. 1. 读取到视频的总长度,然后再分析 Dialogue 的时间出现的部分与整体时间轴的占比,又或者是 Dialogue 之间的连续成都分析,这个有待测试。
  790. 2. 还有一个更加粗暴的方案,把所有的 Eng 都识别出来,然后找最多的 Dialogue 来做为正确的来使用(够粗暴吧)
  791. */
  792. var insideEngSUbIndexList = make([]int, 0)
  793. for _, stream := range videoInfo.MediaStreams {
  794. if stream.IsExternal == false && stream.Language == language.Emby_English_eng && stream.Codec == streamCodec {
  795. insideEngSUbIndexList = append(insideEngSUbIndexList, stream.Index)
  796. }
  797. }
  798. // 没有找到则跳过
  799. if len(insideEngSUbIndexList) == 0 {
  800. return false, nil, nil, nil
  801. }
  802. // 再内置英文字幕能找到的前提下,就可以先找中文的外置字幕,目前版本只能考虑双语字幕
  803. // 内置英文字幕,这里把 srt 和 ass 的都导出来
  804. var inSubList = make([]emby.SubInfo, 0)
  805. // 外置中文双语字幕
  806. var exSubList = make([]emby.SubInfo, 0)
  807. tmpFileNameWithOutExt := ""
  808. for _, stream := range videoInfo.MediaStreams {
  809. // 首先找到外置的字幕文件
  810. if stream.IsExternal == true && stream.IsTextSubtitleStream == true && stream.SupportsExternalStream == true {
  811. // 然后字幕的格式以及语言命名要符合本程序的定义,有字幕
  812. if sub_parser_hub.IsEmbySubCodecWanted(stream.Codec) == true &&
  813. sub_parser_hub.IsEmbySubChineseLangStringWanted(stream.Language) == true {
  814. tmpFileName := filepath.Base(stream.Path)
  815. // 去除 .default 或者 .forced
  816. //tmpFileName = strings.ReplaceAll(tmpFileName, subparser.Sub_Ext_Mark_Default, "")
  817. //tmpFileName = strings.ReplaceAll(tmpFileName, subparser.Sub_Ext_Mark_Forced, "")
  818. tmpFileNameWithOutExt = strings.ReplaceAll(tmpFileName, path.Ext(tmpFileName), "")
  819. exSubList = append(exSubList, *emby.NewSubInfo(tmpFileNameWithOutExt+"."+stream.Codec, "."+stream.Codec, stream.Index))
  820. } else {
  821. continue
  822. }
  823. }
  824. }
  825. // 没有找到则跳过
  826. if len(exSubList) == 0 {
  827. return false, nil, nil, nil
  828. }
  829. /*
  830. 把之前 Internal 英文字幕的 SubInfo 实例的信息补充完整
  831. 但是也不是绝对的,因为后续去 emby 下载字幕的时候,需要与外置字幕的后缀名一致
  832. 这里开始去下载字幕
  833. 先下载内置的文的
  834. 因为上面下载内置英文字幕的梗,所以,需要预先下载多个内置的英文字幕下来,用体积最大(相同后缀名)的那个来作为最后的输出
  835. */
  836. // 那么现在先下载相同格式(.srt)的两个字幕
  837. InsideEngSubIndex := 0
  838. if len(insideEngSUbIndexList) == 1 {
  839. // 如果就找到一个内置字幕,就默认这个
  840. InsideEngSubIndex = insideEngSUbIndexList[0]
  841. } else {
  842. // 如果找到不止一个就需要判断
  843. var tmpSubContentLenList = make([]int, 0)
  844. for _, index := range insideEngSUbIndexList {
  845. // TODO 这里默认是去 Emby 去拿字幕,但是其实可以缓存在视频文件同级的目录下,这样后续就无需多次下载了,毕竟每次下载都需要读取完整的视频
  846. subFileData, err := em.EmbyApi.GetSubFileData(videoId, mediaSourcesId, fmt.Sprintf("%d", index), common2.SubExtSRT)
  847. if err != nil {
  848. return false, nil, nil, err
  849. }
  850. tmpSubContentLenList = append(tmpSubContentLenList, len(subFileData))
  851. }
  852. maxContentLen := -1
  853. for index, contentLen := range tmpSubContentLenList {
  854. if maxContentLen < contentLen {
  855. maxContentLen = contentLen
  856. InsideEngSubIndex = insideEngSUbIndexList[index]
  857. }
  858. }
  859. }
  860. // 这里才是下载最佳的那个字幕
  861. for i := 0; i < 2; i++ {
  862. tmpExt := common2.SubExtSRT
  863. if i == 1 {
  864. tmpExt = common2.SubExtASS
  865. }
  866. subFileData, err := em.EmbyApi.GetSubFileData(videoId, mediaSourcesId, fmt.Sprintf("%d", InsideEngSubIndex), tmpExt)
  867. if err != nil {
  868. return false, nil, nil, err
  869. }
  870. tmpInSubInfo := emby.NewSubInfo(videoFileNameWithOutExt+tmpExt, tmpExt, InsideEngSubIndex)
  871. tmpInSubInfo.Content = []byte(subFileData)
  872. inSubList = append(inSubList, *tmpInSubInfo)
  873. }
  874. // 再下载外置的
  875. for i, subInfo := range exSubList {
  876. subFileData, err := em.EmbyApi.GetSubFileData(videoId, mediaSourcesId, fmt.Sprintf("%d", subInfo.EmbyStreamIndex), subInfo.Ext)
  877. if err != nil {
  878. return false, nil, nil, err
  879. }
  880. exSubList[i].Content = []byte(subFileData)
  881. }
  882. return true, inSubList, exSubList, nil
  883. }
  884. // CheckPath 检查路径 EmbyConfig 配置中的映射路径是否是有效的,
  885. func (em *EmbyHelper) CheckPath(pathType string) ([]string, error) {
  886. // 获取最近的影片列表
  887. items, err := em.EmbyApi.GetRecentlyItems()
  888. if err != nil {
  889. return nil, err
  890. }
  891. // 获取电影和连续剧的文件夹名称
  892. var EpisodeIdList = make([]string, 0)
  893. var MovieIdList = make([]string, 0)
  894. // 分类
  895. for index, item := range items.Items {
  896. if item.Type == videoTypeEpisode {
  897. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  898. EpisodeIdList = append(EpisodeIdList, item.Id)
  899. em.log.Debugln("Episode:", index, item.SeriesName, item.ParentIndexNumber, item.IndexNumber)
  900. } else if item.Type == videoTypeMovie {
  901. // 这个里面可能混有其他的内容,比如目标是连续剧,但是 emby_helper 其实会把其他的混合内容也标记进去
  902. MovieIdList = append(MovieIdList, item.Id)
  903. em.log.Debugln("Movie:", index, item.Name)
  904. } else {
  905. em.log.Debugln("GetRecentlyItems - Is not a goal video type:", index, item.Name, item.Type)
  906. }
  907. }
  908. outCount := 0
  909. outList := make([]string, 0)
  910. if pathType == "movie" {
  911. // 过滤出有效的电影、连续剧的资源出来
  912. filterMovieList, err := em.getMoreVideoInfoList(MovieIdList, true)
  913. if err != nil {
  914. return nil, err
  915. }
  916. for _, info := range filterMovieList {
  917. if my_util.IsFile(info.PhysicalVideoFileFullPath) == true {
  918. outList = append(outList, info.PhysicalVideoFileFullPath)
  919. outCount++
  920. if outCount > 5 {
  921. break
  922. }
  923. }
  924. }
  925. } else {
  926. filterSeriesList, err := em.getMoreVideoInfoList(EpisodeIdList, false)
  927. if err != nil {
  928. return nil, err
  929. }
  930. for _, info := range filterSeriesList {
  931. if my_util.IsFile(info.PhysicalVideoFileFullPath) == true {
  932. outList = append(outList, info.PhysicalVideoFileFullPath)
  933. outCount++
  934. if outCount > 5 {
  935. break
  936. }
  937. }
  938. }
  939. }
  940. return outList, nil
  941. }
  942. type InputData struct {
  943. Id string
  944. Wg *sync.WaitGroup
  945. }
  946. type OutData struct {
  947. Info *emby.EmbyMixInfo
  948. Err error
  949. }
  950. const (
  951. videoTypeEpisode = "Episode"
  952. videoTypeMovie = "Movie"
  953. streamCodec = "subrip"
  954. )