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:
mani
2026-01-06 00:32:44 +01:00
parent 238d30ab94
commit b9a6b5056a
6 changed files with 182 additions and 15 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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>