mirror of
https://github.com/jellyfin/jellyfin-mpv-shim.git
synced 2024-11-23 14:09:57 +00:00
Implement Plex transcode decision processing.
This commit is contained in:
parent
e33f9001a2
commit
4ec642e2bf
39
README.md
39
README.md
@ -24,15 +24,9 @@ The project supports the following:
|
||||
- Playing multiple videos in a queue.
|
||||
- The app doesn't require or save any Plex passwords or tokens.
|
||||
- Executing commands before playing, after media end, and when stopped.
|
||||
- Configurable transcoding support based on remote server and bitrate.
|
||||
- Configurable transcoding support. (Please see the section below.)
|
||||
- The application shows up in Plex dashboard and usage tracking.
|
||||
|
||||
Transcoding is supported, but needs work:
|
||||
- Transcode bandwidth decisions are currently based on values in the config file.
|
||||
- Playback of videos can fail on remote servers if the available bandwidth is lower.
|
||||
- Changing subtitle/audio tracks cannot be done after starting transcode playback.
|
||||
- The only way to control transcode video quality is using the config file.
|
||||
|
||||
You'll need [libmpv1](https://github.com/Kagami/mpv.js/blob/master/README.md#get-libmpv). To install `plex-mpv-shim`, run:
|
||||
```bash
|
||||
sudo pip3 install --upgrade plex-mpv-shim
|
||||
@ -60,20 +54,35 @@ Keyboard Shortcuts:
|
||||
- u to mark unwatched and quit
|
||||
|
||||
You can execute shell commands on media state using the config file:
|
||||
- media\_ended\_cmd - When all media has played.
|
||||
- pre\_media\_cmd - Before the player displays. (Will wait for finish.)
|
||||
- stop\_cmd - After stopping the player.
|
||||
- idle\_cmd - After no activity for idle\_cmd\_delay seconds.
|
||||
- `media_ended_cmd` - When all media has played.
|
||||
- `pre_media_cmd` - Before the player displays. (Will wait for finish.)
|
||||
- `stop_cmd` - After stopping the player.
|
||||
- `idle_cmd` - After no activity for `idle_cmd_delay` seconds.
|
||||
|
||||
This project is based on https://github.com/wnielson/omplex, which
|
||||
is available under the terms of the MIT License. The project was ported
|
||||
to python3, modified to use mpv as the player, and updated to allow all
|
||||
features of the remote control api for video playback.
|
||||
|
||||
UPDATE: It looks like we have a reversal on the Plex Media Player situation.
|
||||
That being said, this project has proven to be interesting as a hackable
|
||||
Plex client. **I plan to maintain this client, although I may not work on
|
||||
adding new features unless someone requests them.**
|
||||
## Transcoding Support
|
||||
|
||||
Plex-MPV-Shim 1.2 introduces revamped transcoding support. It will automatically ask the server to see if transcoding is suggested, which enables Plex-MPV-Shim to play more of your library on the go. You can configure this or switch to the old local transcode decision system.
|
||||
|
||||
- `always_transcode`: This will tell the client to always transcode, without asking. Default: `false`
|
||||
- This may be useful if you are using limited hardware that cannot handle advanced codecs.
|
||||
- You may have some luck changing `client_profile` in the configuration to a more restrictive one.
|
||||
- `auto_transcode`: This will ask the server to determine if transcoding is suggested. Default: `true`
|
||||
- `transcode_kbps`: Transcode bandwidth to request. Default: `2000`
|
||||
- `transcode_res`: Transcode resolution to request. Default: `720p`
|
||||
- `remote_transcode`: This will check for transcoding using locally available metadata for remote servers only. Default: `true`
|
||||
- This will not take effect if `auto_transcode` is enabled.
|
||||
- Configuration options from `auto_transcode` are also used.
|
||||
- `remote_kbps_thresh`: The threshold to force transcoding. If this is lower than the configured server bandwidth, playback may fail.
|
||||
- `adaptive_transcode`: Tell the server to adjust the quality while streaming. Default: `false`
|
||||
|
||||
Caveats:
|
||||
- Controlling Plex-MPV-Shim from the Plex web application only works on a LAN where a Plex Server resides. It does NOT have to be the one you are streaming from. An empty server will work.
|
||||
- The only way to configure transcode quality is the config file. There is no native way to configure transcode quality from the Plex remote control interface. I may implement an on-screen menu to adjust this and other settings.
|
||||
|
||||
## Building on Windows
|
||||
|
||||
|
@ -28,10 +28,13 @@ class Settings(object):
|
||||
"idle_cmd": None,
|
||||
"idle_cmd_delay": 60,
|
||||
"always_transcode": False,
|
||||
"auto_transcode": True,
|
||||
"adaptive_transcode": False,
|
||||
"remote_transcode": True,
|
||||
"remote_kbps_thresh": 5000,
|
||||
"transcode_kbps": 2000,
|
||||
"transcode_res": "720p",
|
||||
"client_profile": "Plex Home Theater",
|
||||
}
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import urllib.parse
|
||||
import requests
|
||||
import uuid
|
||||
|
||||
try:
|
||||
@ -9,7 +10,7 @@ except:
|
||||
import xml.etree.ElementTree as et
|
||||
|
||||
from .conf import settings
|
||||
from .utils import get_plex_url, safe_urlopen, is_local_domain, get_resolution
|
||||
from .utils import get_plex_url, safe_urlopen, is_local_domain, get_resolution, get_session, reset_session
|
||||
|
||||
log = logging.getLogger('media')
|
||||
|
||||
@ -32,6 +33,8 @@ class Video(object):
|
||||
self.audio_seq = {}
|
||||
self.audio_uid = {}
|
||||
self.is_transcode = False
|
||||
self.trs_aid = None
|
||||
self.trs_sid = None
|
||||
|
||||
if media:
|
||||
self.select_media(media, part)
|
||||
@ -54,10 +57,13 @@ class Video(object):
|
||||
self.subtitle_seq[sub.attrib["id"]] = index+1
|
||||
|
||||
def get_transcode_streams(self):
|
||||
audio_obj = self._part_node.find("./Stream[@streamType='2'][@selected='1']")
|
||||
subtitle_obj = self._part_node.find("./Stream[@streamType='3'][@selected='1']")
|
||||
return (audio_obj.get("id") if audio_obj else None,
|
||||
subtitle_obj.get("id") if subtitle_obj else None)
|
||||
if not self.trs_aid:
|
||||
audio_obj = self._part_node.find("./Stream[@streamType='2'][@selected='1']")
|
||||
self.trs_aid = audio_obj.get("id") if audio_obj is not None else None
|
||||
if not self.trs_sid:
|
||||
subtitle_obj = self._part_node.find("./Stream[@streamType='3'][@selected='1']")
|
||||
self.trs_sid = subtitle_obj.get("id") if subtitle_obj is not None else None
|
||||
return self.trs_aid, self.trs_sid
|
||||
|
||||
def select_best_media(self, part=0):
|
||||
"""
|
||||
@ -107,6 +113,22 @@ class Video(object):
|
||||
return False
|
||||
return len(self._media_node.findall("./Part")) > 1
|
||||
|
||||
def set_streams(self, audio_uid, sub_uid):
|
||||
args = {"allParts": "1"}
|
||||
|
||||
if audio_uid is not None:
|
||||
args["audioStreamID"] = audio_uid
|
||||
self.trs_aid = audio_uid
|
||||
|
||||
if sub_uid is not None:
|
||||
args["subtitleStreamID"] = sub_uid
|
||||
self.trs_sid = sub_uid
|
||||
|
||||
if self._part_node != None:
|
||||
partid = self._part_node.get("id")
|
||||
url = "/library/parts/{0}".format(partid)
|
||||
requests.put(get_plex_url(urllib.parse.urljoin(self.parent.server_url, url), args), data=None)
|
||||
|
||||
def get_proper_title(self):
|
||||
if not hasattr(self, "_title"):
|
||||
media_type = self.node.get('type')
|
||||
@ -136,12 +158,80 @@ class Video(object):
|
||||
setattr(self, "_title", title)
|
||||
return getattr(self, "_title")
|
||||
|
||||
def is_transcode_suggested(self):
|
||||
def get_formats(self):
|
||||
audio_formats = []
|
||||
protocols = "protocols=http-video,http-live-streaming,http-mp4-streaming,http-mp4-video,http-mp4-video-720p,http-streaming-video,http-streaming-video-720p;videoDecoders=mpeg4,h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac{channels:8}"
|
||||
if settings.audio_ac3passthrough:
|
||||
audio_formats.append("add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=ac3)")
|
||||
audio_formats.append("add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=eac3)")
|
||||
protocols += ",ac3{bitrate:800000&channels:8}"
|
||||
if settings.audio_dtspassthrough:
|
||||
audio_formats.append("add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=dca)")
|
||||
protocols += ",dts{bitrate:800000&channels:8}"
|
||||
|
||||
return audio_formats, protocols
|
||||
|
||||
def is_transcode_suggested(self, video_height=None, video_width=None,
|
||||
video_bitrate=None, video_quality=100):
|
||||
request_direct_play = "1"
|
||||
request_subtitle_mode = "none"
|
||||
is_local = is_local_domain(self.parent.path.hostname)
|
||||
|
||||
# User would like us to always transcode.
|
||||
if settings.always_transcode:
|
||||
return True
|
||||
elif (settings.remote_transcode and not is_local_domain(self.parent.path.hostname)
|
||||
request_direct_play = "0"
|
||||
request_subtitle_mode = "auto"
|
||||
# Check locally if we should transcode or direct play. (Legacy)
|
||||
elif (settings.remote_transcode and not settings.auto_transcode and not is_local
|
||||
and int(self.node.find("./Media").get("bitrate")) > settings.remote_kbps_thresh):
|
||||
request_direct_play = "0"
|
||||
request_subtitle_mode = "auto"
|
||||
|
||||
# Regardless of if we need the data from the decision, Plex will sometimes deny access
|
||||
# if there is no decision for the current session.
|
||||
audio_formats, protocols = self.get_formats()
|
||||
|
||||
url = "/video/:/transcode/universal/decision"
|
||||
args = {
|
||||
"hasMDE": "1",
|
||||
"path": self.node.get("key"),
|
||||
"session": get_session(self.parent.path.hostname),
|
||||
"protocol": "hls",
|
||||
"directPlay": request_direct_play,
|
||||
"directStream": "1",
|
||||
"fastSeek": "1",
|
||||
"maxVideoBitrate": str(video_bitrate),
|
||||
"videoQuality": str(video_quality),
|
||||
"videoResolution": "%sx%s" % (video_width, video_height),
|
||||
"mediaIndex": self._media or 0,
|
||||
"partIndex": self._part or 0,
|
||||
"location": "lan" if is_local else "wan",
|
||||
"autoAdjustQuality": str(int(settings.adaptive_transcode)),
|
||||
"directStreamAudio": "1",
|
||||
"subtitles": request_subtitle_mode, # Setting this to auto or burn breaks direct play.
|
||||
"copyts": "1",
|
||||
}
|
||||
|
||||
if audio_formats:
|
||||
args["X-Plex-Client-Profile-Extra"] = "+".join(audio_formats)
|
||||
args["X-Plex-Client-Capabilities"] = protocols
|
||||
|
||||
tree = et.parse(urllib.request.urlopen(get_plex_url(urllib.parse.urljoin(self.parent.server_url, url), args)))
|
||||
treeRoot = tree.getroot()
|
||||
decisionText = treeRoot.get("generalDecisionText") or treeRoot.get("mdeDecisionText")
|
||||
decision = treeRoot.get("generalDecisionCode") or treeRoot.get("mdeDecisionCode")
|
||||
log.debug("Decision: {0}: {1}".format(decision, decisionText))
|
||||
|
||||
if request_direct_play == "0":
|
||||
return True
|
||||
# Use the decision from the Plex server.
|
||||
elif settings.auto_transcode:
|
||||
if decision == "1000":
|
||||
return False
|
||||
elif decision == "1001":
|
||||
return True
|
||||
else:
|
||||
log.error("Server reports that file cannot be streamed.")
|
||||
return False
|
||||
|
||||
def get_playback_url(self, direct_play=None, offset=0,
|
||||
@ -150,9 +240,18 @@ class Video(object):
|
||||
"""
|
||||
Returns the URL to use for the trancoded file.
|
||||
"""
|
||||
reset_session(self.parent.path.hostname)
|
||||
|
||||
if video_height is None or video_width is None:
|
||||
video_width, video_height = get_resolution(settings.transcode_res)
|
||||
|
||||
if video_bitrate is None:
|
||||
video_bitrate = settings.transcode_kbps
|
||||
|
||||
if direct_play is None:
|
||||
# See if transcoding is suggested
|
||||
direct_play = not self.is_transcode_suggested()
|
||||
direct_play = not self.is_transcode_suggested(video_height, video_width,
|
||||
video_bitrate, video_quality)
|
||||
|
||||
if direct_play:
|
||||
if not self._part_node:
|
||||
@ -162,39 +261,31 @@ class Video(object):
|
||||
return get_plex_url(url)
|
||||
|
||||
self.is_transcode = True
|
||||
|
||||
if video_height is None or video_width is None:
|
||||
video_width, video_height = get_resolution(settings.transcode_res)
|
||||
|
||||
if video_bitrate is None:
|
||||
video_bitrate = settings.transcode_kbps
|
||||
is_local = is_local_domain(self.parent.path.hostname)
|
||||
|
||||
url = "/video/:/transcode/universal/start.m3u8"
|
||||
args = {
|
||||
"path": self.node.get("key"),
|
||||
"session": settings.client_uuid,
|
||||
"protocol": "hls",
|
||||
"directPlay": "0",
|
||||
"directStream": "1",
|
||||
"fastSeek": "1",
|
||||
"maxVideoBitrate": str(video_bitrate),
|
||||
"videoQuality": str(video_quality),
|
||||
"videoResolution": "%sx%s" % (video_width,video_height),
|
||||
"mediaIndex": self._media or 0,
|
||||
"partIndex": self._part or 0,
|
||||
"offset": offset,
|
||||
"path": self.node.get("key"),
|
||||
"session": get_session(self.parent.path.hostname),
|
||||
"protocol": "hls",
|
||||
"directPlay": "0",
|
||||
"directStream": "1",
|
||||
"fastSeek": "1",
|
||||
"maxVideoBitrate": str(video_bitrate),
|
||||
"videoQuality": str(video_quality),
|
||||
"videoResolution": "%sx%s" % (video_width,video_height),
|
||||
"mediaIndex": self._media or 0,
|
||||
"partIndex": self._part or 0,
|
||||
"location": "lan" if is_local else "wan",
|
||||
"offset": offset,
|
||||
"autoAdjustQuality": str(int(settings.adaptive_transcode)),
|
||||
"directStreamAudio": "1",
|
||||
"subtitles": "auto",
|
||||
"copyts": "1",
|
||||
#"skipSubtitles": "1",
|
||||
}
|
||||
|
||||
audio_formats = []
|
||||
protocols = "protocols=http-live-streaming,http-mp4-streaming,http-mp4-video,http-mp4-video-720p,http-streaming-video,http-streaming-video-720p;videoDecoders=mpeg4,h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac{channels:8}"
|
||||
if settings.audio_ac3passthrough:
|
||||
audio_formats.append("add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=ac3)")
|
||||
audio_formats.append("add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=eac3)")
|
||||
protocols += ",ac3{bitrate:800000&channels:8}"
|
||||
if settings.audio_dtspassthrough:
|
||||
audio_formats.append("add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=dca)")
|
||||
protocols += ",dts{bitrate:800000&channels:8}"
|
||||
audio_formats, protocols = self.get_formats()
|
||||
|
||||
if audio_formats:
|
||||
args["X-Plex-Client-Profile-Extra"] = "+".join(audio_formats)
|
||||
@ -300,7 +391,7 @@ class XMLCollection(object):
|
||||
return self.path.path
|
||||
|
||||
class Media(XMLCollection):
|
||||
def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=None, session=None):
|
||||
def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=None):
|
||||
XMLCollection.__init__(self, url)
|
||||
self.video = self.tree.find('./Video')
|
||||
self.is_tv = self.video.get("type") == "episode"
|
||||
@ -310,11 +401,6 @@ class Media(XMLCollection):
|
||||
self.play_queue = play_queue
|
||||
self.play_queue_xml = play_queue_xml
|
||||
|
||||
if session:
|
||||
self.session = session
|
||||
else:
|
||||
self.session = str(uuid.uuid4())
|
||||
|
||||
if self.play_queue:
|
||||
if not series:
|
||||
self.upd_play_queue()
|
||||
@ -380,21 +466,21 @@ class Media(XMLCollection):
|
||||
if self.play_queue and self.seq+1 == len(self.series):
|
||||
self.upd_play_queue()
|
||||
next_video = self.series[self.seq+1]
|
||||
return Media(self.get_path(next_video.get('key')), self.series, self.seq+1, self.play_queue, self.play_queue_xml, session=self.session)
|
||||
return Media(self.get_path(next_video.get('key')), self.series, self.seq+1, self.play_queue, self.play_queue_xml)
|
||||
|
||||
def get_prev(self):
|
||||
if self.has_prev:
|
||||
if self.play_queue and self.seq-1 == 0:
|
||||
self.upd_play_queue()
|
||||
prev_video = self.series[self.seq-1]
|
||||
return Media(self.get_path(prev_video.get('key')), self.series, self.seq-1, self.play_queue, self.play_queue_xml, session=self.session)
|
||||
return Media(self.get_path(prev_video.get('key')), self.series, self.seq-1, self.play_queue, self.play_queue_xml)
|
||||
|
||||
def get_from_key(self, key):
|
||||
if self.play_queue:
|
||||
self.upd_play_queue()
|
||||
for i, video in enumerate(self.series):
|
||||
if video.get("key") == key:
|
||||
return Media(self.get_path(key), self.series, i, self.play_queue, self.play_queue_xml, session=self.session)
|
||||
return Media(self.get_path(key), self.series, i, self.play_queue, self.play_queue_xml)
|
||||
return None
|
||||
else:
|
||||
return Media(self.get_path(key))
|
||||
|
@ -1,12 +1,14 @@
|
||||
import logging
|
||||
import mpv
|
||||
import os
|
||||
import requests
|
||||
import urllib.parse
|
||||
|
||||
from threading import RLock
|
||||
from queue import Queue
|
||||
|
||||
from . import conffile
|
||||
from .utils import synchronous, Timer
|
||||
from .utils import synchronous, Timer, get_plex_url
|
||||
from .conf import settings
|
||||
|
||||
APP_NAME = 'plex-mpv-shim'
|
||||
@ -248,6 +250,12 @@ class PlayerManager(object):
|
||||
return True
|
||||
return False
|
||||
|
||||
@synchronous('_lock')
|
||||
def restart_playback(self):
|
||||
current_time = self._player.playback_time
|
||||
self.play(self._video, current_time)
|
||||
return True
|
||||
|
||||
@synchronous('_lock')
|
||||
def get_video_attr(self, attr, default=None):
|
||||
if self._video:
|
||||
@ -256,16 +264,22 @@ class PlayerManager(object):
|
||||
|
||||
@synchronous('_lock')
|
||||
def set_streams(self, audio_uid, sub_uid):
|
||||
if audio_uid is not None:
|
||||
log.debug("PlayerManager::play selecting audio stream index=%s" % audio_uid)
|
||||
self._player.audio = self._video.audio_seq[audio_uid]
|
||||
if not self._video.is_transcode:
|
||||
if audio_uid is not None:
|
||||
log.debug("PlayerManager::play selecting audio stream index=%s" % audio_uid)
|
||||
self._player.audio = self._video.audio_seq[audio_uid]
|
||||
|
||||
if sub_uid == '0':
|
||||
log.debug("PlayerManager::play selecting subtitle stream (none)")
|
||||
self._player.sub = 'no'
|
||||
elif sub_uid is not None:
|
||||
log.debug("PlayerManager::play selecting subtitle stream index=%s" % sub_uid)
|
||||
self._player.sub = self._video.subtitle_seq[sub_uid]
|
||||
if sub_uid == '0':
|
||||
log.debug("PlayerManager::play selecting subtitle stream (none)")
|
||||
self._player.sub = 'no'
|
||||
elif sub_uid is not None:
|
||||
log.debug("PlayerManager::play selecting subtitle stream index=%s" % sub_uid)
|
||||
self._player.sub = self._video.subtitle_seq[sub_uid]
|
||||
|
||||
self._video.set_streams(audio_uid, sub_uid)
|
||||
|
||||
if self._video.is_transcode:
|
||||
self.restart_playback()
|
||||
|
||||
playerManager = PlayerManager()
|
||||
|
||||
|
@ -66,18 +66,13 @@ class TimelineManager(threading.Thread):
|
||||
# Also send timeline to plex server.
|
||||
video = playerManager._video
|
||||
options = self.GetCurrentTimeline()
|
||||
session = None
|
||||
server_url = None
|
||||
if video:
|
||||
server_url = video.parent.server_url
|
||||
self.last_server_url = video.parent.server_url
|
||||
session = video.parent.session
|
||||
self.last_session = video.parent.session
|
||||
elif self.last_server_url and self.last_session:
|
||||
elif self.last_server_url:
|
||||
server_url = self.last_server_url
|
||||
session = self.last_session
|
||||
if server_url and session:
|
||||
options["X-Plex-Session-Identifier"] = session
|
||||
if server_url:
|
||||
url = safe_urlopen("%s/:/timeline" % server_url, options, quiet=True)
|
||||
|
||||
def SendTimelineToSubscriber(self, subscriber):
|
||||
@ -193,9 +188,8 @@ class TimelineManager(threading.Thread):
|
||||
controllable.append("skipTo")
|
||||
controllable.append("autoPlay")
|
||||
|
||||
if not video.is_transcode:
|
||||
controllable.append("subtitleStream")
|
||||
controllable.append("audioStream")
|
||||
controllable.append("subtitleStream")
|
||||
controllable.append("audioStream")
|
||||
|
||||
if video.parent.has_next:
|
||||
controllable.append("skipNext")
|
||||
@ -225,7 +219,10 @@ class TimelineManager(threading.Thread):
|
||||
options["containerKey"] = video.get_video_attr("key")
|
||||
if video.parent.play_queue:
|
||||
options.update(video.parent.get_queue_info())
|
||||
options["state"] = "stopped"
|
||||
if player.playback_abort:
|
||||
options["state"] = "stopped"
|
||||
else:
|
||||
options["state"] = "buffering"
|
||||
|
||||
return options
|
||||
|
||||
|
@ -3,6 +3,7 @@ import os
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import socket
|
||||
import ipaddress
|
||||
import uuid
|
||||
|
||||
from .conf import settings
|
||||
from datetime import datetime
|
||||
@ -10,6 +11,7 @@ from functools import wraps
|
||||
|
||||
log = logging.getLogger("utils")
|
||||
plex_eph_tokens = {}
|
||||
plex_sessions = {}
|
||||
|
||||
class Timer(object):
|
||||
def __init__(self):
|
||||
@ -45,6 +47,17 @@ def synchronous(tlockname):
|
||||
def upd_token(domain, token):
|
||||
plex_eph_tokens[domain] = token
|
||||
|
||||
def get_session(domain):
|
||||
if domain not in plex_sessions:
|
||||
session = str(uuid.uuid4())
|
||||
plex_sessions[domain] = session
|
||||
return plex_sessions[domain]
|
||||
|
||||
def reset_session(domain):
|
||||
session = str(uuid.uuid4())
|
||||
plex_sessions[domain] = session
|
||||
return session
|
||||
|
||||
def get_plex_url(url, data=None, quiet=False):
|
||||
if not data:
|
||||
data = {}
|
||||
@ -62,17 +75,23 @@ def get_plex_url(url, data=None, quiet=False):
|
||||
else:
|
||||
log.error("get_plex_url No token for: %s" % domain)
|
||||
|
||||
if domain in plex_sessions:
|
||||
data.update({
|
||||
"X-Plex-Session-Identifier": plex_sessions[domain]
|
||||
})
|
||||
|
||||
data.update({
|
||||
"X-Plex-Version": "2.0",
|
||||
"X-Plex-Client-Identifier": settings.client_uuid,
|
||||
"X-Plex-Provides": "player",
|
||||
"X-Plex-Device-Name": settings.player_name,
|
||||
"X-Plex-Model": "RaspberryPI",
|
||||
"X-Plex-Device": "RaspberryPI",
|
||||
"X-Plex-Version": "2.0",
|
||||
"X-Plex-Client-Identifier": settings.client_uuid,
|
||||
"X-Plex-Provides": "player",
|
||||
"X-Plex-Device-Name": settings.player_name,
|
||||
"X-Plex-Model": "RaspberryPI",
|
||||
"X-Plex-Device": "RaspberryPI",
|
||||
|
||||
# Lies
|
||||
"X-Plex-Product": "Plex Home Theater",
|
||||
"X-Plex-Platform": "Plex Home Theater"
|
||||
"X-Plex-Product": "Plex MPV Shim",
|
||||
"X-Plex-Platform": "Plex Home Theater",
|
||||
"X-Plex-Client-Profile-Name": settings.client_profile,
|
||||
})
|
||||
|
||||
# Kinda ghetto...
|
||||
|
Loading…
Reference in New Issue
Block a user