MediaDash/api/jellyfin.py

334 lines
11 KiB
Python

import time
import base64
from urllib.parse import urljoin
from datetime import timedelta
import requests as RQ
from dateutil.parser import parse as parse_datetime
from utils import timed_cache
class Jellyfin(object):
def __init__(self, url, user, password):
self.url = url
self.session = RQ.Session()
self.device_id = str(
base64.b64encode(
"MediaDash ({})".format(
self.session.headers["User-Agent"]).encode("utf-8")),
"utf8",
)
self.auth_headers = {
"X-Emby-Authorization": 'MediaBrowser Client="MediaDash", Device="Python", DeviceId="{}", Version="{}"'.format(
self.device_id, RQ.__version__)}
self.user = None
if user is not None:
res = self.login_user(user, password)
self.api_key = res["AccessToken"]
else:
self.api_key = password
self.auth_headers = {
"X-Emby-Authorization": 'MediaBrowser Client="MediaDash", Device="Python", DeviceId="{}", Version="{}", Token="{}"'.format(
self.device_id, RQ.__version__, self.api_key)}
# ws_url=self.url.replace("http","ws").rstrip("/")+"/?"+urlencode({"api_key":self.api_key,"deviceId":self.device_id})
# self.ws = websocket.WebSocketApp(ws_url,on_open=print,on_error=print,on_message=print,on_close=print)
# self.ws_thread = Thread(target=self.ws.run_forever,daemon=True)
self.session.headers.update(
{**self.auth_headers, "X-Emby-Token": self.api_key})
self.user = self.get_self()
self.user_id = self.user["Id"]
self.playstate_commands = sorted(
[
"Stop",
"Pause",
"Unpause",
"NextTrack",
"PreviousTrack",
"Seek",
"Rewind",
"FastForward",
"PlayPause",
]
)
self.session_commands = sorted(
[
"MoveUp",
"MoveDown",
"MoveLeft",
"MoveRight",
"PageUp",
"PageDown",
"PreviousLetter",
"NextLetter",
"ToggleOsd",
"ToggleContextMenu",
"Select",
"Back",
"TakeScreenshot",
"SendKey",
"SendString",
"GoHome",
"GoToSettings",
"VolumeUp",
"VolumeDown",
"Mute",
"Unmute",
"ToggleMute",
"SetVolume",
"SetAudioStreamIndex",
"SetSubtitleStreamIndex",
"ToggleFullscreen",
"DisplayContent",
"GoToSearch",
"DisplayMessage",
"SetRepeatMode",
"ChannelUp",
"ChannelDown",
"Guide",
"ToggleStats",
"PlayMediaSource",
"PlayTrailers",
"SetShuffleQueue",
"PlayState",
"PlayNext",
"ToggleOsdMenu",
"Play",
]
)
def login_user(self, user, passwd):
res = self.post(
"Users/AuthenticateByName",
json={"Username": user, "Pw": passwd},
headers=self.auth_headers,
)
res.raise_for_status()
res = res.json()
self.session.headers.update(
{**self.auth_headers, "X-Emby-Token": res["AccessToken"]}
)
return res
def logout(self):
self.session.post(urljoin(self.url, "Sessions/Logout"))
def status(self):
res = self.session.get(urljoin(self.url, "System/Info"))
res.raise_for_status()
return res.json()
def chapter_image_url(self, item_id, chapter_num, tag):
return urljoin(
self.url,
"Items",
item_id,
"Images",
"Chapter",
chapter_num)
def rq(self, method, url, *args, **kwargs):
res = self.session.request(
method, urljoin(
self.url, url), *args, **kwargs)
res.raise_for_status()
return res
def get(self, url, *args, **kwargs):
res = self.session.get(urljoin(self.url, url), *args, **kwargs)
res.raise_for_status()
return res
def post(self, url, *args, **kwargs):
res = self.session.post(urljoin(self.url, url), *args, **kwargs)
res.raise_for_status()
return res
def sessions(self):
res = self.get("Sessions")
res.raise_for_status()
return res.json()
@timed_cache()
def season_episodes(self, item_id, season_id):
res = self.get(
"Shows/{}/Episodes".format(item_id),
params={
"UserId": self.user_id,
"seasonId": season_id,
"fields": "Overview,MediaStreams,MediaSources,ExternalUrls",
},
)
res.raise_for_status()
res = res.json()["Items"]
for episode in res:
episode["Info"] = self.media_info(episode["Id"])
return res
@timed_cache()
def seasons(self, item_id):
res = self.get(
"Shows/{}/Seasons".format(item_id),
params={
"UserId": self.user_id,
"fields": "Overview,MediaStreams,MediaSources,ExternalUrls",
},
)
res.raise_for_status()
res = res.json()["Items"]
for season in res:
season["Episodes"] = self.season_episodes(item_id, season["Id"])
return res
@timed_cache()
def media_info(self, item_id):
res = self.get(
"Users/{}/Items/{}".format(self.user_id, item_id),
)
res.raise_for_status()
res = res.json()
if res["Type"] == "Series":
res["Seasons"] = self.seasons(item_id)
return res
def system_info(self):
res = self.get("System/Info")
res.raise_for_status()
return res.json()
def __get_child_items(self, item_id):
print(item_id)
res = self.get(
"Users/{}/Items".format(self.user_id),
params={"ParentId": item_id},
)
res.raise_for_status()
return res.json()
def get_recursive(self, item_id):
for item in self.__get_child_items(item_id).get("Items", []):
yield item
yield from self.get_recursive(item["Id"])
def get_counts(self):
res = self.get("Items/Counts").json()
return res
@timed_cache(seconds=10)
def id_map(self):
res = self.get(
"Users/{}/Items".format(self.user_id),
params={
"recursive": True,
"includeItemTypes": "Movie,Series",
"collapseBoxSetItems": False,
"fields": "ProviderIds",
},
)
res.raise_for_status()
res = res.json()["Items"]
id_map = {}
for item in res:
for _, prov_id in item["ProviderIds"].items():
for prov in ["Imdb", "Tmdb", "Tvdb"]:
id_map[(prov.lower(), prov_id)] = item["Id"]
return id_map
@timed_cache()
def get_library(self):
res = self.get(
"Users/{}/Items".format(self.user_id),
params={
"recursive": True,
"includeItemTypes": "Movie,Series",
"collapseBoxSetItems": False,
},
).json()
library = {}
for item in res["Items"]:
library[item["Id"]] = item
for item in res["Items"]:
for key, value in item.copy().items():
if key != "Id" and key.endswith("Id"):
key = key[:-2]
if value in library and key not in item:
item[key] = library[value]
return library
def get_usage(self):
report = self.post(
"user_usage_stats/submit_custom_query",
params={"stamp": int(time.time())},
json={
"CustomQueryString": "SELECT * FROM PlaybackActivity",
"ReplaceUserId": True,
},
).json()
ret = []
for row in report["results"]:
rec = dict(zip(report["colums"], row))
rec["PlayDuration"] = timedelta(seconds=int(rec["PlayDuration"]))
ts = rec.pop("DateCreated")
if ts:
rec["Timestamp"] = parse_datetime(ts)
ret.append(rec)
return ret
def __db_fetch(self, endpoint):
ret = []
res = self.session.get(
urljoin(
self.url,
endpoint),
params={
"StartIndex": 0,
"IncludeItemTypes": "*",
"ReportColumns": ""},
)
res.raise_for_status()
res = res.json()
headers = [h["Name"].lower() for h in res["Headers"]]
for row in res["Rows"]:
fields = [c["Name"] for c in row["Columns"]]
ret.append(dict(zip(headers, fields)))
ret[-1]["row_type"] = row["RowType"]
return ret
def get_self(self):
res = self.get("Users/Me")
return res.json()
def get_users(self, user_id=None):
if user_id:
res = self.get("Users/{}".format(user_id))
else:
res = self.get("Users")
res.raise_for_status()
return res.json()
def activity(self):
return self.__db_fetch("Reports/Activities")
def report(self):
return self.__db_fetch("Reports/Items")
def stop_session(self, session_id):
sessions = self.get("Sessions").json()
for session in sessions:
if session["Id"] == session_id and "NowPlayingItem" in session:
s_id = session["Id"]
u_id = session["UserId"]
i_id = session["NowPlayingItem"]["Id"]
d_id = session["DeviceId"]
self.rq(
"delete",
"Videos/ActiveEncodings",
params={"deviceId": d_id, "playSessionId": s_id},
)
self.rq("delete", f"Users/{u_id}/PlayingItems/{i_id}")
self.rq("post", f"Sessions/{s_id}/Playing/Stop")
def test(self):
self.status()
return {}