emby_api.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package emby_api
  2. import (
  3. "fmt"
  4. "github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
  5. "github.com/allanpk716/ChineseSubFinder/internal/types/emby"
  6. "github.com/go-resty/resty/v2"
  7. "github.com/panjf2000/ants/v2"
  8. "github.com/sirupsen/logrus"
  9. "golang.org/x/net/context"
  10. "net/http"
  11. "sync"
  12. "time"
  13. )
  14. type EmbyApi struct {
  15. log *logrus.Logger
  16. embyConfig *settings.EmbySettings
  17. threads int
  18. timeOut time.Duration
  19. client *resty.Client
  20. }
  21. func NewEmbyApi(log *logrus.Logger, embyConfig *settings.EmbySettings) *EmbyApi {
  22. em := EmbyApi{}
  23. em.log = log
  24. em.embyConfig = embyConfig
  25. // 检查是否超过范围
  26. em.embyConfig.Check()
  27. // 强制设置
  28. em.threads = 6
  29. em.timeOut = 5 * 60 * time.Second
  30. // 见 https://github.com/allanpk716/ChineseSubFinder/issues/140
  31. em.client = resty.New().SetTransport(&http.Transport{
  32. DisableKeepAlives: true,
  33. MaxIdleConns: 100,
  34. MaxIdleConnsPerHost: 100,
  35. }).RemoveProxy().SetTimeout(em.timeOut)
  36. return &em
  37. }
  38. // RefreshRecentlyVideoInfo 字幕下载完毕一次,就可以触发一次这个。并发 6 线程去刷新
  39. func (em EmbyApi) RefreshRecentlyVideoInfo() error {
  40. items, err := em.GetRecentlyItems()
  41. if err != nil {
  42. return err
  43. }
  44. em.log.Debugln("RefreshRecentlyVideoInfo - GetRecentlyItems Count", len(items.Items))
  45. updateFunc := func(i interface{}) error {
  46. tmpId := i.(string)
  47. return em.UpdateVideoSubList(tmpId)
  48. }
  49. p, err := ants.NewPoolWithFunc(em.threads, func(inData interface{}) {
  50. data := inData.(InputData)
  51. defer data.Wg.Done()
  52. ctx, cancel := context.WithTimeout(context.Background(), em.timeOut)
  53. defer cancel()
  54. done := make(chan error, 1)
  55. panicChan := make(chan interface{}, 1)
  56. go func() {
  57. defer func() {
  58. if p := recover(); p != nil {
  59. panicChan <- p
  60. }
  61. close(done)
  62. close(panicChan)
  63. }()
  64. done <- updateFunc(data.Id)
  65. }()
  66. select {
  67. case err = <-done:
  68. if err != nil {
  69. em.log.Errorln("RefreshRecentlyVideoInfo.NewPoolWithFunc got error", err)
  70. }
  71. return
  72. case p := <-panicChan:
  73. em.log.Errorln("RefreshRecentlyVideoInfo.NewPoolWithFunc got panic", p)
  74. case <-ctx.Done():
  75. em.log.Errorln("RefreshRecentlyVideoInfo.NewPoolWithFunc got time out", ctx.Err())
  76. return
  77. }
  78. })
  79. if err != nil {
  80. return err
  81. }
  82. defer p.Release()
  83. wg := sync.WaitGroup{}
  84. for _, item := range items.Items {
  85. wg.Add(1)
  86. err = p.Invoke(InputData{Id: item.Id, Wg: &wg})
  87. if err != nil {
  88. em.log.Errorln("RefreshRecentlyVideoInfo ants.Invoke", err)
  89. }
  90. }
  91. wg.Wait()
  92. return nil
  93. }
  94. func (em EmbyApi) GetRecentItemsByUserID(userId string) (emby.EmbyRecentlyItems, error) {
  95. var tmpRecItems emby.EmbyRecentlyItems
  96. // 获取指定用户的视频列表
  97. _, err := em.client.R().
  98. SetQueryParams(map[string]string{
  99. "api_key": em.embyConfig.APIKey,
  100. "IsUnaired": "false",
  101. "Limit": fmt.Sprintf("%d", em.embyConfig.MaxRequestVideoNumber),
  102. "Recursive": "true",
  103. "SortOrder": "Descending",
  104. "IncludeItemTypes": "Episode,Movie",
  105. "Filters": "IsNotFolder",
  106. "SortBy": "DateCreated",
  107. }).
  108. SetResult(&tmpRecItems).
  109. Get(em.embyConfig.AddressUrl + "/emby/Users/" + userId + "/Items")
  110. if err != nil {
  111. return emby.EmbyRecentlyItems{}, err
  112. }
  113. return tmpRecItems, nil
  114. }
  115. // GetRecentlyItems 获取近期的视频(根据 SkipWatched 的情况,如果不跳过,那么就是获取所有用户的列表,如果是跳过,那么就会单独读取每个用户的再交叉判断)
  116. // 在 API 调试界面 -- ItemsService
  117. func (em EmbyApi) GetRecentlyItems() (emby.EmbyRecentlyItems, error) {
  118. var recItems emby.EmbyRecentlyItems
  119. recItems.Items = make([]emby.EmbyRecentlyItem, 0)
  120. var recItemMap = make(map[string]emby.EmbyRecentlyItem)
  121. var recItemExsitMap = make(map[string]emby.EmbyRecentlyItem)
  122. var err error
  123. if em.embyConfig.SkipWatched == false {
  124. em.log.Debugln("Emby Setting SkipWatched = false")
  125. // 默认是不指定某一个User的视频列表
  126. _, err = em.client.R().
  127. SetQueryParams(map[string]string{
  128. "api_key": em.embyConfig.APIKey,
  129. "IsUnaired": "false",
  130. "Limit": fmt.Sprintf("%d", em.embyConfig.MaxRequestVideoNumber),
  131. "Recursive": "true",
  132. "SortOrder": "Descending",
  133. "IncludeItemTypes": "Episode,Movie",
  134. "Filters": "IsNotFolder",
  135. "SortBy": "DateCreated",
  136. }).
  137. SetResult(&recItems).
  138. Get(em.embyConfig.AddressUrl + "/emby/Items")
  139. if err != nil {
  140. return emby.EmbyRecentlyItems{}, err
  141. }
  142. } else {
  143. em.log.Debugln("Emby Setting SkipWatched = true")
  144. var userIds emby.EmbyUsers
  145. userIds, err = em.GetUserIdList()
  146. if err != nil {
  147. return emby.EmbyRecentlyItems{}, err
  148. }
  149. for _, item := range userIds.Items {
  150. tmpRecItems, err := em.GetRecentItemsByUserID(item.Id)
  151. if err != nil {
  152. return emby.EmbyRecentlyItems{}, err
  153. }
  154. // 相同的视频项目,需要判断是否已经看过了,看过的需要排除
  155. // 项目是否相同可以通过 Id 判断
  156. for _, recentlyItem := range tmpRecItems.Items {
  157. // 这个视频是否已经插入过了,可能会进行删除
  158. _, bFound := recItemMap[recentlyItem.Id]
  159. if bFound == false {
  160. // map 中不存在
  161. // 如果没有播放过,则插入
  162. if recentlyItem.UserData.Played == false {
  163. recItemMap[recentlyItem.Id] = recentlyItem
  164. }
  165. } else {
  166. // map 中存在
  167. // 既然存在,则可以理解为其他人是没有看过的,但是,如果当前的用户看过了,那么就要删除这一条
  168. if recentlyItem.UserData.Played == true {
  169. // 先记录下来,然后再删除这一条
  170. recItemExsitMap[recentlyItem.Id] = recentlyItem
  171. }
  172. }
  173. recItemMap[recentlyItem.Id] = recentlyItem
  174. }
  175. }
  176. for id := range recItemExsitMap {
  177. em.log.Debugln("Skip Watched Video:", recItemMap[id].Type, recItemMap[id].Name)
  178. delete(recItemMap, id)
  179. }
  180. for _, item := range recItemMap {
  181. recItems.Items = append(recItems.Items, item)
  182. }
  183. recItems.TotalRecordCount = len(recItemMap)
  184. }
  185. return recItems, nil
  186. }
  187. // GetUserIdList 获取所有的 UserId
  188. func (em EmbyApi) GetUserIdList() (emby.EmbyUsers, error) {
  189. var recItems emby.EmbyUsers
  190. _, err := em.client.R().
  191. SetQueryParams(map[string]string{
  192. "api_key": em.embyConfig.APIKey,
  193. }).
  194. SetResult(&recItems).
  195. Get(em.embyConfig.AddressUrl + "/emby/Users/Query")
  196. if err != nil {
  197. return emby.EmbyUsers{}, err
  198. }
  199. return recItems, nil
  200. }
  201. // GetItemAncestors 获取父级信息,在 API 调试界面 -- LibraryService
  202. func (em EmbyApi) GetItemAncestors(id string) ([]emby.EmbyItemsAncestors, error) {
  203. var recItems []emby.EmbyItemsAncestors
  204. _, err := em.client.R().
  205. SetQueryParams(map[string]string{
  206. "api_key": em.embyConfig.APIKey,
  207. }).
  208. SetResult(&recItems).
  209. Get(em.embyConfig.AddressUrl + "/emby/Items/" + id + "/Ancestors")
  210. if err != nil {
  211. return nil, err
  212. }
  213. return recItems, nil
  214. }
  215. // GetItemVideoInfo 在 API 调试界面 -- UserLibraryService,如果是电影,那么是可以从 ProviderIds 得到 IMDB ID 的
  216. // 如果是连续剧,那么不能使用一集的ID取获取,需要是这个剧集的 ID,注意一季的ID也是不行的
  217. func (em EmbyApi) GetItemVideoInfo(id string) (emby.EmbyVideoInfo, error) {
  218. var recItem emby.EmbyVideoInfo
  219. _, err := em.client.R().
  220. SetQueryParams(map[string]string{
  221. "api_key": em.embyConfig.APIKey,
  222. }).
  223. SetResult(&recItem).
  224. Get(em.embyConfig.AddressUrl + "/emby/LiveTv/Programs/" + id)
  225. if err != nil {
  226. return emby.EmbyVideoInfo{}, err
  227. }
  228. return recItem, nil
  229. }
  230. // GetItemVideoInfoByUserId 可以拿到这个视频的选择字幕Index,配合 GetItemVideoInfo 使用。 在 API 调试界面 -- UserLibraryService
  231. func (em EmbyApi) GetItemVideoInfoByUserId(userId, videoId string) (emby.EmbyVideoInfoByUserId, error) {
  232. var recItem emby.EmbyVideoInfoByUserId
  233. _, err := em.client.R().
  234. SetQueryParams(map[string]string{
  235. "api_key": em.embyConfig.APIKey,
  236. }).
  237. SetResult(&recItem).
  238. Get(em.embyConfig.AddressUrl + "/emby/Users/" + userId + "/Items/" + videoId)
  239. if err != nil {
  240. return emby.EmbyVideoInfoByUserId{}, err
  241. }
  242. return recItem, nil
  243. }
  244. // UpdateVideoSubList 更新字幕列表, 在 API 调试界面 -- ItemRefreshService
  245. func (em EmbyApi) UpdateVideoSubList(id string) error {
  246. _, err := em.client.R().
  247. SetQueryParams(map[string]string{
  248. "api_key": em.embyConfig.APIKey,
  249. }).
  250. Post(em.embyConfig.AddressUrl + "/emby/Items/" + id + "/Refresh")
  251. if err != nil {
  252. return err
  253. }
  254. return nil
  255. }
  256. // GetSubFileData 下载字幕 subExt -> .ass or .srt , 在 API 调试界面 -- SubtitleService
  257. func (em EmbyApi) GetSubFileData(videoId, mediaSourceId, subIndex, subExt string) (string, error) {
  258. response, err := em.client.R().
  259. Get(em.embyConfig.AddressUrl + "/emby/Videos/" + videoId + "/" + mediaSourceId + "/Subtitles/" + subIndex + "/Stream" + subExt)
  260. if err != nil {
  261. return "", err
  262. }
  263. return response.String(), nil
  264. }
  265. type InputData struct {
  266. Id string
  267. Wg *sync.WaitGroup
  268. }