py_emby.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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 isVideoFormat(self, url):
  27. pass
  28. def manualVideoCheck(self):
  29. pass
  30. def homeContent(self, filter):
  31. try:
  32. embyInfos = self.getAccessToken()
  33. except:
  34. return {'msg': '获取Emby服务器信息出错'}
  35. header = self.header.copy()
  36. header['Content-Type'] = "application/json; charset=UTF-8"
  37. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Views"
  38. params = {
  39. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  40. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  41. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  42. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  43. "X-Emby-Token": embyInfos['AccessToken']
  44. }
  45. r = requests.get(url, params=params, headers=header, timeout=30)
  46. typeInfos = r.json()["Items"]
  47. classList = []
  48. for typeInfo in typeInfos:
  49. if "播放列表" in typeInfo['Name'] or '相机' in typeInfo['Name']:
  50. continue
  51. classList.append({"type_name": typeInfo['Name'], "type_id": typeInfo['Id']})
  52. result = {'class': classList}
  53. return result
  54. def homeVideoContent(self):
  55. return {}
  56. def categoryContent(self, cid, page, filter, ext):
  57. try:
  58. embyInfos = self.getAccessToken()
  59. except:
  60. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  61. result = {}
  62. page = int(page)
  63. header = self.header.copy()
  64. header['Content-Type'] = "application/json; charset=UTF-8"
  65. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  66. params = {
  67. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  68. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  69. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  70. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  71. "X-Emby-Token": embyInfos['AccessToken'],
  72. "SortBy": "SortName",
  73. "SortOrder": "Ascending",
  74. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,Prefix",
  75. "StartIndex": str((page - 1) * 30),
  76. "ParentId": cid,
  77. "EnableImageTypes": "Primary,Backdrop,Thumb",
  78. "ImageTypeLimit": 1,
  79. "GroupItemsIntoCollections": "true",
  80. "Limit": "30"
  81. }
  82. r = requests.get(url, params=params, headers=header, timeout=30)
  83. videoList = r.json()['Items']
  84. videos = []
  85. for video in videoList:
  86. name = self.cleanText(video['Name'])
  87. videos.append({
  88. "vod_id": video['Id'],
  89. "vod_name": name,
  90. "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 '',
  91. "vod_remarks": video['ProductionYear'] if 'ProductionYear' in video else ''
  92. })
  93. result['list'] = videos
  94. result['page'] = page
  95. result['pagecount'] = page + 1 if page * 30 < int(r.json()['TotalRecordCount']) else page
  96. result['limit'] = len(videos)
  97. result['total'] = len(videos)
  98. return result
  99. def detailContent(self, did):
  100. try:
  101. embyInfos = self.getAccessToken()
  102. except:
  103. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  104. header = self.header.copy()
  105. header['Content-Type'] = "application/json; charset=UTF-8"
  106. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items/{did[0]}"
  107. params = {
  108. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  109. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  110. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  111. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  112. "X-Emby-Token": embyInfos['AccessToken']
  113. }
  114. r = requests.get(url, params=params, headers=header, timeout=30)
  115. videoInfos = r.json()
  116. vod = {
  117. "vod_id": did[0],
  118. "vod_name": videoInfos['Name'],
  119. "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 '',
  120. "type_name": videoInfos['Genres'][0] if len(videoInfos['Genres']) > 0 else '',
  121. "vod_year": videoInfos['ProductionYear'] if 'ProductionYear' in videoInfos else '',
  122. "vod_content": videoInfos['Overview'].replace('\xa0', ' ').replace('\n\n', '\n').strip() if 'Overview' in videoInfos else '',
  123. "vod_play_from": "EMBY"
  124. }
  125. playUrl = ''
  126. if not videoInfos['IsFolder']:
  127. playUrl += f"{videoInfos['Name'].strip()}${videoInfos['Id']}#"
  128. else:
  129. url = f"{self.baseUrl}/emby/Shows/{did[0]}/Seasons"
  130. params.update(
  131. {
  132. "UserId": embyInfos['User']['Id'],
  133. "EnableImages": "true",
  134. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating",
  135. "EnableUserData": "true",
  136. "EnableTotalRecordCount": "false"
  137. }
  138. )
  139. r = requests.get(url, params=params, headers=header, timeout=30)
  140. if r.status_code == 200:
  141. playInfos = r.json()['Items']
  142. for playInfo in playInfos:
  143. url = f"{self.baseUrl}/emby/Shows/{playInfo['Id']}/Episodes"
  144. params.update(
  145. {
  146. "SeasonId": playInfo['Id'],
  147. "Fields": "BasicSyncInfo,CanDelete,CommunityRating,PrimaryImageAspectRatio,ProductionYear,Overview"
  148. }
  149. )
  150. r = requests.get(url, params=params, headers=header, timeout=30)
  151. videoList = r.json()['Items']
  152. for video in videoList:
  153. playUrl += f"{playInfo['Name'].strip()}|{video['Name'].strip()}${video['Id']}#"
  154. else:
  155. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  156. params = {
  157. "ParentId": did[0],
  158. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,CriticRating",
  159. "ImageTypeLimit": "1",
  160. "StartIndex": "0",
  161. "EnableUserData": "true",
  162. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  163. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  164. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  165. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  166. "X-Emby-Token": embyInfos['AccessToken']
  167. }
  168. r = requests.get(url, params=params, headers=header, timeout=30)
  169. videoList = r.json()['Items']
  170. for video in videoList:
  171. playUrl += f"{video['Name'].strip()}${video['Id']}#"
  172. vod['vod_play_url'] = playUrl.strip('#')
  173. result = {'list': [vod]}
  174. return result
  175. def searchContent(self, key, quick):
  176. return self.searchContentPage(key, quick, '1')
  177. def searchContentPage(self, keywords, quick, page):
  178. try:
  179. embyInfos = self.getAccessToken()
  180. except:
  181. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  182. page = int(page)
  183. header = self.header.copy()
  184. header['Content-Type'] = "application/json; charset=UTF-8"
  185. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  186. params = {
  187. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  188. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  189. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  190. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  191. "X-Emby-Token": embyInfos['AccessToken'],
  192. "SortBy": "SortName",
  193. "SortOrder": "Ascending",
  194. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,Status,EndDate",
  195. "StartIndex": str(((page-1)*50)),
  196. "EnableImageTypes": "Primary,Backdrop,Thumb",
  197. "ImageTypeLimit": "1",
  198. "Recursive": "true",
  199. "SearchTerm": keywords,
  200. "IncludeItemTypes": "Movie,Series,BoxSet",
  201. "GroupProgramsBySeries": "true",
  202. "Limit": "50",
  203. "EnableTotalRecordCount": "true"
  204. }
  205. r = requests.get(url, params=params, headers=header, timeout=30)
  206. videos = []
  207. vodList = r.json()['Items']
  208. for vod in vodList:
  209. sid = vod['Id']
  210. name = self.cleanText(vod['Name'])
  211. pic = f'{self.baseUrl}/emby/Items/{sid}/Images/Primary?maxWidth=400&tag={vod["ImageTags"]["Primary"]}&quality=90' if 'Primary' in vod["ImageTags"] else ''
  212. videos.append({
  213. "vod_id": sid,
  214. "vod_name": name,
  215. "vod_pic": pic,
  216. "vod_remarks": vod['ProductionYear'] if 'ProductionYear' in vod else ''
  217. })
  218. result = {'list': videos}
  219. return result
  220. def playerContent(self, flag, pid, vipFlags):
  221. try:
  222. embyInfos = self.getAccessToken()
  223. except:
  224. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  225. header = self.header.copy()
  226. header['Content-Type'] = "application/json; charset=UTF-8"
  227. url = f"{self.baseUrl}/emby/Items/{pid}/PlaybackInfo"
  228. params = {
  229. "UserId": embyInfos['User']['Id'],
  230. "IsPlayback": "false",
  231. "AutoOpenLiveStream": "false",
  232. "StartTimeTicks": 0,
  233. "MaxStreamingBitrate": "2147483647",
  234. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  235. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  236. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  237. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  238. "X-Emby-Token": embyInfos['AccessToken']
  239. }
  240. 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}}"
  241. r = requests.post(url, params=params, data=data, headers=header, timeout=30)
  242. url = self.baseUrl + r.json()['MediaSources'][0]['DirectStreamUrl']
  243. if int(self.thread) > 0:
  244. try:
  245. self.fetch('http://127.0.0.1:7777', timeout=1)
  246. except:
  247. self.fetch('http://127.0.0.1:9978/go')
  248. url = f'http://127.0.0.1:7777/?url={quote(url)}&thread={self.thread}'
  249. result = {
  250. "url": url,
  251. "header": self.header,
  252. "parse": 0
  253. }
  254. return result
  255. def localProxy(self, params):
  256. pass
  257. def getAccessToken(self):
  258. key = f"emby_{self.baseUrl}_{self.username}_{self.password }"
  259. embyInfos = self.getCache(key)
  260. if embyInfos:
  261. return embyInfos
  262. header = self.header.copy()
  263. header['Content-Type'] = "application/json; charset=UTF-8"
  264. 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)
  265. embyInfos = r.json()
  266. self.setCache(key, embyInfos)
  267. return embyInfos
  268. def getCache(self, key):
  269. value = self.fetch(f'http://127.0.0.1:9978/cache?do=get&key={key}', timeout=5).text
  270. # value = self.fetch(f'http://192.168.1.254:9978/cache?do=get&key={key}', timeout=5).text
  271. if len(value) > 0:
  272. if value.startswith('{') and value.endswith('}') or value.startswith('[') and value.endswith(']'):
  273. value = json.loads(value)
  274. if type(value) == dict:
  275. if not 'expiresAt' in value or value['expiresAt'] >= int(time.time()):
  276. return value
  277. else:
  278. self.delCache(key)
  279. return None
  280. return value
  281. else:
  282. return None
  283. def setCache(self, key, value):
  284. if len(value) > 0:
  285. if type(value) == dict or type(value) == list:
  286. value = json.dumps(value, ensure_ascii=False)
  287. self.post(f'http://127.0.0.1:9978/cache?do=set&key={key}', data={"value": value}, timeout=5)
  288. # self.post(f'http://192.168.1.254:9978/cache?do=set&key={key}', data={"value": value}, timeout=5)
  289. def delCache(self, key):
  290. self.fetch(f'http://127.0.0.1:9978/cache?do=del&key={key}', timeout=5)
  291. # self.fetch(f'http://192.168.1.254:9978/cache?do=del&key={key}', timeout=5)
  292. header = {"User-Agent": "Yamby/1.0.2(Android"}