emby_api.go 8.3 KB

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