py_emby.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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": "DateLastContentAdded,SortName",
  73. "IncludeItemTypes": "Movie,Series",
  74. "SortOrder": "Descending",
  75. "ParentId": cid,
  76. "Recursive": "true",
  77. "Limit": "30",
  78. "ImageTypeLimit": 1,
  79. "StartIndex": str((page - 1) * 30),
  80. "EnableImageTypes": "Primary,Backdrop,Thumb,Banner",
  81. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,Status,CriticRating,EndDate,Path",
  82. "EnableUserData": "true"
  83. }
  84. r = requests.get(url, params=params, headers=header, timeout=30)
  85. videoList = r.json()['Items']
  86. videos = []
  87. for video in videoList:
  88. name = self.cleanText(video['Name'])
  89. videos.append({
  90. "vod_id": video['Id'],
  91. "vod_name": name,
  92. "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 '',
  93. "vod_remarks": video['ProductionYear'] if 'ProductionYear' in video else ''
  94. })
  95. result['list'] = videos
  96. result['page'] = page
  97. result['pagecount'] = page + 1 if page * 30 < int(r.json()['TotalRecordCount']) else page
  98. result['limit'] = len(videos)
  99. result['total'] = int(r.json()['TotalRecordCount']) if "TotalRecordCount" in r.json() else 0
  100. return result
  101. def detailContent(self, did):
  102. try:
  103. embyInfos = self.getAccessToken()
  104. except:
  105. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  106. header = self.header.copy()
  107. header['Content-Type'] = "application/json; charset=UTF-8"
  108. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items/{did[0]}"
  109. params = {
  110. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  111. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  112. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  113. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  114. "X-Emby-Token": embyInfos['AccessToken']
  115. }
  116. r = requests.get(url, params=params, headers=header, timeout=30)
  117. videoInfos = r.json()
  118. vod = {
  119. "vod_id": did[0],
  120. "vod_name": videoInfos['Name'],
  121. "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 '',
  122. "type_name": videoInfos['Genres'][0] if len(videoInfos['Genres']) > 0 else '',
  123. "vod_year": videoInfos['ProductionYear'] if 'ProductionYear' in videoInfos else '',
  124. "vod_content": videoInfos['Overview'].replace('\xa0', ' ').replace('\n\n', '\n').strip() if 'Overview' in videoInfos else '',
  125. "vod_play_from": "EMBY"
  126. }
  127. playUrl = ''
  128. if not videoInfos['IsFolder']:
  129. playUrl += f"{videoInfos['Name'].strip()}${videoInfos['Id']}#"
  130. else:
  131. url = f"{self.baseUrl}/emby/Shows/{did[0]}/Seasons"
  132. params.update(
  133. {
  134. "UserId": embyInfos['User']['Id'],
  135. "EnableImages": "true",
  136. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating",
  137. "EnableUserData": "true",
  138. "EnableTotalRecordCount": "false"
  139. }
  140. )
  141. r = requests.get(url, params=params, headers=header, timeout=30)
  142. if r.status_code == 200:
  143. playInfos = r.json()['Items']
  144. for playInfo in playInfos:
  145. url = f"{self.baseUrl}/emby/Shows/{playInfo['Id']}/Episodes"
  146. params.update(
  147. {
  148. "SeasonId": playInfo['Id'],
  149. "Fields": "BasicSyncInfo,CanDelete,CommunityRating,PrimaryImageAspectRatio,ProductionYear,Overview"
  150. }
  151. )
  152. r = requests.get(url, params=params, headers=header, timeout=30)
  153. videoList = r.json()['Items']
  154. for video in videoList:
  155. playUrl += f"{playInfo['Name'].replace('#', '-').replace('$', '|').strip()}|{video['Name'].strip()}${video['Id']}#"
  156. else:
  157. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  158. params = {
  159. "ParentId": did[0],
  160. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,CriticRating",
  161. "ImageTypeLimit": "1",
  162. "StartIndex": "0",
  163. "EnableUserData": "true",
  164. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  165. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  166. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  167. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  168. "X-Emby-Token": embyInfos['AccessToken']
  169. }
  170. r = requests.get(url, params=params, headers=header, timeout=30)
  171. videoList = r.json()['Items']
  172. for video in videoList:
  173. playUrl += f"{video['Name'].replace('#', '-').replace('$', '|').strip()}${video['Id']}#"
  174. vod['vod_play_url'] = playUrl.strip('#')
  175. result = {'list': [vod]}
  176. return result
  177. def searchContent(self, key, quick):
  178. return self.searchContentPage(key, quick, '1')
  179. def searchContentPage(self, keywords, quick, page):
  180. try:
  181. embyInfos = self.getAccessToken()
  182. except:
  183. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  184. page = int(page)
  185. header = self.header.copy()
  186. header['Content-Type'] = "application/json; charset=UTF-8"
  187. url = f"{self.baseUrl}/emby/Users/{embyInfos['User']['Id']}/Items"
  188. params = {
  189. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  190. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  191. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  192. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  193. "X-Emby-Token": embyInfos['AccessToken'],
  194. "SortBy": "SortName",
  195. "SortOrder": "Ascending",
  196. "Fields": "BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,Status,EndDate",
  197. "StartIndex": str(((page-1)*50)),
  198. "EnableImageTypes": "Primary,Backdrop,Thumb",
  199. "ImageTypeLimit": "1",
  200. "Recursive": "true",
  201. "SearchTerm": keywords,
  202. "IncludeItemTypes": "Movie,Series,BoxSet",
  203. "GroupProgramsBySeries": "true",
  204. "Limit": "50",
  205. "EnableTotalRecordCount": "true"
  206. }
  207. r = requests.get(url, params=params, headers=header, timeout=30)
  208. videos = []
  209. vodList = r.json()['Items']
  210. for vod in vodList:
  211. sid = vod['Id']
  212. name = self.cleanText(vod['Name'])
  213. pic = f'{self.baseUrl}/emby/Items/{sid}/Images/Primary?maxWidth=400&tag={vod["ImageTags"]["Primary"]}&quality=90' if 'Primary' in vod["ImageTags"] else ''
  214. videos.append({
  215. "vod_id": sid,
  216. "vod_name": name,
  217. "vod_pic": pic,
  218. "vod_remarks": vod['ProductionYear'] if 'ProductionYear' in vod else ''
  219. })
  220. result = {'list': videos}
  221. return result
  222. def playerContent(self, flag, pid, vipFlags):
  223. try:
  224. embyInfos = self.getAccessToken()
  225. except:
  226. return {'list': [], 'msg': '获取Emby服务器信息出错'}
  227. header = self.header.copy()
  228. header['Content-Type'] = "application/json; charset=UTF-8"
  229. url = f"{self.baseUrl}/emby/Items/{pid}/PlaybackInfo"
  230. params = {
  231. "UserId": embyInfos['User']['Id'],
  232. "IsPlayback": "false",
  233. "AutoOpenLiveStream": "false",
  234. "StartTimeTicks": 0,
  235. "MaxStreamingBitrate": "2147483647",
  236. "X-Emby-Client": embyInfos['SessionInfo']['Client'],
  237. "X-Emby-Device-Name": embyInfos['SessionInfo']['DeviceName'],
  238. "X-Emby-Device-Id": embyInfos['SessionInfo']['DeviceId'],
  239. "X-Emby-Client-Version": embyInfos['SessionInfo']['ApplicationVersion'],
  240. "X-Emby-Token": embyInfos['AccessToken']
  241. }
  242. 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}}"
  243. r = requests.post(url, params=params, data=data, headers=header, timeout=30)
  244. url = self.baseUrl + r.json()['MediaSources'][0]['DirectStreamUrl']
  245. if int(self.thread) > 0:
  246. try:
  247. self.fetch('http://127.0.0.1:7777', timeout=1)
  248. except:
  249. self.fetch('http://127.0.0.1:9978/go')
  250. url = f'http://127.0.0.1:7777/?url={quote(url)}&thread={self.thread}'
  251. result = {
  252. "url": url,
  253. "header": self.header,
  254. "parse": 0
  255. }
  256. return result
  257. def localProxy(self, params):
  258. pass
  259. def destroy(self):
  260. return '正常进入'
  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. def destroy(self):
  273. pass
  274. header = {"User-Agent": "Yamby/1.0.2(Android"}