jellyfin-mpv-shim/jellyfin_mpv_shim/bulk_subtitle.py
Izzie Walton d0a11486b6 black
2024-05-13 20:00:57 -04:00

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