diff --git a/gui/config_integration.py b/gui/config_integration.py index 683fdb7..4ca0455 100644 --- a/gui/config_integration.py +++ b/gui/config_integration.py @@ -109,7 +109,7 @@ class ConfigIntegration: # Find configuration lines config_patterns = [ - r'^(FIRSTLINESPEAKERS|FACENAME101|NAMES|BRFLAG|FIXTEXTWRAP|IGNORETLTEXT)\s*=\s*(True|False)', + r'^(FIRSTLINESPEAKERS|INLINE401SPEAKERS|FACENAME101|NAMES|BRFLAG|FIXTEXTWRAP|IGNORETLTEXT)\s*=\s*(True|False)', r'^(CODE\d+)\s*=\s*(True|False)' ] @@ -152,6 +152,7 @@ class ConfigIntegration: return { # General config "FIRSTLINESPEAKERS": False, + "INLINE401SPEAKERS": False, "FACENAME101": False, "NAMES": False, "BRFLAG": False, @@ -185,6 +186,7 @@ class ConfigIntegration: """Get descriptions for all configuration options.""" return { "FIRSTLINESPEAKERS": "If 1st line of 401 is a speaker, set to True", + "INLINE401SPEAKERS": "Detect speaker from Name\u300cdialogue\u300d inline format in 401 lines", "FACENAME101": "Find Speakers in 101 Codes based on Face Name", "NAMES": "Output a list of all the character names found", "BRFLAG": "If the game uses
instead of newlines", diff --git a/gui/rpgmaker_tab.py b/gui/rpgmaker_tab.py index a3f5773..2203e2f 100644 --- a/gui/rpgmaker_tab.py +++ b/gui/rpgmaker_tab.py @@ -50,6 +50,7 @@ class RPGMakerTab(QWidget): DEFAULT_CONFIG = { # General settings "FIRSTLINESPEAKERS": False, + "INLINE401SPEAKERS": False, "FACENAME101": False, "BRFLAG": False, "FIXTEXTWRAP": True, @@ -256,6 +257,11 @@ class RPGMakerTab(QWidget): ) col1.addLayout(layout) + self.inline401speakers_cb, layout = self._create_checkbox_with_description( + "Inline 401 Speaker", "Detect Name「dialogue」 speaker format", "Extracts speaker from Name\u300cdialogue\u300d inline format in 401 lines." + ) + col1.addLayout(layout) + self.facename101_cb, layout = self._create_checkbox_with_description( "Face → Speaker", "Use face image as speaker name", "Uses face filenames to identify speakers." ) @@ -502,6 +508,7 @@ class RPGMakerTab(QWidget): try: # General settings checkboxes self.first_line_speakers_cb.stateChanged.disconnect() + self.inline401speakers_cb.stateChanged.disconnect() self.facename101_cb.stateChanged.disconnect() self.brflag_cb.stateChanged.disconnect() self.fixtextwrap_cb.stateChanged.disconnect() @@ -544,6 +551,7 @@ class RPGMakerTab(QWidget): """Connect all checkboxes to auto-apply changes when modified.""" # General settings checkboxes self.first_line_speakers_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) + self.inline401speakers_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) self.facename101_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) self.brflag_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) self.fixtextwrap_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) @@ -584,6 +592,7 @@ class RPGMakerTab(QWidget): config = { # General settings "FIRSTLINESPEAKERS": self.first_line_speakers_cb.isChecked(), + "INLINE401SPEAKERS": self.inline401speakers_cb.isChecked(), "FACENAME101": self.facename101_cb.isChecked(), "BRFLAG": self.brflag_cb.isChecked(), "FIXTEXTWRAP": self.fixtextwrap_cb.isChecked(), @@ -628,6 +637,7 @@ class RPGMakerTab(QWidget): """Set configuration from dictionary.""" # General settings self.first_line_speakers_cb.setChecked(config.get("FIRSTLINESPEAKERS", False)) + self.inline401speakers_cb.setChecked(config.get("INLINE401SPEAKERS", False)) self.facename101_cb.setChecked(config.get("FACENAME101", False)) self.brflag_cb.setChecked(config.get("BRFLAG", False)) self.fixtextwrap_cb.setChecked(config.get("FIXTEXTWRAP", True)) diff --git a/modules/rpgmakermvmz.py b/modules/rpgmakermvmz.py index cde8dba..21fc2e4 100644 --- a/modules/rpgmakermvmz.py +++ b/modules/rpgmakermvmz.py @@ -73,6 +73,8 @@ LEAVE = False # Config (Default) # FIRSTLINESPEAKERS: Guess speaker from first line. FIRSTLINESPEAKERS = False +# INLINE401SPEAKERS: Extract speaker from "Name「dialogue」" inline format on 401 lines. +INLINE401SPEAKERS = True # FACENAME101: Map face name -> speaker. FACENAME101 = False # Face name -> speaker mapping for FACENAME101. @@ -1774,6 +1776,12 @@ def searchCodes(page, pbar, jobList, filename): jaString, ) + # Inline Japanese quote speakers (e.g. "トルテ「dialogue」" or "エルミナ「first line...") + if len(speakerList) == 0 and INLINE401SPEAKERS: + inlineQuoteDetect = re.match(r"^([^\s「」。、!?…\\\n]{1,20})「", jaString) + if inlineQuoteDetect: + speakerList = [inlineQuoteDetect.group(1).strip()] + # First Line Speakers if len(speakerList) == 0 and FIRSTLINESPEAKERS is True: # Test Speaker @@ -1811,7 +1819,9 @@ def searchCodes(page, pbar, jobList, filename): if len(speakerList) != 0: # Check if speaker+dialogue are on same line (【speaker】dialogue) sameLineMatch = re.match(r"^\s*【([^】]+)】(.+)", jaString, re.DOTALL) - + # Check if speaker+dialogue are on same line via Japanese quote (Name「dialogue) + inlineQuoteMatch = re.match(r"^([^\s「」。、!?…\\\n]{1,20})「(.*)", jaString, re.DOTALL) if INLINE401SPEAKERS else None + if sameLineMatch and len(speakerList) == 1: # Translate speaker response = getSpeaker(speakerList[0]) @@ -1824,6 +1834,18 @@ def searchCodes(page, pbar, jobList, filename): if not setData: nametag = f"【{speaker}】" + nametag # Don't skip to next line - continue with current line + elif inlineQuoteMatch and len(speakerList) == 1: + # Inline quote format: strip "Name「" prefix entirely, keep raw dialogue + response = getSpeaker(speakerList[0]) + speaker = response[0] + totalTokens[0] += response[1][0] + totalTokens[1] += response[1][1] + # Remove speaker name and opening 「 from jaString; strip trailing 」 too + jaString = re.sub(r"」\s*$", "", inlineQuoteMatch.group(2)) + # Format as [Speaker]: for output + if not setData: + nametag = f"[{speaker}]: " + nametag + # Don't skip to next line - continue with current line elif codeList[i + 1]["code"] in [401, 405, -1]: # Original behavior: speaker on its own line, dialogue on next line # Single