Add language-based track selection and context menu
Context Menu Integration: - Add 'Play with track selection' option to video context menus - Available for movies and episodes via long-press/context menu - Forces manual track selection dialog even when auto-selection is configured Language Selection Improvements: - Add dropdown selectors for preferred audio/subtitle languages - Support German, English, French, Spanish, Italian, Japanese, Russian - Add comprehensive language matching (e.g., 'ger' matches 'deu', 'deutsch', 'german') - Add setting to auto-select 'No subtitles' when no matching subtitle found Track Selection Scoring System: - Audio tracks: Language match (+100), default flag (+50), channel count (+5-40), codec quality (+10-30), exclude commentary (-100) - Subtitle tracks: Language match (+100), forced flag (+50), SRT preference (+30), default flag (+10) Implementation Details: - Add force_track_selection parameter throughout playback chain - Preserve server/remote control track selections when not forcing manual selection - Add extensive debug logging for track selection decisions - Respect user preferences while allowing manual override via context menu
This commit is contained in:
@@ -1281,3 +1281,47 @@ msgstr "Forced-Untertitel bevorzugen"
|
||||
msgctxt "#30690"
|
||||
msgid "SRT vor PGS/Bild-Untertiteln bevorzugen"
|
||||
msgstr "SRT vor PGS/Bild-Untertiteln bevorzugen"
|
||||
|
||||
msgctxt "#30691"
|
||||
msgid "Deutsch"
|
||||
msgstr "Deutsch"
|
||||
|
||||
msgctxt "#30692"
|
||||
msgid "Englisch"
|
||||
msgstr "Englisch"
|
||||
|
||||
msgctxt "#30693"
|
||||
msgid "Französisch"
|
||||
msgstr "Französisch"
|
||||
|
||||
msgctxt "#30694"
|
||||
msgid "Spanisch"
|
||||
msgstr "Spanisch"
|
||||
|
||||
msgctxt "#30695"
|
||||
msgid "Italienisch"
|
||||
msgstr "Italienisch"
|
||||
|
||||
msgctxt "#30696"
|
||||
msgid "Japanisch"
|
||||
msgstr "Japanisch"
|
||||
|
||||
msgctxt "#30697"
|
||||
msgid "Russisch"
|
||||
msgstr "Russisch"
|
||||
|
||||
msgctxt "#30698"
|
||||
msgid "Andere"
|
||||
msgstr "Andere"
|
||||
|
||||
msgctxt "#30699"
|
||||
msgid "Keine (nur Audio-Sprache)"
|
||||
msgstr "Keine (nur Audio-Sprache)"
|
||||
|
||||
msgctxt "#30700"
|
||||
msgid "Bei keinem Treffer automatisch 'Keine Untertitel' wählen"
|
||||
msgstr "Bei keinem Treffer automatisch 'Keine Untertitel' wählen"
|
||||
|
||||
msgctxt "#30701"
|
||||
msgid "Abspielen mit Track-Auswahl"
|
||||
msgstr "Abspielen mit Track-Auswahl"
|
||||
|
||||
@@ -1272,3 +1272,47 @@ msgstr "Prefer forced subtitles"
|
||||
msgctxt "#30690"
|
||||
msgid "Prefer SRT over PGS/image subtitles"
|
||||
msgstr "Prefer SRT over PGS/image subtitles"
|
||||
|
||||
msgctxt "#30691"
|
||||
msgid "German"
|
||||
msgstr "German"
|
||||
|
||||
msgctxt "#30692"
|
||||
msgid "English"
|
||||
msgstr "English"
|
||||
|
||||
msgctxt "#30693"
|
||||
msgid "French"
|
||||
msgstr "French"
|
||||
|
||||
msgctxt "#30694"
|
||||
msgid "Spanish"
|
||||
msgstr "Spanish"
|
||||
|
||||
msgctxt "#30695"
|
||||
msgid "Italian"
|
||||
msgstr "Italian"
|
||||
|
||||
msgctxt "#30696"
|
||||
msgid "Japanese"
|
||||
msgstr "Japanese"
|
||||
|
||||
msgctxt "#30697"
|
||||
msgid "Russian"
|
||||
msgstr "Russian"
|
||||
|
||||
msgctxt "#30698"
|
||||
msgid "Other"
|
||||
msgstr "Other"
|
||||
|
||||
msgctxt "#30699"
|
||||
msgid "None (audio language only)"
|
||||
msgstr "None (audio language only)"
|
||||
|
||||
msgctxt "#30700"
|
||||
msgid "Auto-select 'No subtitles' if no match found"
|
||||
msgstr "Auto-select 'No subtitles' if no match found"
|
||||
|
||||
msgctxt "#30701"
|
||||
msgid "Play with track selection"
|
||||
msgstr "Play with track selection"
|
||||
|
||||
@@ -923,6 +923,9 @@ def play_action(params):
|
||||
audio_stream_index = params.get("audio_stream_index")
|
||||
log.debug("audio_stream_index: {0}".format(audio_stream_index))
|
||||
|
||||
force_track_selection = params.get("force_track_selection", "false") == "true"
|
||||
log.debug("force_track_selection: {0}".format(force_track_selection))
|
||||
|
||||
action = params.get("action", "play")
|
||||
|
||||
# set the current playing item id
|
||||
@@ -939,6 +942,7 @@ def play_action(params):
|
||||
play_info["media_source_id"] = media_source_id
|
||||
play_info["subtitle_stream_index"] = subtitle_stream_index
|
||||
play_info["audio_stream_index"] = audio_stream_index
|
||||
play_info["force_track_selection"] = force_track_selection
|
||||
log.info("Sending jellycon_play_action : {0}".format(play_info))
|
||||
play_file(play_info)
|
||||
|
||||
|
||||
@@ -595,6 +595,20 @@ def add_gui_item(url, item_details, display_options, folder=True, default_sort=F
|
||||
|
||||
list_item.setProperties(item_properties)
|
||||
|
||||
# Add context menu for playable video items
|
||||
if not folder and is_video and item_details.item_type.lower() in ['movie', 'episode']:
|
||||
from . import translation
|
||||
context_menu = []
|
||||
|
||||
# Add "Play with track selection" option
|
||||
play_with_selection_url = (
|
||||
'RunPlugin(plugin://plugin.video.jellycon/'
|
||||
'?mode=PLAY&item_id={}&force_track_selection=true)'.format(item_details.id)
|
||||
)
|
||||
context_menu.append((translation.translate_string(30701), play_with_selection_url))
|
||||
|
||||
list_item.addContextMenuItems(context_menu)
|
||||
|
||||
return u, list_item, folder
|
||||
|
||||
|
||||
|
||||
@@ -242,8 +242,10 @@ def play_file(play_info):
|
||||
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)
|
||||
force_track_selection = play_info.get("force_track_selection", False)
|
||||
|
||||
log.debug("playFile id({0}) resume({1}) force_transcode({2})".format(item_id, auto_resume, force_transcode))
|
||||
log.debug("playFile id({0}) resume({1}) force_transcode({2}) force_track_selection({3})".format(
|
||||
item_id, auto_resume, force_transcode, force_track_selection))
|
||||
|
||||
addon_path = settings.getAddonInfo('path')
|
||||
force_auto_resume = settings.getSetting('forceAutoResume') == 'true'
|
||||
@@ -461,7 +463,7 @@ def play_file(play_info):
|
||||
|
||||
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)
|
||||
subtitle_stream_index, force_track_selection)
|
||||
log.debug("New playurl for transcoding: {0}".format(playurl))
|
||||
|
||||
elif playback_type == "1": # for direct stream add any streamable subtitles
|
||||
@@ -778,7 +780,7 @@ def set_list_item_props(item_id, list_item, result, server, extra_props, title):
|
||||
# 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
|
||||
def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, subtitle_stream_index):
|
||||
def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, subtitle_stream_index, force_track_selection=False):
|
||||
dialog = xbmcgui.Dialog()
|
||||
audio_streams_list = {}
|
||||
audio_streams = []
|
||||
@@ -795,11 +797,51 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
|
||||
source_id = media_source["Id"]
|
||||
|
||||
# Read user preferences
|
||||
preferred_audio_lang = settings.getSetting("preferred_audio_language").lower()
|
||||
# Map select index to language code
|
||||
lang_index_map = ['ger', 'eng', 'fra', 'spa', 'ita', 'jpn', 'rus', ''] # '' = other/custom
|
||||
sub_lang_index_map = ['', 'ger', 'eng', 'fra', 'spa', 'ita', 'jpn', 'rus', ''] # First is 'none'
|
||||
|
||||
audio_lang_index = int(settings.getSetting("preferred_audio_language") or "0")
|
||||
preferred_audio_lang = lang_index_map[audio_lang_index] if audio_lang_index < len(lang_index_map) else ''
|
||||
|
||||
auto_select_default_audio = settings.getSetting("auto_select_default_audio") == "true"
|
||||
preferred_sub_lang = settings.getSetting("preferred_subtitle_language").lower()
|
||||
|
||||
sub_lang_index = int(settings.getSetting("preferred_subtitle_language") or "1")
|
||||
preferred_sub_lang = sub_lang_index_map[sub_lang_index] if sub_lang_index < len(sub_lang_index_map) else ''
|
||||
|
||||
prefer_forced = settings.getSetting("prefer_forced_subtitles") == "true"
|
||||
prefer_srt = settings.getSetting("prefer_srt_over_pgs") == "true"
|
||||
auto_no_subs = settings.getSetting("auto_no_subtitles_if_no_match") == "true"
|
||||
|
||||
# Language code mapping for better matching
|
||||
language_map = {
|
||||
'ger': ['ger', 'deu', 'de', 'german', 'deutsch'],
|
||||
'eng': ['eng', 'en', 'english'],
|
||||
'fra': ['fra', 'fr', 'fre', 'french', 'français'],
|
||||
'spa': ['spa', 'es', 'spanish', 'español'],
|
||||
'ita': ['ita', 'it', 'italian', 'italiano'],
|
||||
'jpn': ['jpn', 'ja', 'japanese', '日本語'],
|
||||
'rus': ['rus', 'ru', 'russian'],
|
||||
}
|
||||
|
||||
# Helper function to check if language matches
|
||||
def language_matches(stream_lang, preferred_lang):
|
||||
stream_lang = stream_lang.lower()
|
||||
preferred_lang = preferred_lang.lower()
|
||||
|
||||
# Direct match
|
||||
if preferred_lang in stream_lang or stream_lang in preferred_lang:
|
||||
return True
|
||||
|
||||
# Check against language map
|
||||
for key, variations in language_map.items():
|
||||
if preferred_lang in variations:
|
||||
# Check if stream language matches any variation
|
||||
for variant in variations:
|
||||
if variant in stream_lang or stream_lang.startswith(variant[:2]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
media_streams = media_source['MediaStreams']
|
||||
|
||||
@@ -856,7 +898,8 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
|
||||
subtitle_streams_data.append(stream)
|
||||
|
||||
# Auto-select audio track based on preferences
|
||||
if select_audio_index is None and len(audio_streams_data) > 0:
|
||||
# Only auto-select if not already set by server/remote control AND not forcing manual selection
|
||||
if not force_track_selection and select_audio_index is None and len(audio_streams_data) > 0 and (preferred_audio_lang or auto_select_default_audio):
|
||||
auto_selected = None
|
||||
best_score = -1
|
||||
|
||||
@@ -864,10 +907,10 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
|
||||
if preferred_audio_lang:
|
||||
for stream in audio_streams_data:
|
||||
score = 0
|
||||
stream_lang = stream.get('Language', '').lower()
|
||||
stream_lang = stream.get('Language', '')
|
||||
|
||||
# Match against common variations (ger, deu, de, german)
|
||||
if preferred_audio_lang in stream_lang or stream_lang.startswith(preferred_audio_lang[:3]):
|
||||
# Match against common variations using language map
|
||||
if language_matches(stream_lang, preferred_audio_lang):
|
||||
score += 100 # Language match
|
||||
|
||||
# Bonus for default track
|
||||
@@ -911,19 +954,29 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
|
||||
log.debug("Auto-selected audio (index {0}, score {1})".format(auto_selected, best_score))
|
||||
|
||||
# Auto-select subtitle track based on preferences
|
||||
if select_subs_index is None and len(subtitle_streams_data) > 0:
|
||||
# Only auto-select if not already set by server/remote control AND not forcing manual selection
|
||||
if force_track_selection:
|
||||
log.debug("Forcing manual track selection (from context menu)")
|
||||
elif select_subs_index is not None:
|
||||
log.debug("Using subtitle index from server/remote: {0}".format(select_subs_index))
|
||||
elif len(subtitle_streams_data) == 0:
|
||||
log.debug("No subtitle streams available")
|
||||
elif len(subtitle_streams_data) > 0 and preferred_sub_lang: # Only if user configured a language preference
|
||||
auto_selected = None
|
||||
best_score = -1
|
||||
|
||||
log.debug("Auto-selecting subtitle: preferred_lang={0}, prefer_forced={1}, prefer_srt={2}".format(
|
||||
preferred_sub_lang, prefer_forced, prefer_srt))
|
||||
|
||||
for stream in subtitle_streams_data:
|
||||
score = 0
|
||||
stream_lang = stream.get('Language', '').lower()
|
||||
stream_lang = stream.get('Language', '')
|
||||
codec = stream.get('Codec', '').lower()
|
||||
is_forced = stream.get('IsForced', False)
|
||||
is_default = stream.get('IsDefault', False)
|
||||
|
||||
# Score based on language match
|
||||
if preferred_sub_lang and (preferred_sub_lang in stream_lang or stream_lang.startswith(preferred_sub_lang[:3])):
|
||||
if preferred_sub_lang and language_matches(stream_lang, preferred_sub_lang):
|
||||
score += 100
|
||||
|
||||
# Bonus for forced if preferred
|
||||
@@ -950,6 +1003,9 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
|
||||
if auto_selected is not None and best_score >= 100: # Only auto-select if language matched
|
||||
select_subs_index = auto_selected
|
||||
log.debug("Auto-selected subtitle (index {0}, score {1})".format(auto_selected, best_score))
|
||||
elif auto_no_subs and preferred_sub_lang: # No match found but user wants auto "no subs"
|
||||
select_subs_index = -1 # Special value to indicate "no subtitles"
|
||||
log.debug("No matching subtitle found - auto-selected 'No subtitles'")
|
||||
|
||||
# set audio index
|
||||
if select_audio_index is not None:
|
||||
@@ -967,8 +1023,12 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
|
||||
|
||||
# set subtitle index
|
||||
if select_subs_index is not None:
|
||||
# Handle special "no subtitles" value
|
||||
if select_subs_index == -1:
|
||||
# User wants no subtitles - do nothing
|
||||
pass
|
||||
# Load subtitles in the listitem if downloadable
|
||||
if select_subs_index in downloadable_streams:
|
||||
elif select_subs_index in downloadable_streams:
|
||||
subtitle_url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
|
||||
subtitle_url = subtitle_url % (settings.getSetting('server_address'), item_id, source_id, select_subs_index)
|
||||
log.debug("Streaming subtitles url: {0} {1}".format(select_subs_index, subtitle_url))
|
||||
|
||||
@@ -50,11 +50,12 @@
|
||||
|
||||
<setting label="30685" type="lsep"/>
|
||||
<setting type="sep" />
|
||||
<setting id="preferred_audio_language" type="text" label="30686" default="ger" visible="true"/>
|
||||
<setting id="preferred_audio_language" type="select" label="30686" lvalues="30691|30692|30693|30694|30695|30696|30697|30698" default="0" visible="true"/>
|
||||
<setting id="auto_select_default_audio" type="bool" label="30687" default="true" visible="true"/>
|
||||
<setting id="preferred_subtitle_language" type="text" label="30688" default="ger" visible="true"/>
|
||||
<setting id="preferred_subtitle_language" type="select" label="30688" lvalues="30699|30691|30692|30693|30694|30695|30696|30697|30698" default="1" visible="true"/>
|
||||
<setting id="prefer_forced_subtitles" type="bool" label="30689" default="true" visible="true"/>
|
||||
<setting id="prefer_srt_over_pgs" type="bool" label="30690" default="true" visible="true"/>
|
||||
<setting id="auto_no_subtitles_if_no_match" type="bool" label="30700" default="true" visible="true"/>
|
||||
<setting id="max_play_queue" type="slider" label="30447" default="200" range="20, 10, 1000" option="int" visible="true"/>
|
||||
|
||||
</category>
|
||||
|
||||
Reference in New Issue
Block a user