mirror of
https://github.com/jellyfin/jellyfin-mpv-shim.git
synced 2024-11-23 05:59:43 +00:00
281 lines
8.7 KiB
Python
281 lines
8.7 KiB
Python
from collections import namedtuple
|
|
from .utils import get_sub_display_title
|
|
from .i18n import _
|
|
|
|
# TODO: Should probably support automatic profiles for languages other than English and Japanese...
|
|
|
|
import time
|
|
import logging
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from .player import PlayerManager as PlayerManager_type
|
|
|
|
Part = namedtuple("Part", ["id", "audio", "subtitle"])
|
|
Audio = namedtuple("Audio", ["id", "language_code", "name", "display_name"])
|
|
Subtitle = namedtuple(
|
|
"Subtitle", ["id", "language_code", "name", "is_forced", "display_name"]
|
|
)
|
|
|
|
log = logging.getLogger("bulk_subtitle")
|
|
messages = []
|
|
keep_messages = 6
|
|
|
|
|
|
def render_message(message, show_text):
|
|
log.info(message)
|
|
messages.append(message)
|
|
text = _("Selecting Tracks...")
|
|
for message in messages[-6:]:
|
|
text += "\n " + message
|
|
show_text(text, 2**30, 1)
|
|
|
|
|
|
def process_series(mode, player: "PlayerManager_type", m_raid=None, m_rsid=None):
|
|
messages.clear()
|
|
media = player.get_video()
|
|
client = media.client
|
|
show_text = player.show_text
|
|
c_aid, c_sid = None, None
|
|
c_pid = media.media_source.get("Id")
|
|
|
|
success_ct = 0
|
|
partial_ct = 0
|
|
count = 0
|
|
|
|
videos = client.jellyfin.get_season(media.item["SeriesId"], media.item["SeasonId"])[
|
|
"Items"
|
|
]
|
|
for video in videos:
|
|
name = "s{0}e{1:02}".format(
|
|
video.get("ParentIndexNumber"), video.get("IndexNumber")
|
|
)
|
|
video = client.jellyfin.get_item(video.get("Id"))
|
|
for media_source in video["MediaSources"]:
|
|
count += 1
|
|
audio_list = [
|
|
Audio(
|
|
s.get("Index"),
|
|
s.get("Language"),
|
|
s.get("Title"),
|
|
s.get("DisplayTitle"),
|
|
)
|
|
for s in media_source["MediaStreams"]
|
|
if s.get("Type") == "Audio"
|
|
]
|
|
subtitle_list = [
|
|
Subtitle(
|
|
s.get("Index"),
|
|
s.get("Language"),
|
|
s.get("Title"),
|
|
s.get("IsForced"),
|
|
get_sub_display_title(s),
|
|
)
|
|
for s in media_source["MediaStreams"]
|
|
if s.get("Type") == "Subtitle"
|
|
]
|
|
part = Part(media_source.get("Id"), audio_list, subtitle_list)
|
|
|
|
aid = None
|
|
sid = -1
|
|
if mode == "subbed":
|
|
audio, subtitle = get_subbed(part)
|
|
if audio and subtitle:
|
|
render_message(
|
|
"{0}: {1} ({2})".format(
|
|
name, subtitle.display_name, subtitle.name
|
|
),
|
|
show_text,
|
|
)
|
|
aid, sid = audio.id, subtitle.id
|
|
success_ct += 1
|
|
elif mode == "dubbed":
|
|
audio, subtitle = get_dubbed(part)
|
|
if audio and subtitle:
|
|
render_message(
|
|
"{0}: {1} ({2})".format(
|
|
name, subtitle.display_name, subtitle.name
|
|
),
|
|
show_text,
|
|
)
|
|
aid, sid = audio.id, subtitle.id
|
|
success_ct += 1
|
|
elif audio:
|
|
render_message(_("{0}: No Subtitles").format(name), show_text)
|
|
aid = audio.id
|
|
partial_ct += 1
|
|
elif mode == "manual":
|
|
if m_raid < len(part.audio) and m_rsid < len(part.subtitle):
|
|
audio = part.audio[m_raid]
|
|
aid = audio.id
|
|
render_message(
|
|
"{0} a: {1} ({2})".format(name, audio.display_name, audio.name),
|
|
show_text,
|
|
)
|
|
if m_rsid != -1:
|
|
subtitle = part.subtitle[m_rsid]
|
|
sid = subtitle.id
|
|
render_message(
|
|
"{0} s: {1} ({2})".format(
|
|
name, subtitle.display_name, subtitle.name
|
|
),
|
|
show_text,
|
|
)
|
|
success_ct += 1
|
|
|
|
if aid:
|
|
if c_pid == part.id:
|
|
c_aid, c_sid = aid, sid
|
|
|
|
# This is a horrible hack to change the audio/subtitle settings without playing
|
|
# the media. I checked the jelyfin source code and didn't find a better way.
|
|
client.jellyfin.session_progress(
|
|
{
|
|
"ItemId": part.id,
|
|
"AudioStreamIndex": aid,
|
|
"SubtitleStreamIndex": sid,
|
|
}
|
|
)
|
|
|
|
else:
|
|
render_message(_("{0}: Fail").format(name), show_text)
|
|
|
|
if mode == "subbed":
|
|
render_message(
|
|
_("Set Subbed: {0} ok, {1} fail").format(success_ct, count - success_ct),
|
|
show_text,
|
|
)
|
|
elif mode == "dubbed":
|
|
render_message(
|
|
_("Set Dubbed: {0} ok, {1} audio only, {2} fail").format(
|
|
success_ct, partial_ct, count - success_ct - partial_ct
|
|
),
|
|
show_text,
|
|
)
|
|
elif mode == "manual":
|
|
render_message(
|
|
_("Manual: {0} ok, {1} fail").format(success_ct, count - success_ct),
|
|
show_text,
|
|
)
|
|
time.sleep(3)
|
|
if c_aid:
|
|
render_message(_("Setting Current..."), show_text)
|
|
player.put_task(player.set_streams, c_aid, c_sid)
|
|
player.timeline_handle()
|
|
|
|
|
|
def get_subbed(part):
|
|
japanese_audio = None
|
|
english_subtitles = None
|
|
subtitle_weight = None
|
|
|
|
for audio in part.audio:
|
|
lower_title = audio.name.lower() if audio.name is not None else ""
|
|
if audio.language_code != "jpn" and "japan" not in lower_title:
|
|
continue
|
|
if "commentary" in lower_title:
|
|
continue
|
|
|
|
if japanese_audio is None:
|
|
japanese_audio = audio
|
|
break
|
|
|
|
for subtitle in part.subtitle:
|
|
lower_title = subtitle.name.lower() if subtitle.name is not None else ""
|
|
if subtitle.language_code != "eng" and "english" not in lower_title:
|
|
continue
|
|
if subtitle.is_forced:
|
|
continue
|
|
|
|
weight = dialogue_weight(lower_title)
|
|
if subtitle_weight is None or weight < subtitle_weight:
|
|
subtitle_weight = weight
|
|
english_subtitles = subtitle
|
|
|
|
if japanese_audio and english_subtitles:
|
|
return japanese_audio, english_subtitles
|
|
return None, None
|
|
|
|
|
|
def get_dubbed(part):
|
|
english_audio = None
|
|
sign_subtitles = None
|
|
subtitle_weight = None
|
|
|
|
for audio in part.audio:
|
|
lower_title = audio.name.lower() if audio.name is not None else ""
|
|
if audio.language_code != "eng" and "english" not in lower_title:
|
|
continue
|
|
if "commentary" in lower_title:
|
|
continue
|
|
|
|
if english_audio is None:
|
|
english_audio = audio
|
|
break
|
|
|
|
for subtitle in part.subtitle:
|
|
lower_title = subtitle.name.lower() if subtitle.name is not None else ""
|
|
if subtitle.language_code != "eng" and "english" not in lower_title:
|
|
continue
|
|
if subtitle.is_forced:
|
|
sign_subtitles = subtitle
|
|
break
|
|
|
|
weight = sign_weight(lower_title)
|
|
if weight == 0:
|
|
continue
|
|
|
|
if subtitle_weight is None or weight < subtitle_weight:
|
|
subtitle_weight = weight
|
|
sign_subtitles = subtitle
|
|
|
|
if english_audio:
|
|
return english_audio, sign_subtitles
|
|
return None, None
|
|
|
|
|
|
def dialogue_weight(text: str):
|
|
if not text:
|
|
return 900
|
|
lower_text = text.lower()
|
|
has_dialogue = (
|
|
"main" in lower_text or "full" in lower_text or "dialogue" in lower_text
|
|
)
|
|
has_songs = "op/ed" in lower_text or "song" in lower_text or "lyric" in lower_text
|
|
has_signs = "sign" in lower_text
|
|
vendor = "bd" in lower_text or "retail" in lower_text
|
|
weight = 900
|
|
|
|
if has_dialogue and has_songs:
|
|
weight -= 100
|
|
if has_songs:
|
|
weight += 200
|
|
if has_dialogue and has_signs:
|
|
weight -= 100
|
|
elif has_signs:
|
|
weight += 700
|
|
if vendor:
|
|
weight += 50
|
|
return weight
|
|
|
|
|
|
def sign_weight(text: str):
|
|
if not text:
|
|
return 0
|
|
lower_text = text.lower()
|
|
has_songs = "op/ed" in lower_text or "song" in lower_text or "lyric" in lower_text
|
|
has_signs = "sign" in lower_text
|
|
vendor = "bd" in lower_text or "retail" in lower_text
|
|
weight = 900
|
|
|
|
if not (has_songs or has_signs):
|
|
return 0
|
|
if has_songs:
|
|
weight -= 200
|
|
if has_signs:
|
|
weight -= 300
|
|
if vendor:
|
|
weight += 50
|
|
return weight
|