embyhelper.go 40 KB

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