py_emby.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. #coding=utf-8
  2. #!/usr/bin/python
  3. import sys
  4. import json
  5. import time
  6. import requests
  7. from uuid import uuid4
  8. from urllib.parse import quote
  9. sys.path.append('..')
  10. from base.spider import Spider
  11. class Spider(Spider):
  12. def getName(self):
  13. return "EMBY"
  14. def init(self, extend):
  15. try:
  16. extendDict = json.loads(extend)
  17. self.baseUrl = extendDict['server'].strip('/')
  18. self.username = extendDict['username']
  19. self.password = extendDict['password']
  20. self.thread = extendDict['thread'] if 'thread' in extendDict else 0
  21. except:
  22. self.baseUrl = ''
  23. self.username = ''
  24. self.password = ''
  25. self.thread = 0
  26. def destroy(self):
  27. pass
  28. def isVideoFormat(self, url):
  29. pass
  30. def manualVideoCheck(self):
  31. pass
  32. def homeContent(self, filter):
  33. try:
  34. embyInfos = self.getAccessToken()
  35. except:
  36. return {'msg': '获取Emby服务器信息出错'}
  37. header = self.header.copy()
  38. header['Content-Type'] = "application/json; charset=UTF-8"
  39. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Views"
  40. params = {
  41. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  42. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  43. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  44. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  45. "X-Emby-Token": embyInfos['AccessToken']
  46. }
  47. r = requests.get(url, params=params, headers=header, timeout=30)
  48. typeInfos = r.json()["Items"]
  49. classList = []
  50. for typeInfo in typeInfos:
  51. if "播放列表" in typeInfo['Name'] or '相机' in typeInfo['Name']:
  52. continue
  53. classList.append({"type_name": typeInfo['Name'], "type_id": typeInfo['Id']})
  54. result = {'class': classList}
  55. return result
  56. def homeVideoContent(self):
  57. return {}
  58. def categoryContent(self, cid, page, filter, ext):
  59. try:
  60. embyInfos = self.getAccessToken()
  61. except:
  62. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  63. result = {}
  64. page = int(page)
  65. header = self.header.copy()
  66. header['Content-Type'] = "application/json; charset=UTF-8"
  67. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  68. params = {
  69. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  70. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  71. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  72. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  73. "X-Emby-Token": embyInfos['AccessToken'],
  74. "SortBy": "DateLastContentAdded,SortName",
  75. "IncludeItemTypes": "Movie,Series",
  76. "SortOrder": "Descending",
  77. "ParentId": cid,
  78. "Recursive": "true",
  79. "Limit": "30",
  80. "ImageTypeLimit": 1,
  81. "StartIndex": str((page - 1) * 30),
  82. "EnableImageTypes": "Primary,Backdrop,Thumb,Banner",
  83. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,Status,CriticRating,EndDate,Path",
  84. "EnableUserData": "true"
  85. }
  86. r = requests.get(url, params=params, headers=header, timeout=30)
  87. videoList = r.json()['Items']
  88. videos = []
  89. for video in videoList:
  90. name = self.cleanText(video['Name'])
  91. videos.append({
  92. "vod_id": video['Id'],
  93. "vod_name": name,
  94. "vod_pic": f"{self.baseUrl}/emby/Items/{video['Id']}/Images/Primary?maxWidth=400&tag={video['ImageTags']['Primary']}&quality=90" if 'Primary' in video['ImageTags'] else '',
  95. "vod_remarks": video['ProductionYear'] if 'ProductionYear' in video else ''
  96. })
  97. result['list'] = videos
  98. result['page'] = page
  99. result['pagecount'] = page + 1 if page * 30 < int(r.json()['TotalRecordCount']) else page
  100. result['limit'] = len(videos)
  101. result['total'] = int(r.json()['TotalRecordCount']) if "TotalRecordCount" in r.json() else 0
  102. return result
  103. def detailContent(self, did):
  104. try:
  105. embyInfos = self.getAccessToken()
  106. except:
  107. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  108. header = self.header.copy()
  109. header['Content-Type'] = "application/json; charset=UTF-8"
  110. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items/{did[0]}"
  111. params = {
  112. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  113. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  114. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  115. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  116. "X-Emby-Token": embyInfos['AccessToken']
  117. }
  118. r = requests.get(url, params=params, headers=header, timeout=30)
  119. videoInfos = r.json()
  120. vod = {
  121. "vod_id": did[0],
  122. "vod_name": videoInfos['Name'],
  123. "vod_pic": f'{self.baseUrl}/emby/Items/{did[0]}/Images/Primary?maxWidth=400&tag={videoInfos["ImageTags"]["Primary"]}&quality=90' if 'Primary' in videoInfos['ImageTags'] else '',
  124. "type_name": videoInfos['Genres'][0] if len(videoInfos['Genres']) > 0 else '',
  125. "vod_year": videoInfos['ProductionYear'] if 'ProductionYear' in videoInfos else '',
  126. "vod_content": videoInfos['Overview'].replace('\xa0', ' ').replace('\n\n', '\n').strip() if 'Overview' in videoInfos else '',
  127. "vod_play_from": "EMBY"
  128. }
  129. playUrl = ''
  130. if not videoInfos['IsFolder']:
  131. playUrl += f"{videoInfos['Name'].strip()}${videoInfos['Id']}#"
  132. else:
  133. url = f"{self.baseUrl}/emby/Shows/{did[0]}/Seasons"
  134. params.update(
  135. {
  136. "UserId": embyInfos['User']['Id'],
  137. "EnableImages": "true",
  138. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating",
  139. "EnableUserData": "true",
  140. "EnableTotalRecordCount": "false"
  141. }
  142. )
  143. r = requests.get(url, params=params, headers=header, timeout=30)
  144. if r.status_code == 200:
  145. playInfos = r.json()['Items']
  146. for playInfo in playInfos:
  147. url = f"{self.baseUrl}/emby/Shows/{playInfo['Id']}/Episodes"
  148. params.update(
  149. {
  150. "SeasonId": playInfo['Id'],
  151. "Fields": "BasicSyncInfo,CanDelete,CommunityRating,PrimaryImageAspectRatio,ProductionYear,Overview"
  152. }
  153. )
  154. r = requests.get(url, params=params, headers=header, timeout=30)
  155. videoList = r.json()['Items']
  156. for video in videoList:
  157. playUrl += f"{playInfo['Name'].replace('#', '-').replace('$', '|').strip()}|{video['Name'].strip()}${video['Id']}#"
  158. else:
  159. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  160. params = {
  161. "ParentId": did[0],
  162. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,CriticRating",
  163. "ImageTypeLimit": "1",
  164. "StartIndex": "0",
  165. "EnableUserData": "true",
  166. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  167. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  168. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  169. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  170. "X-Emby-Token": embyInfos['AccessToken']
  171. }
  172. r = requests.get(url, params=params, headers=header, timeout=30)
  173. videoList = r.json()['Items']
  174. for video in videoList:
  175. playUrl += f"{video['Name'].replace('#', '-').replace('$', '|').strip()}${video['Id']}#"
  176. vod['vod_play_url'] = playUrl.strip('#')
  177. result = {'list': [vod]}
  178. return result
  179. def searchContent(self, key, quick):
  180. return self.searchContentPage(key, quick, '1')
  181. def searchContentPage(self, keywords, quick, page):
  182. try:
  183. embyInfos = self.getAccessToken()
  184. except:
  185. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  186. page = int(page)
  187. header = self.header.copy()
  188. header['Content-Type'] = "application/json; charset=UTF-8"
  189. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  190. params = {
  191. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  192. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  193. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  194. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  195. "X-Emby-Token": embyInfos['AccessToken'],
  196. "SortBy": "SortName",
  197. "SortOrder": "Ascending",
  198. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,Status,EndDate",
  199. "StartIndex": str(((page-1)*50)),
  200. "EnableImageTypes": "Primary,Backdrop,Thumb",
  201. "ImageTypeLimit": "1",
  202. "Recursive": "true",
  203. "SearchTerm": keywords,
  204. "IncludeItemTypes": "Movie,Series,BoxSet",
  205. "GroupProgramsBySeries": "true",
  206. "Limit": "50",
  207. "EnableTotalRecordCount": "true"
  208. }
  209. r = requests.get(url, params=params, headers=header, timeout=30)
  210. videos = []
  211. vodList = r.json()['Items']
  212. for vod in vodList:
  213. sid = vod['Id']
  214. name = self.cleanText(vod['Name'])
  215. pic = f'{self.baseUrl}/emby/Items/{sid}/Images/Primary?maxWidth=400&tag={vod["ImageTags"]["Primary"]}&quality=90' if 'Primary' in vod["ImageTags"] else ''
  216. videos.append({
  217. "vod_id": sid,
  218. "vod_name": name,
  219. "vod_pic": pic,
  220. "vod_remarks": vod['ProductionYear'] if 'ProductionYear' in vod else ''
  221. })
  222. result = {'list': videos}
  223. return result
  224. def playerContent(self, flag, pid, vipFlags):
  225. try:
  226. embyInfos = self.getAccessToken()
  227. except:
  228. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  229. header = self.header.copy()
  230. header['Content-Type'] = "application/json; charset=UTF-8"
  231. url = f"{self.baseUrl}/emby/Items/{pid}/PlaybackInfo"
  232. params = {
  233. "UserId": embyInfos['User']['Id'],
  234. "IsPlayback": "false",
  235. "AutoOpenLiveStream": "false",
  236. "StartTimeTicks": 0,
  237. "MaxStreamingBitrate": "2147483647",
  238. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  239. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  240. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  241. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  242. "X-Emby-Token": embyInfos['AccessToken']
  243. }
  244. data = "{\"DeviceProfile\":{\"SubtitleProfiles\":[{\"Method\":\"Embed\",\"Format\":\"ass\"},{\"Format\":\"ssa\",\"Method\":\"Embed\"},{\"Format\":\"subrip\",\"Method\":\"Embed\"},{\"Format\":\"sub\",\"Method\":\"Embed\"},{\"Method\":\"Embed\",\"Format\":\"pgssub\"},{\"Format\":\"subrip\",\"Method\":\"External\"},{\"Method\":\"External\",\"Format\":\"sub\"},{\"Method\":\"External\",\"Format\":\"ass\"},{\"Format\":\"ssa\",\"Method\":\"External\"},{\"Method\":\"External\",\"Format\":\"vtt\"},{\"Method\":\"External\",\"Format\":\"ass\"},{\"Format\":\"ssa\",\"Method\":\"External\"}],\"CodecProfiles\":[{\"Codec\":\"h264\",\"Type\":\"Video\",\"ApplyConditions\":[{\"Property\":\"IsAnamorphic\",\"Value\":\"true\",\"Condition\":\"NotEquals\",\"IsRequired\":false},{\"IsRequired\":false,\"Value\":\"high|main|baseline|constrained baseline\",\"Condition\":\"EqualsAny\",\"Property\":\"VideoProfile\"},{\"IsRequired\":false,\"Value\":\"80\",\"Condition\":\"LessThanEqual\",\"Property\":\"VideoLevel\"},{\"IsRequired\":false,\"Value\":\"true\",\"Condition\":\"NotEquals\",\"Property\":\"IsInterlaced\"}]},{\"Codec\":\"hevc\",\"ApplyConditions\":[{\"Property\":\"IsAnamorphic\",\"Value\":\"true\",\"Condition\":\"NotEquals\",\"IsRequired\":false},{\"IsRequired\":false,\"Value\":\"high|main|main 10\",\"Condition\":\"EqualsAny\",\"Property\":\"VideoProfile\"},{\"Property\":\"VideoLevel\",\"Value\":\"175\",\"Condition\":\"LessThanEqual\",\"IsRequired\":false},{\"IsRequired\":false,\"Value\":\"true\",\"Condition\":\"NotEquals\",\"Property\":\"IsInterlaced\"}],\"Type\":\"Video\"}],\"MaxStreamingBitrate\":40000000,\"TranscodingProfiles\":[{\"Container\":\"ts\",\"AudioCodec\":\"aac,mp3,wav,ac3,eac3,flac,opus\",\"VideoCodec\":\"hevc,h264,mpeg4\",\"BreakOnNonKeyFrames\":true,\"Type\":\"Video\",\"MaxAudioChannels\":\"6\",\"Protocol\":\"hls\",\"Context\":\"Streaming\",\"MinSegments\":2}],\"DirectPlayProfiles\":[{\"Container\":\"mov,mp4,mkv,hls,webm\",\"Type\":\"Video\",\"VideoCodec\":\"h264,hevc,dvhe,dvh1,h264,hevc,hev1,mpeg4,vp9\",\"AudioCodec\":\"aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus,pcm,pcm_s24le\"}],\"ResponseProfiles\":[{\"MimeType\":\"video/mp4\",\"Type\":\"Video\",\"Container\":\"m4v\"}],\"ContainerProfiles\":[],\"MusicStreamingTranscodingBitrate\":40000000,\"MaxStaticBitrate\":40000000}}"
  245. r = requests.post(url, params=params, data=data, headers=header, timeout=30)
  246. url = self.baseUrl + r.json()['MediaSources'][0]['DirectStreamUrl']
  247. if int(self.thread) > 0:
  248. try:
  249. self.fetch('http://127.0.0.1:7777', timeout=1)
  250. except:
  251. self.fetch('http://127.0.0.1:9978/go')
  252. url = f'http://127.0.0.1:7777/?url={quote(url)}&thread={self.thread}'
  253. result = {
  254. "url": url,
  255. "header": self.header,
  256. "parse": 0
  257. }
  258. return result
  259. def localProxy(self, params):
  260. pass
  261. def getAccessToken(self):
  262. key = f"emby_{self.baseUrl}_{self.username}_{self.password }"
  263. embyInfos = self.getCache(key)
  264. if embyInfos:
  265. return embyInfos
  266. header = self.header.copy()
  267. header['Content-Type'] = "application/json; charset=UTF-8"
  268. r = requests.post(f"{self.baseUrl}/emby/Users/AuthenticateByName", params={"Username": self.username, "Password": self.password , "Pw": self.password , "X-Emby-Client": "Yamby", "X-Emby-Device-Name": "Yamby", "X-Emby-Device-Id": str(uuid4()), "X-Emby-Client-Version": "1.0.2"}, headers=header, timeout=30)
  269. embyInfos = r.json()
  270. self.setCache(key, embyInfos)
  271. return embyInfos
  272. header = {"User-Agent": "Yamby/1.0.2(Android"}