Files
jellycon/resources/lib/play_utils.py

1288 lines
46 KiB
Python
Raw Normal View History

# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmc
import xbmcgui
import xbmcaddon
from datetime import timedelta
import json
2018-08-04 16:49:37 +10:00
import os
import base64
2021-01-26 22:34:39 -05:00
from six.moves.urllib.parse import urlparse
from .loghandler import LazyLogger
2018-09-26 08:41:23 +10:00
from .downloadutils import DownloadUtils
from .resume_dialog import ResumeDialog
2020-06-21 11:27:09 +10:00
from .utils import PlayUtils, get_art, send_event_notification, convert_size
2018-09-26 08:41:23 +10:00
from .kodi_utils import HomeWindow
2018-10-26 14:04:54 +11:00
from .translation import string_load
from .datamanager import DataManager, clear_old_cache_data
2018-11-11 15:08:07 +11:00
from .item_functions import extract_item_info, add_gui_item
2018-09-26 08:41:23 +10:00
from .clientinfo import ClientInformation
from .cache_images import CacheArtwork
from .picture_viewer import PictureViewer
2019-10-14 11:55:18 +11:00
from .tracking import timer
2020-07-03 16:39:22 +10:00
from .playnext import PlayNextDialog
log = LazyLogger(__name__)
download_utils = DownloadUtils()
2020-06-21 11:27:09 +10:00
2021-01-26 22:34:39 -05:00
def play_all_files(items, play_items=True):
home_window = HomeWindow()
log.debug("playAllFiles called with items: {0}", items)
2020-06-21 11:27:09 +10:00
server = download_utils.get_server()
2018-01-07 23:59:39 +11:00
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
playlist_data = {}
2018-01-07 23:59:39 +11:00
for item in items:
item_id = item.get("Id")
# get playback info
playback_info = download_utils.get_item_playback_info(item_id, False)
if playback_info is None:
log.debug("playback_info was None, could not get MediaSources so can not play!")
return
if playback_info.get("ErrorCode") is not None:
error_string = playback_info.get("ErrorCode")
xbmcgui.Dialog().notification(string_load(30316),
error_string,
icon="special://home/addons/plugin.video.jellycon/icon.png")
return
play_session_id = playback_info.get("PlaySessionId")
# select the media source to use
sources = playback_info.get('MediaSources')
2018-01-07 23:59:39 +11:00
selected_media_source = sources[0]
2018-11-08 20:31:28 +11:00
source_id = selected_media_source.get("Id")
2018-01-07 23:59:39 +11:00
playurl, playback_type, listitem_props = PlayUtils().get_play_url(selected_media_source, play_session_id)
log.info("Play URL: {0} PlaybackType: {1} ListItem Properties: {2}".format(playurl, playback_type, listitem_props))
2018-01-07 23:59:39 +11:00
if playurl is None:
2020-06-21 11:27:09 +10:00
return
2018-01-07 23:59:39 +11:00
playback_type_string = "DirectPlay"
if playback_type == "2":
playback_type_string = "Transcode"
elif playback_type == "1":
playback_type_string = "DirectStream"
# add the playback type into the overview
if item.get("Overview", None) is not None:
item["Overview"] = playback_type_string + "\n" + item.get("Overview")
else:
item["Overview"] = playback_type_string
# add title decoration is needed
2018-10-26 14:04:54 +11:00
item_title = item.get("Name", string_load(30280))
2018-01-07 23:59:39 +11:00
list_item = xbmcgui.ListItem(label=item_title)
# add playurl and data to the monitor
playlist_data[playurl] = {}
playlist_data[playurl]["item_id"] = item_id
playlist_data[playurl]["source_id"] = source_id
playlist_data[playurl]["playback_type"] = playback_type_string
playlist_data[playurl]["play_session_id"] = play_session_id
playlist_data[playurl]["play_action_type"] = "play_all"
home_window.set_property('playlist', json.dumps(playlist_data))
# Set now_playing to the first track
if len(playlist_data) == 1:
home_window.set_property('now_playing', json.dumps(playlist_data[playurl]))
2018-01-07 23:59:39 +11:00
list_item.setPath(playurl)
2020-06-21 11:27:09 +10:00
list_item = set_list_item_props(item_id, list_item, item, server, listitem_props, item_title)
2018-01-07 23:59:39 +11:00
playlist.add(playurl, list_item)
2020-06-28 12:25:46 +10:00
if play_items:
xbmc.Player().play(playlist)
return None
else:
return playlist
2018-01-07 23:59:39 +11:00
2021-01-26 22:34:39 -05:00
def play_list_of_items(id_list):
log.debug("Loading all items in the list")
data_manager = DataManager()
items = []
2020-06-21 11:27:09 +10:00
for item_id in id_list:
url = "{server}/Users/{userid}/Items/%s?format=json"
2020-06-21 11:27:09 +10:00
url = url % (item_id,)
result = data_manager.get_content(url)
if result is None:
log.debug("Playfile item was None, so can not play!")
return
items.append(result)
2021-01-26 22:34:39 -05:00
return play_all_files(items)
2020-06-21 11:27:09 +10:00
2021-01-26 22:34:39 -05:00
def add_to_playlist(play_info):
log.debug("Adding item to playlist : {0}".format(play_info))
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
2020-06-21 11:27:09 +10:00
server = download_utils.get_server()
item_id = play_info.get("item_id")
url = "{server}/Users/{userid}/Items/%s?format=json"
url = url % (item_id,)
data_manager = DataManager()
2020-06-21 11:27:09 +10:00
item = data_manager.get_content(url)
if item is None:
log.debug("Playfile item was None, so can not play!")
return
# get playback info
playback_info = download_utils.get_item_playback_info(item_id, False)
if playback_info is None:
log.debug("playback_info was None, could not get MediaSources so can not play!")
return
if playback_info.get("ErrorCode") is not None:
error_string = playback_info.get("ErrorCode")
xbmcgui.Dialog().notification(string_load(30316),
error_string,
icon="special://home/addons/plugin.video.jellycon/icon.png")
return
play_session_id = playback_info.get("PlaySessionId")
# select the media source to use
sources = playback_info.get('MediaSources')
selected_media_source = sources[0]
source_id = selected_media_source.get("Id")
playurl, playback_type, listitem_props = PlayUtils().get_play_url(selected_media_source, play_session_id)
log.info("Play URL: {0} PlaybackType: {1} ListItem Properties: {2}".format(playurl, playback_type, listitem_props))
if playurl is None:
return
playback_type_string = "DirectPlay"
if playback_type == "2":
playback_type_string = "Transcode"
elif playback_type == "1":
playback_type_string = "DirectStream"
# add the playback type into the overview
if item.get("Overview", None) is not None:
item["Overview"] = playback_type_string + "\n" + item.get("Overview")
else:
item["Overview"] = playback_type_string
# add title decoration is needed
item_title = item.get("Name", string_load(30280))
list_item = xbmcgui.ListItem(label=item_title)
# add playurl and data to the monitor
data = {}
data["item_id"] = item_id
data["source_id"] = source_id
data["playback_type"] = playback_type_string
data["play_session_id"] = play_session_id
data["play_action_type"] = "play_all"
list_item.setPath(playurl)
2020-06-21 11:27:09 +10:00
list_item = set_list_item_props(item_id, list_item, item, server, listitem_props, item_title)
playlist.add(playurl, list_item)
2020-06-28 12:25:46 +10:00
def get_playback_intros(item_id):
log.debug("get_playback_intros")
data_manager = DataManager()
url = "{server}/Users/{userid}/Items/%s/Intros" % item_id
2020-06-28 12:25:46 +10:00
intro_items = data_manager.get_content(url)
if intro_items is None:
log.debug("get_playback_intros failed!")
return
into_list = []
intro_items = intro_items["Items"]
for into in intro_items:
into_list.append(into)
return into_list
2019-10-14 11:55:18 +11:00
@timer
2021-01-26 22:34:39 -05:00
def play_file(play_info):
2020-06-21 11:27:09 +10:00
item_id = play_info.get("item_id")
home_window = HomeWindow()
2020-06-21 11:27:09 +10:00
last_url = home_window.get_property("last_content_url")
if last_url:
2020-06-21 11:27:09 +10:00
home_window.set_property("skip_cache_for_" + last_url, "true")
action = play_info.get("action", "play")
if action == "add_to_playlist":
2021-01-26 22:34:39 -05:00
add_to_playlist(play_info)
return
# if this is a list of items them add them all to the play list
2020-06-21 11:27:09 +10:00
if isinstance(item_id, list):
2021-01-26 22:34:39 -05:00
return play_list_of_items(item_id)
2017-12-14 13:43:23 +11:00
auto_resume = play_info.get("auto_resume", "-1")
force_transcode = play_info.get("force_transcode", False)
media_source_id = play_info.get("media_source_id", "")
subtitle_stream_index = play_info.get("subtitle_stream_index", None)
audio_stream_index = play_info.get("audio_stream_index", None)
log.debug("playFile id({0}) resume({1}) force_transcode({2})".format(item_id, auto_resume, force_transcode))
settings = xbmcaddon.Addon()
addon_path = settings.getAddonInfo('path')
force_auto_resume = settings.getSetting('forceAutoResume') == 'true'
jump_back_amount = int(settings.getSetting("jump_back_amount"))
2020-06-28 12:25:46 +10:00
play_cinema_intros = settings.getSetting('play_cinema_intros') == 'true'
2020-06-21 11:27:09 +10:00
server = download_utils.get_server()
url = "{server}/Users/{userid}/Items/%s?format=json" % (item_id,)
2017-12-28 16:19:14 +11:00
data_manager = DataManager()
2020-06-21 11:27:09 +10:00
result = data_manager.get_content(url)
log.debug("Playfile item: {0}".format(result))
if result is None:
log.debug("Playfile item was None, so can not play!")
return
# if this is a season, playlist or album then play all items in that parent
if result.get("Type") in ["Season", "MusicAlbum", "Playlist"]:
log.debug("PlayAllFiles for parent item id: {0}".format(item_id))
url = ('{server}/Users/{userid}/items' +
'?ParentId=%s' +
'&Fields=MediaSources' +
'&format=json')
2020-06-21 11:27:09 +10:00
url = url % (item_id,)
result = data_manager.get_content(url)
log.debug("PlayAllFiles items: {0}".format(result))
# process each item
items = result["Items"]
if items is None:
items = []
2021-01-26 22:34:39 -05:00
return play_all_files(items)
2018-01-07 23:59:39 +11:00
2018-12-09 13:42:01 +11:00
# if this is a program from live tv epg then play the actual channel
if result.get("Type") == "Program":
channel_id = result.get("ChannelId")
url = "{server}/Users/{userid}/Items/%s?format=json" % (channel_id,)
2020-06-21 11:27:09 +10:00
result = data_manager.get_content(url)
item_id = result["Id"]
2018-12-09 13:42:01 +11:00
if result.get("Type") == "Photo":
play_url = "%s/Items/%s/Images/Primary"
2020-06-21 11:27:09 +10:00
play_url = play_url % (server, item_id)
plugin_path = xbmc.translatePath(os.path.join(xbmcaddon.Addon().getAddonInfo('path')))
action_menu = PictureViewer("PictureViewer.xml", plugin_path, "default", "720p")
action_menu.setPicture(play_url)
action_menu.doModal()
return
# get playback info from the server using the device profile
playback_info = download_utils.get_item_playback_info(item_id, force_transcode)
if playback_info is None:
log.debug("playback_info was None, could not get MediaSources so can not play!")
return
if playback_info.get("ErrorCode") is not None:
error_string = playback_info.get("ErrorCode")
xbmcgui.Dialog().notification(string_load(30316),
error_string,
icon="special://home/addons/plugin.video.jellycon/icon.png")
return
play_session_id = playback_info.get("PlaySessionId")
# select the media source to use
media_sources = playback_info.get('MediaSources')
selected_media_source = None
if media_sources is None or len(media_sources) == 0:
log.debug("Play Failed! There is no MediaSources data!")
return
2017-12-14 13:43:23 +11:00
elif len(media_sources) == 1:
selected_media_source = media_sources[0]
2017-12-14 13:43:23 +11:00
elif media_source_id != "":
for source in media_sources:
if source.get("Id", "na") == media_source_id:
selected_media_source = source
break
elif len(media_sources) > 1:
items = []
for source in media_sources:
label = source.get("Name", "na")
label2 = __build_label2_from(source)
2020-06-21 11:27:09 +10:00
items.append(xbmcgui.ListItem(label=label, label2=label2))
dialog = xbmcgui.Dialog()
2020-06-21 11:27:09 +10:00
resp = dialog.select(string_load(30309), items, useDetails=True)
if resp > -1:
selected_media_source = media_sources[resp]
else:
log.debug("Play Aborted, user did not select a MediaSource")
return
if selected_media_source is None:
log.debug("Play Aborted, MediaSource was None")
return
2018-11-08 20:31:28 +11:00
source_id = selected_media_source.get("Id")
2020-06-21 11:27:09 +10:00
seek_time = 0
auto_resume = int(auto_resume)
# process user data for resume points
if auto_resume != -1:
2020-06-21 11:27:09 +10:00
seek_time = (auto_resume / 1000) / 10000
elif force_auto_resume:
2020-06-21 11:27:09 +10:00
user_data = result.get("UserData")
reasonable_ticks = int(user_data.get("PlaybackPositionTicks")) / 1000
seek_time = reasonable_ticks / 10000
else:
2020-06-21 11:27:09 +10:00
user_data = result.get("UserData")
if user_data.get("PlaybackPositionTicks") != 0:
reasonable_ticks = int(user_data.get("PlaybackPositionTicks")) / 1000
seek_time = reasonable_ticks / 10000
display_time = str(timedelta(seconds=seek_time))
resume_dialog = ResumeDialog("ResumeDialog.xml", addon_path, "default", "720p")
resume_dialog.setResumeTime("Resume from " + display_time)
resume_dialog.doModal()
resume_result = resume_dialog.getResumeAction()
del resume_dialog
log.debug("Resume Dialog Result: {0}".format(resume_result))
if resume_result == 1:
2020-06-21 11:27:09 +10:00
seek_time = 0
elif resume_result == -1:
return
log.debug("play_session_id: {0}".format(play_session_id))
playurl, playback_type, listitem_props = PlayUtils().get_play_url(selected_media_source, play_session_id)
log.info("Play URL: {0} Playback Type: {1} ListItem Properties: {2}".format(playurl, playback_type, listitem_props))
2020-06-21 11:27:09 +10:00
if playurl is None:
return
2017-05-25 18:16:01 +10:00
playback_type_string = "DirectPlay"
2017-09-14 19:55:28 +10:00
if playback_type == "2":
2017-05-25 18:16:01 +10:00
playback_type_string = "Transcode"
elif playback_type == "1":
playback_type_string = "DirectStream"
2017-05-25 18:16:01 +10:00
# add the playback type into the overview
if result.get("Overview", None) is not None:
result["Overview"] = playback_type_string + "\n" + result.get("Overview")
else:
result["Overview"] = playback_type_string
# add title decoration is needed
2018-10-26 14:04:54 +11:00
item_title = result.get("Name", string_load(30280))
# extract item info from result
gui_options = {}
gui_options["server"] = server
gui_options["name_format"] = None
gui_options["name_format_type"] = ""
item_details = extract_item_info(result, gui_options)
# create ListItem
display_options = {}
display_options["addCounts"] = False
display_options["addResumePercent"] = False
display_options["addSubtitleAvailable"] = False
display_options["addUserRatings"] = False
gui_item = add_gui_item("", item_details, display_options, False)
list_item = gui_item[1]
2020-06-21 11:27:09 +10:00
if playback_type == "2": # if transcoding then prompt for audio and subtitle
playurl = audio_subs_pref(playurl, list_item, selected_media_source, item_id, audio_stream_index,
subtitle_stream_index)
log.debug("New playurl for transcoding: {0}".format(playurl))
2020-06-21 11:27:09 +10:00
elif playback_type == "1": # for direct stream add any streamable subtitles
external_subs(selected_media_source, list_item, item_id)
# add playurl and data to the monitor
data = {}
2020-06-21 11:27:09 +10:00
data["item_id"] = item_id
2018-11-08 20:31:28 +11:00
data["source_id"] = source_id
data["playback_type"] = playback_type_string
data["play_session_id"] = play_session_id
2018-01-07 23:59:39 +11:00
data["play_action_type"] = "play"
2020-03-28 17:58:29 +11:00
data["item_type"] = result.get("Type", None)
data["can_delete"] = result.get("CanDelete", False)
2021-05-28 23:33:13 -04:00
# Check for next episodes
if result.get('Type') == 'Episode':
next_episode = get_next_episode(result)
data["next_episode"] = next_episode
2021-01-26 22:34:39 -05:00
home_window.set_property('now_playing', json.dumps(data))
list_item.setPath(playurl)
2020-06-21 11:27:09 +10:00
list_item = set_list_item_props(item_id, list_item, result, server, listitem_props, item_title)
player = xbmc.Player()
2020-06-28 12:25:46 +10:00
intro_items = []
if play_cinema_intros and seek_time == 0:
intro_items = get_playback_intros(item_id)
if len(intro_items) > 0:
2021-01-26 22:34:39 -05:00
playlist = play_all_files(intro_items, play_items=False)
2020-06-28 12:25:46 +10:00
playlist.add(playurl, list_item)
else:
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
playlist.add(playurl, list_item)
player.play(playlist)
2020-06-21 11:27:09 +10:00
if seek_time != 0:
2019-07-21 19:48:58 +10:00
player.pause()
monitor = xbmc.Monitor()
count = 0
while not player.isPlaying() and not monitor.abortRequested() and count != 100:
count = count + 1
xbmc.sleep(100)
2019-07-21 19:48:58 +10:00
if count == 100 or not player.isPlaying() or monitor.abortRequested():
log.info("PlaybackResumrAction : Playback item did not get to a play state in 10 seconds so exiting")
player.stop()
return
2019-07-21 19:48:58 +10:00
log.info("PlaybackResumrAction : Playback is Running")
2020-06-21 11:27:09 +10:00
seek_to_time = seek_time - jump_back_amount
2019-07-21 19:48:58 +10:00
target_seek = (seek_to_time - 10)
count = 0
max_loops = 2 * 120
while not monitor.abortRequested() and player.isPlaying() and count < max_loops:
log.info("PlaybackResumrAction : Seeking to : {0}".format(seek_to_time))
2019-07-21 19:48:58 +10:00
player.seekTime(seek_to_time)
current_position = player.getTime()
if current_position >= target_seek:
break
log.info("PlaybackResumrAction : target:{0} current:{1}".format(target_seek, current_position))
count = count + 1
2019-07-21 19:48:58 +10:00
xbmc.sleep(500)
if count == max_loops:
2019-07-21 19:48:58 +10:00
log.info("PlaybackResumrAction : Playback could not seek to required position")
player.stop()
else:
2019-07-21 19:48:58 +10:00
count = 0
while bool(xbmc.getCondVisibility("Player.Paused")) and count < 10:
2019-07-21 19:48:58 +10:00
log.info("PlaybackResumrAction : Unpausing playback")
player.pause()
xbmc.sleep(1000)
count = count + 1
if count == 10:
2019-07-21 19:48:58 +10:00
log.info("PlaybackResumrAction : Could not unpause")
else:
log.info("PlaybackResumrAction : Playback resumed")
def __build_label2_from(source):
videos = [item for item in source.get('MediaStreams', {}) if item.get('Type') == "Video"]
audios = [item for item in source.get('MediaStreams', {}) if item.get('Type') == "Audio"]
subtitles = [item for item in source.get('MediaStreams', {}) if item.get('Type') == "Subtitle"]
details = [str(convert_size(source.get('Size', 0)))]
for video in videos:
details.append('{} {} {}bit'.format(video.get('DisplayTitle', ''),
video.get('VideoRange', ''),
video.get('BitDepth', '')))
aud = []
for audio in audios:
aud.append('{} {} {}'.format(audio.get('Language', ''),
audio.get('Codec', ''),
audio.get('Channels', '')))
if len(aud) > 0:
details.append(', '.join(aud).upper())
subs = []
for subtitle in subtitles:
subs.append(subtitle.get('Language', ''))
if len(subs) > 0:
details.append('S: {}'.format(', '.join(subs)).upper())
return ' | '.join(details)
2018-11-11 15:08:07 +11:00
def get_next_episode(item):
2021-10-09 21:55:01 +02:00
if item.get("Type") != "Episode":
2018-11-11 15:08:07 +11:00
log.debug("Not an episode, can not get next")
return None
2021-10-09 21:55:01 +02:00
parent_id = item.get("ParentId")
item_index = item.get("IndexNumber")
2018-11-11 15:08:07 +11:00
2021-10-09 21:55:01 +02:00
if parent_id is None:
2018-11-11 15:08:07 +11:00
log.debug("No parent id, can not get next")
return None
2021-10-09 21:55:01 +02:00
if item_index is None:
2018-11-11 15:08:07 +11:00
log.debug("No episode number, can not get next")
return None
url = ('{server}/Users/{userid}/Items?' +
2020-06-21 11:27:09 +10:00
'?Recursive=true' +
'&ParentId=' + parent_id +
'&IsVirtualUnaired=false' +
'&IsMissing=False' +
'&IncludeItemTypes=Episode' +
'&ImageTypeLimit=1' +
'&format=json')
2018-11-11 15:08:07 +11:00
data_manager = DataManager()
2020-06-21 11:27:09 +10:00
items_result = data_manager.get_content(url)
log.debug("get_next_episode, sibling list: {0}".format(items_result))
2018-11-11 15:08:07 +11:00
if items_result is None:
log.debug("get_next_episode no results")
return None
2021-10-09 21:55:01 +02:00
item_list = items_result.get("Items") or []
2018-11-11 15:08:07 +11:00
for item in item_list:
# find the very next episode in the season
2021-10-09 21:55:01 +02:00
if item.get("IndexNumber") == item_index + 1:
log.debug("get_next_episode, found next episode: {0}".format(item))
2018-11-11 15:08:07 +11:00
return item
return None
2020-03-28 17:58:29 +11:00
def send_next_episode_details(item, next_episode):
if next_episode is None:
log.debug("No next episode")
return
gui_options = {}
2020-06-21 11:27:09 +10:00
gui_options["server"] = download_utils.get_server()
gui_options["name_format"] = None
gui_options["name_format_type"] = ""
item_details = extract_item_info(item, gui_options)
next_item_details = extract_item_info(next_episode, gui_options)
current_item = {}
current_item["episodeid"] = item_details.id
current_item["tvshowid"] = item_details.series_name
current_item["title"] = item_details.name
current_item["art"] = {}
current_item["art"]["tvshow.poster"] = item_details.art.get('tvshow.poster', '')
current_item["art"]["thumb"] = item_details.art.get('thumb', '')
current_item["art"]["tvshow.fanart"] = item_details.art.get('tvshow.fanart', '')
current_item["art"]["tvshow.landscape"] = item_details.art.get('tvshow.landscape', '')
current_item["art"]["tvshow.clearart"] = item_details.art.get('tvshow.clearart', '')
current_item["art"]["tvshow.clearlogo"] = item_details.art.get('tvshow.clearlogo', '')
current_item["plot"] = item_details.plot
current_item["showtitle"] = item_details.series_name
current_item["playcount"] = item_details.play_count
current_item["season"] = item_details.season_number
current_item["episode"] = item_details.episode_number
current_item["rating"] = item_details.critic_rating
current_item["firstaired"] = item_details.year
next_item = {}
next_item["episodeid"] = next_item_details.id
next_item["tvshowid"] = next_item_details.series_name
next_item["title"] = next_item_details.name
next_item["art"] = {}
next_item["art"]["tvshow.poster"] = next_item_details.art.get('tvshow.poster', '')
next_item["art"]["thumb"] = next_item_details.art.get('thumb', '')
next_item["art"]["tvshow.fanart"] = next_item_details.art.get('tvshow.fanart', '')
next_item["art"]["tvshow.landscape"] = next_item_details.art.get('tvshow.landscape', '')
next_item["art"]["tvshow.clearart"] = next_item_details.art.get('tvshow.clearart', '')
next_item["art"]["tvshow.clearlogo"] = next_item_details.art.get('tvshow.clearlogo', '')
next_item["plot"] = next_item_details.plot
next_item["showtitle"] = next_item_details.series_name
next_item["playcount"] = next_item_details.play_count
next_item["season"] = next_item_details.season_number
next_item["episode"] = next_item_details.episode_number
next_item["rating"] = next_item_details.critic_rating
next_item["firstaired"] = next_item_details.year
next_info = {
"current_episode": current_item,
"next_episode": next_item,
"play_info": {
"item_id": next_item_details.id,
"auto_resume": False,
"force_transcode": False
}
}
2021-05-28 23:33:13 -04:00
send_event_notification("upnext_data", next_info, True)
2020-06-21 11:27:09 +10:00
def set_list_item_props(item_id, list_item, result, server, extra_props, title):
# set up item and item info
2020-06-21 11:27:09 +10:00
art = get_art(result, server=server)
list_item.setArt(art)
2020-06-21 11:27:09 +10:00
list_item.setProperty('IsPlayable', 'false')
list_item.setProperty('IsFolder', 'false')
list_item.setProperty('id', result.get("Id"))
for prop in extra_props:
2020-06-21 11:27:09 +10:00
list_item.setProperty(prop[0], prop[1])
2018-01-03 11:53:24 +11:00
item_type = result.get("Type", "").lower()
mediatype = 'video'
if item_type == 'movie' or item_type == 'boxset':
mediatype = 'movie'
elif item_type == 'series':
mediatype = 'tvshow'
elif item_type == 'season':
mediatype = 'season'
elif item_type == 'episode':
mediatype = 'episode'
2018-01-07 23:59:39 +11:00
elif item_type == 'audio':
mediatype = 'song'
2018-01-03 11:53:24 +11:00
2018-01-07 23:59:39 +11:00
if item_type == "audio":
details = {
'title': title,
'mediatype': mediatype
}
2020-06-21 11:27:09 +10:00
list_item.setInfo("Music", infoLabels=details)
2018-01-07 23:59:39 +11:00
else:
details = {
'title': title,
'plot': result.get("Overview"),
'mediatype': mediatype
}
2018-01-07 23:59:39 +11:00
tv_show_name = result.get("SeriesName")
if tv_show_name is not None:
details['tvshowtitle'] = tv_show_name
2018-01-03 11:53:24 +11:00
if item_type == "episode":
episode_number = result.get("IndexNumber", -1)
2018-01-07 23:59:39 +11:00
details["episode"] = str(episode_number)
season_number = result.get("ParentIndexNumber", -1)
details["season"] = str(season_number)
elif item_type == "season":
season_number = result.get("IndexNumber", -1)
2018-01-07 23:59:39 +11:00
details["season"] = str(season_number)
details["plotoutline"] = "jellyfin_id:%s" % (item_id,)
2020-06-21 11:27:09 +10:00
list_item.setInfo("Video", infoLabels=details)
2020-06-21 11:27:09 +10:00
return list_item
# For transcoding only
# Present the list of audio and subtitles to select from
# for external streamable subtitles add the URL to the Kodi item and let Kodi handle it
# else ask for the subtitles to be burnt in when transcoding
2020-06-21 11:27:09 +10:00
def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, subtitle_stream_index):
dialog = xbmcgui.Dialog()
2020-06-21 11:27:09 +10:00
audio_streams_list = {}
audio_streams = []
subtitle_streams_list = {}
subtitle_streams = ['No subtitles']
downloadable_streams = []
select_audio_index = audio_stream_index
select_subs_index = subtitle_stream_index
playurlprefs = ""
default_audio = media_source.get('DefaultAudioStreamIndex', 1)
default_sub = media_source.get('DefaultSubtitleStreamIndex', "")
2018-11-08 16:09:44 +11:00
source_id = media_source["Id"]
media_streams = media_source['MediaStreams']
for stream in media_streams:
# Since Jellyfin returns all possible tracks together, have to sort them.
index = stream['Index']
if 'Audio' in stream['Type']:
codec = stream['Codec']
2020-06-21 11:27:09 +10:00
channel_layout = stream.get('ChannelLayout', "")
try:
2020-06-21 11:27:09 +10:00
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channel_layout)
except:
2020-06-21 11:27:09 +10:00
track = "%s - %s %s" % (index, codec, channel_layout)
2020-06-21 11:27:09 +10:00
audio_streams_list[track] = index
audio_streams.append(track)
elif 'Subtitle' in stream['Type']:
try:
track = "%s - %s" % (index, stream['Language'])
except:
track = "%s - %s" % (index, stream['Codec'])
default = stream['IsDefault']
forced = stream['IsForced']
downloadable = stream['IsTextSubtitleStream'] and stream['IsExternal'] and stream['SupportsExternalStream']
if default:
track = "%s - Default" % track
if forced:
track = "%s - Forced" % track
if downloadable:
2020-06-21 11:27:09 +10:00
downloadable_streams.append(index)
2020-06-21 11:27:09 +10:00
subtitle_streams_list[track] = index
subtitle_streams.append(track)
# set audio index
2020-06-21 11:27:09 +10:00
if select_audio_index is not None:
playurlprefs += "&AudioStreamIndex=%s" % select_audio_index
2017-12-14 13:43:23 +11:00
2020-06-21 11:27:09 +10:00
elif len(audio_streams) > 1:
resp = dialog.select(string_load(30291), audio_streams)
if resp > -1:
# User selected audio
2020-06-21 11:27:09 +10:00
selected = audio_streams[resp]
select_audio_index = audio_streams_list[selected]
playurlprefs += "&AudioStreamIndex=%s" % select_audio_index
else: # User backed out of selection
playurlprefs += "&AudioStreamIndex=%s" % default_audio
2017-12-14 13:43:23 +11:00
# set subtitle index
2020-06-21 11:27:09 +10:00
if select_subs_index is not None:
# Load subtitles in the listitem if downloadable
2020-06-21 11:27:09 +10:00
if select_subs_index in downloadable_streams:
subtitle_url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
2020-06-21 11:27:09 +10:00
subtitle_url = subtitle_url % (download_utils.get_server(), item_id, source_id, select_subs_index)
log.debug("Streaming subtitles url: {0} {1}".format(select_subs_index, subtitle_url))
2020-06-21 11:27:09 +10:00
list_item.setSubtitles([subtitle_url])
else:
# Burn subtitles
2020-06-21 11:27:09 +10:00
playurlprefs += "&SubtitleStreamIndex=%s" % select_subs_index
2020-06-21 11:27:09 +10:00
elif len(subtitle_streams) > 1:
resp = dialog.select(string_load(30292), subtitle_streams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
2020-06-21 11:27:09 +10:00
selected = subtitle_streams[resp]
select_subs_index = subtitle_streams_list[selected]
# Load subtitles in the listitem if downloadable
2020-06-21 11:27:09 +10:00
if select_subs_index in downloadable_streams:
subtitle_url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
2020-06-21 11:27:09 +10:00
subtitle_url = subtitle_url % (download_utils.get_server(), item_id, source_id, select_subs_index)
log.debug("Streaming subtitles url: {0} {1}".format(select_subs_index, subtitle_url))
2020-06-21 11:27:09 +10:00
list_item.setSubtitles([subtitle_url])
else:
# Burn subtitles
2020-06-21 11:27:09 +10:00
playurlprefs += "&SubtitleStreamIndex=%s" % select_subs_index
else: # User backed out of selection
playurlprefs += "&SubtitleStreamIndex=%s" % default_sub
if url.find("|verifypeer=false") != -1:
new_url = url.replace("|verifypeer=false", playurlprefs + "|verifypeer=false")
else:
new_url = url + playurlprefs
return new_url
# direct stream, set any available subtitle streams
2020-06-21 11:27:09 +10:00
def external_subs(media_source, list_item, item_id):
2018-12-09 13:42:01 +11:00
media_streams = media_source.get('MediaStreams')
2018-08-04 16:49:37 +10:00
if media_streams is None:
return
2019-01-25 15:34:25 +11:00
externalsubs = []
sub_names = []
for stream in media_streams:
if (stream['Type'] == "Subtitle"
and stream['IsExternal']
and stream['IsTextSubtitleStream']
and stream['SupportsExternalStream']):
index = stream['Index']
2018-11-08 16:09:44 +11:00
source_id = media_source['Id']
2020-06-21 11:27:09 +10:00
server = download_utils.get_server()
2019-01-25 15:34:25 +11:00
token = download_utils.authenticate()
language = stream.get('Language', '')
codec = stream.get('Codec', '')
2019-01-25 15:34:25 +11:00
url_root = '{}/Videos/{}/{}/Subtitles/{}'.format(server, item_id, source_id, index)
if language:
url = '{}/0/Stream.{}.{}?api_key={}'.format(
url_root, language, codec, token)
2019-01-25 15:34:25 +11:00
else:
url = '{}/0/Stream.{}?api_key={}'.format(url_root, codec, token)
2019-01-25 15:34:25 +11:00
default = ""
if stream['IsDefault']:
default = "default"
forced = ""
if stream['IsForced']:
forced = "forced"
sub_name = '{} ( {} ) {} {}'.format(language, codec, default, forced)
2019-01-25 15:34:25 +11:00
sub_names.append(sub_name)
externalsubs.append(url)
2019-01-25 19:18:16 +11:00
if len(externalsubs) == 0:
return
2019-01-25 15:34:25 +11:00
settings = xbmcaddon.Addon()
direct_stream_sub_select = settings.getSetting("direct_stream_sub_select")
if direct_stream_sub_select == "0" or (len(externalsubs) == 1 and not direct_stream_sub_select == "2"):
list_item.setSubtitles(externalsubs)
else:
resp = xbmcgui.Dialog().select(string_load(30292), sub_names)
if resp > -1:
selected_sub = externalsubs[resp]
log.debug("External Subtitle Selected: {0}".format(selected_sub))
2019-01-25 15:34:25 +11:00
list_item.setSubtitles([selected_sub])
2021-01-26 22:34:39 -05:00
def send_progress():
home_window = HomeWindow()
2021-12-11 21:00:28 -05:00
play_data = get_playing_data()
if play_data is None:
return
log.debug("Sending Progress Update")
player = xbmc.Player()
item_id = play_data.get("item_id")
if item_id is None:
return
play_time = player.getTime()
total_play_time = player.getTotalTime()
play_data["current_position"] = play_time
2018-12-28 16:18:23 +11:00
play_data["duration"] = total_play_time
play_data["currently_playing"] = True
home_window.set_property('now_playing', json.dumps(play_data))
2018-11-08 20:31:28 +11:00
source_id = play_data.get("source_id")
ticks = int(play_time * 10000000)
2018-12-28 16:18:23 +11:00
duration = int(total_play_time * 10000000)
paused = play_data.get("paused", False)
playback_type = play_data.get("playback_type")
play_session_id = play_data.get("play_session_id")
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist_position = playlist.getposition()
playlist_size = playlist.size()
volume, muted = get_volume()
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': item_id,
2018-11-08 20:31:28 +11:00
'MediaSourceId': source_id,
'PositionTicks': ticks,
2018-12-28 16:18:23 +11:00
'RunTimeTicks': duration,
'IsPaused': paused,
'IsMuted': muted,
'PlayMethod': playback_type,
'PlaySessionId': play_session_id,
'PlaylistIndex': playlist_position,
'PlaylistLength': playlist_size,
'VolumeLevel': volume
}
log.debug("Sending POST progress started: {0}".format(postdata))
url = "{server}/Sessions/Playing/Progress"
2020-06-21 11:27:09 +10:00
download_utils.download_url(url, post_body=postdata, method="POST")
def get_volume():
json_data = xbmc.executeJSONRPC(
'{ "jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume", "muted"]}, "id": 1 }')
result = json.loads(json_data)
result = result.get('result', {})
volume = result.get('volume')
muted = result.get('muted')
return volume, muted
2018-12-29 09:22:33 +11:00
def prompt_for_stop_actions(item_id, data):
log.debug("prompt_for_stop_actions Called : {0}".format(data))
settings = xbmcaddon.Addon()
current_position = data.get("current_position", 0)
2018-12-29 09:22:33 +11:00
duration = data.get("duration", 0)
2020-03-28 17:58:29 +11:00
next_episode = data.get("next_episode")
item_type = data.get("item_type")
can_delete = data.get("can_delete", False)
prompt_next_percentage = int(settings.getSetting('promptPlayNextEpisodePercentage'))
play_prompt = settings.getSetting('promptPlayNextEpisodePercentage_prompt') == "true"
prompt_delete_episode_percentage = int(settings.getSetting('promptDeleteEpisodePercentage'))
prompt_delete_movie_percentage = int(settings.getSetting('promptDeleteMoviePercentage'))
2018-12-28 16:18:23 +11:00
# everything is off so return
if (prompt_next_percentage == 100 and
prompt_delete_episode_percentage == 100 and
prompt_delete_movie_percentage == 100):
return
prompt_to_delete = False
# if no runtime we cant calculate perceantge so just return
2020-03-28 17:58:29 +11:00
if duration == 0:
log.debug("No duration so returing")
return
# item percentage complete
2020-03-28 17:58:29 +11:00
percenatge_complete = int((current_position / duration) * 100)
log.debug("Episode Percentage Complete: {0}".format(percenatge_complete))
2020-03-28 17:58:29 +11:00
if (can_delete and
prompt_delete_episode_percentage < 100 and
item_type == "Episode" and
percenatge_complete > prompt_delete_episode_percentage):
prompt_to_delete = True
2020-03-28 17:58:29 +11:00
if (can_delete and
prompt_delete_movie_percentage < 100 and
item_type == "Movie" and
percenatge_complete > prompt_delete_movie_percentage):
prompt_to_delete = True
# prompt for next episode
if (next_episode is not None and
2020-03-28 17:58:29 +11:00
prompt_next_percentage < 100 and
item_type == "Episode" and
percenatge_complete > prompt_next_percentage):
index = next_episode.get("IndexNumber", -1)
if play_prompt:
2020-07-03 16:39:22 +10:00
plugin_path = settings.getAddonInfo('path')
plugin_path_real = xbmc.translatePath(os.path.join(plugin_path))
2020-03-28 17:58:29 +11:00
2020-07-03 16:39:22 +10:00
play_next_dialog = PlayNextDialog("PlayNextDialog.xml", plugin_path_real, "default", "720p")
play_next_dialog.set_episode_info(next_episode)
play_next_dialog.doModal()
if not play_next_dialog.get_play_called():
xbmc.executebuiltin("Container.Refresh")
2021-12-11 21:00:28 -05:00
def stop_all_playback():
home_window = HomeWindow()
played_information_string = home_window.get_property('played_information')
if played_information_string:
played_information = json.loads(played_information_string)
else:
played_information = {}
log.debug("stop_all_playback : {0}".format(played_information))
2020-03-28 17:58:29 +11:00
if len(played_information) == 0:
return
log.debug("played_information: {0}".format(played_information))
clear_entries = []
2021-12-11 21:00:28 -05:00
home_window.clear_property("currently_playing_id")
for item in played_information:
data = played_information.get(item)
2018-01-06 17:03:44 +11:00
if data.get("currently_playing", False) is True:
log.debug("item_data: {0}".format(data))
current_position = data.get("current_position", 0)
2018-12-29 09:22:33 +11:00
duration = data.get("duration", 0)
jellyfin_item_id = data.get("item_id")
jellyfin_source_id = data.get("source_id")
2020-04-21 19:40:03 +10:00
play_session_id = data.get("play_session_id")
if jellyfin_item_id is not None and current_position >= 0:
log.debug("Playback Stopped at: {0}".format(current_position))
url = "{server}/Sessions/Playing/Stopped"
postdata = {
'ItemId': jellyfin_item_id,
'MediaSourceId': jellyfin_source_id,
2018-12-29 09:22:33 +11:00
'PositionTicks': int(current_position * 10000000),
2020-04-21 19:40:03 +10:00
'RunTimeTicks': int(duration * 10000000),
'PlaySessionId': play_session_id
}
2020-06-21 11:27:09 +10:00
download_utils.download_url(url, post_body=postdata, method="POST")
data["currently_playing"] = False
2018-01-07 23:59:39 +11:00
if data.get("play_action_type", "") == "play":
prompt_for_stop_actions(jellyfin_item_id, data)
clear_entries.append(item)
if data.get('playback_type') == 'Transcode':
device_id = ClientInformation().get_device_id()
url = "{server}/Videos/ActiveEncodings?DeviceId=%s" % device_id
download_utils.download_url(url, method="DELETE")
for entry in clear_entries:
del played_information[entry]
2021-12-11 21:00:28 -05:00
home_window.set_property('played_information', json.dumps(played_information))
2018-12-29 09:22:33 +11:00
2021-01-26 22:34:39 -05:00
def get_playing_data():
2021-12-11 21:00:28 -05:00
player = xbmc.Player()
2021-05-28 23:33:13 -04:00
home_window = HomeWindow()
play_data_string = home_window.get_property('now_playing')
play_data = json.loads(play_data_string)
2021-12-11 21:00:28 -05:00
played_information_string = home_window.get_property('played_information')
if played_information_string:
played_information = json.loads(played_information_string)
else:
played_information = {}
playlist_data_string = home_window.get_property('playlist')
2021-12-11 21:00:28 -05:00
if playlist_data_string:
playlist_data = json.loads(playlist_data_string)
else:
playlist_data = {}
item_id = play_data.get("item_id")
2021-05-28 23:33:13 -04:00
2021-01-26 22:34:39 -05:00
settings = xbmcaddon.Addon()
server = settings.getSetting('server_address')
try:
2021-12-11 21:00:28 -05:00
playing_file = player.getPlayingFile()
except Exception as e:
log.error("get_playing_data : getPlayingFile() : {0}".format(e))
return None
log.debug("get_playing_data : getPlayingFile() : {0}".format(playing_file))
2021-12-11 21:00:28 -05:00
if server in playing_file and item_id is not None:
play_time = player.getTime()
total_play_time = player.getTotalTime()
if item_id is not None and item_id not in playing_file and playing_file in playlist_data:
# if the current now_playing data isn't correct, pull it from the playlist_data
play_data = playlist_data.pop(playing_file)
# Update now_playing data
home_window.set_property('playlist', json.dumps(playlist_data))
2021-12-11 21:00:28 -05:00
play_data["current_position"] = play_time
play_data["duration"] = total_play_time
played_information[item_id] = play_data
home_window.set_property('now_playing', json.dumps(play_data))
home_window.set_property('played_information', json.dumps(played_information))
return play_data
2021-12-05 22:43:21 -05:00
return {}
2018-12-29 09:22:33 +11:00
class Service(xbmc.Player):
def __init__(self, *args):
log.debug("Starting monitor service: {0}".format(args))
def onPlayBackStarted(self):
# Will be called when xbmc starts playing a file
2021-12-11 21:00:28 -05:00
stop_all_playback()
2018-08-04 16:49:37 +10:00
if not xbmc.Player().isPlaying():
log.debug("onPlayBackStarted: not playing file!")
return
2021-02-16 18:54:46 -05:00
play_data = get_playing_data()
2018-12-29 09:22:33 +11:00
if play_data is None:
return
2018-12-29 09:22:33 +11:00
play_data["paused"] = False
play_data["currently_playing"] = True
jellyfin_item_id = play_data["item_id"]
jellyfin_source_id = play_data["source_id"]
2018-12-29 09:22:33 +11:00
playback_type = play_data["playback_type"]
play_session_id = play_data["play_session_id"]
# if we could not find the ID of the current item then return
if jellyfin_item_id is None:
return
2021-12-11 21:00:28 -05:00
home_window = HomeWindow()
played_information_string = home_window.get_property('played_information')
played_information = json.loads(played_information_string)
played_information[jellyfin_item_id] = play_data
home_window.set_property('played_information', json.dumps(played_information))
log.debug("Sending Playback Started")
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': jellyfin_item_id,
'MediaSourceId': jellyfin_source_id,
'PlayMethod': playback_type,
'PlaySessionId': play_session_id
}
log.debug("Sending POST play started: {0}".format(postdata))
url = "{server}/Sessions/Playing"
2020-06-21 11:27:09 +10:00
download_utils.download_url(url, post_body=postdata, method="POST")
home_screen = HomeWindow()
home_screen.set_property("currently_playing_id", str(jellyfin_item_id))
def onPlayBackEnded(self):
# Will be called when kodi stops playing a file
2020-03-28 17:58:29 +11:00
log.debug("onPlayBackEnded")
2021-12-11 21:00:28 -05:00
stop_all_playback()
def onPlayBackStopped(self):
# Will be called when user stops kodi playing a file
log.debug("onPlayBackStopped")
2021-12-11 21:00:28 -05:00
stop_all_playback()
def onPlayBackPaused(self):
# Will be called when kodi pauses the video
log.debug("onPlayBackPaused")
2018-12-28 16:18:23 +11:00
2021-02-16 18:54:46 -05:00
play_data = get_playing_data()
if play_data is not None:
play_data['paused'] = True
2021-01-26 22:34:39 -05:00
send_progress()
def onPlayBackResumed(self):
# Will be called when kodi resumes the video
log.debug("onPlayBackResumed")
2018-12-28 16:18:23 +11:00
2021-02-16 18:54:46 -05:00
play_data = get_playing_data()
if play_data is not None:
play_data['paused'] = False
2021-02-16 18:54:46 -05:00
send_progress()
2020-06-21 11:27:09 +10:00
def onPlayBackSeek(self, time, seek_offset):
# Will be called when kodi seeks in video
log.debug("onPlayBackSeek")
2021-02-16 18:54:46 -05:00
send_progress()
class PlaybackService(xbmc.Monitor):
background_image_cache_thread = None
def __init__(self, monitor):
self.monitor = monitor
def onNotification(self, sender, method, data):
if method == 'GUI.OnScreensaverActivated':
self.screensaver_activated()
return
elif method == 'GUI.OnScreensaverDeactivated':
self.screensaver_deactivated()
return
elif method == 'System.OnQuit':
home_window = HomeWindow()
home_window.set_property('exit', 'True')
return
2021-05-28 23:33:13 -04:00
if sender != 'plugin.video.jellycon':
return
signal = method.split('.', 1)[-1]
if signal not in ("jellycon_play_action", "jellycon_play_youtube_trailer_action", "set_view"):
return
data_json = json.loads(data)
2021-05-28 23:33:13 -04:00
play_info = data_json[0]
log.debug("PlaybackService:onNotification:{0}".format(play_info))
if signal == "jellycon_play_action":
2021-05-28 23:33:13 -04:00
play_file(play_info)
elif signal == "jellycon_play_youtube_trailer_action":
trailer_link = play_info["url"]
xbmc.executebuiltin(trailer_link)
2020-01-02 14:05:12 +11:00
elif signal == "set_view":
view_id = play_info["view_id"]
log.debug("Setting view id: {0}".format(view_id))
2020-01-02 14:05:12 +11:00
xbmc.executebuiltin("Container.SetViewMode(%s)" % int(view_id))
def screensaver_activated(self):
log.debug("Screen Saver Activated")
home_screen = HomeWindow()
2020-06-21 11:27:09 +10:00
home_screen.clear_property("skip_select_user")
settings = xbmcaddon.Addon()
stop_playback = settings.getSetting("stopPlaybackOnScreensaver") == 'true'
if stop_playback:
player = xbmc.Player()
if player.isPlayingVideo():
log.debug("Screen Saver Activated : isPlayingVideo() = true")
2021-02-16 18:54:46 -05:00
play_data = get_playing_data()
if play_data:
log.debug("Screen Saver Activated : this is an JellyCon item so stop it")
player.stop()
clear_old_cache_data()
cache_images = settings.getSetting('cacheImagesOnScreenSaver') == 'true'
if cache_images:
self.background_image_cache_thread = CacheArtwork()
self.background_image_cache_thread.start()
def screensaver_deactivated(self):
log.debug("Screen Saver Deactivated")
if self.background_image_cache_thread:
self.background_image_cache_thread.stop_activity()
self.background_image_cache_thread = None
settings = xbmcaddon.Addon()
show_change_user = settings.getSetting('changeUserOnScreenSaver') == 'true'
if show_change_user:
home_screen = HomeWindow()
2020-06-21 11:27:09 +10:00
skip_select_user = home_screen.get_property("skip_select_user")
if skip_select_user is not None and skip_select_user == "true":
return
xbmc.executebuiltin("RunScript(plugin.video.jellycon,0,?mode=CHANGE_USER)")