Fix Speaker bug and add tests
This commit is contained in:
parent
4dd9d2bbd4
commit
3dea0b8972
14 changed files with 139 additions and 77 deletions
|
|
@ -14,6 +14,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# Globals
|
||||
|
|
@ -330,8 +331,7 @@ def translateCSV(data, pbar, writeFile, writer, filename, translatedList):
|
|||
translatedList.pop(0)
|
||||
|
||||
# Remove speaker prefix from translation if present
|
||||
if speaker:
|
||||
translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Add wordwrap
|
||||
translatedText = dazedwrap.wrapText(translatedText, WIDTH)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# Globals
|
||||
|
|
@ -912,10 +913,9 @@ def translateDialogueSrcTl(data, filename):
|
|||
|
||||
# Strip speaker prefix if the AI echoed it back
|
||||
# Handles: [Speaker]: text | Speaker: text | Speaker(text) / CJK(text)
|
||||
match = re.search(r'^\[.+?\]\s?[|:]\s?', translatedText)
|
||||
if match:
|
||||
translatedText = translatedText[match.end():]
|
||||
else:
|
||||
before_strip = translatedText
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
if translatedText == before_strip:
|
||||
# Fallback: strip any leading Japanese/CJK name followed by ( or :
|
||||
cjk_m = re.match(r'^[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]+\s*[\((:]\s*', translatedText)
|
||||
if cjk_m:
|
||||
|
|
@ -1149,10 +1149,9 @@ def translateJSON(data, filename, translatedList):
|
|||
stringList = None
|
||||
|
||||
# Remove speaker prefix — handles [Name]: / Name: / CJK(text)
|
||||
match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translatedText)
|
||||
if match:
|
||||
translatedText = translatedText.replace(match.group(1), "")
|
||||
else:
|
||||
before_strip = translatedText
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
if translatedText == before_strip:
|
||||
cjk_m = re.match(r'^[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]+\s*[\((:]\s*', translatedText)
|
||||
if cjk_m:
|
||||
translatedText = translatedText[cjk_m.end():]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# Globals
|
||||
|
|
@ -324,7 +325,7 @@ def translateKiriKiri(data, pbar, filename, jobList):
|
|||
translatedText = stringList[0]
|
||||
stringList.pop(0)
|
||||
# Remove Speaker label if present
|
||||
translatedText = re.sub(r"\[.*?\]:\s", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
# Wrap and convert newlines to [r]
|
||||
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||
translatedText = translatedText.replace("\n", "[r]")
|
||||
|
|
@ -362,7 +363,7 @@ def translateKiriKiri(data, pbar, filename, jobList):
|
|||
if len(stringList) > 0:
|
||||
translatedText = stringList[0]
|
||||
stringList.pop(0)
|
||||
translatedText = re.sub(r"\[.*?\]:\s", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||
translatedText = translatedText.replace("\n", "[r]")
|
||||
indent_match = re.match(r"^([ \t\u3000]+)", data[i])
|
||||
|
|
@ -402,7 +403,7 @@ def translateKiriKiri(data, pbar, filename, jobList):
|
|||
stringList.pop(0)
|
||||
|
||||
# Remove Speaker
|
||||
translatedText = re.sub(r"\[.*?\]:\s", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# Globals
|
||||
|
|
@ -291,7 +292,7 @@ def translateRegex(data, filename, translatedList):
|
|||
stringList = None
|
||||
|
||||
# Remove speaker prefix from translation
|
||||
translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||
|
|
@ -383,7 +384,7 @@ def translateRegex(data, filename, translatedList):
|
|||
stringList = None
|
||||
|
||||
# Remove speaker
|
||||
translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Escape Quotes
|
||||
translatedText = re.sub(r'(?<!\\)"', r"", translatedText)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# Globals
|
||||
|
|
@ -251,9 +252,7 @@ def translateRenpy(data, filename, translatedList):
|
|||
translatedList = None
|
||||
|
||||
# Remove speaker
|
||||
if speaker != "":
|
||||
matchSpeakerList = re.findall(r"^\[?(.+?)\]?\s?[|:]\s?", translatedText)
|
||||
translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Escape Quotes
|
||||
translatedText = re.sub(r'[\\]*(")', '\\"', translatedText)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost, get_var_translation, set_var_translations_batch
|
||||
from util.speaker_prefix import SPEAKER_BRACKET_INNER, strip_speaker_prefix
|
||||
|
||||
# Globals
|
||||
MODEL = os.getenv("model")
|
||||
|
|
@ -2288,7 +2289,7 @@ def searchCodes(page, pbar, jobList, filename):
|
|||
|
||||
# [Speaker] standalone line format (written back by inline re-export)
|
||||
if len(speakerList) == 0:
|
||||
inlineFmtMatch = re.match(r"^\[([^\[\]\n]+)\]\s*$", speakerWork)
|
||||
inlineFmtMatch = re.match(rf"^\[({SPEAKER_BRACKET_INNER})\]\s*$", speakerWork, re.IGNORECASE)
|
||||
if inlineFmtMatch:
|
||||
speakerList = [inlineFmtMatch.group(1).strip()]
|
||||
|
||||
|
|
@ -2559,9 +2560,7 @@ def searchCodes(page, pbar, jobList, filename):
|
|||
translatedText = list401[0]
|
||||
|
||||
# Remove speaker prefix if present
|
||||
match = re.search(r'(^\[(.+?)\]\s?[|:]\s?)', translatedText)
|
||||
if match:
|
||||
translatedText = translatedText.replace(match.group(1), "")
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Remove 。 that appears after ... in AI output
|
||||
translatedText = re.sub(r'\.\.\.(。)+', '...', translatedText)
|
||||
|
|
@ -2884,9 +2883,7 @@ def searchCodes(page, pbar, jobList, filename):
|
|||
list357.pop(0)
|
||||
|
||||
# Remove speaker prefix if present (same pattern used for 401)
|
||||
m = re.search(r'(^\[.+?\]\s?[|:]\s?)', translatedText)
|
||||
if m:
|
||||
translatedText = translatedText.replace(m.group(1), "")
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
if FIXTEXTWRAP:
|
||||
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||
|
|
@ -3282,7 +3279,7 @@ def searchCodes(page, pbar, jobList, filename):
|
|||
translatedText = list355655[0]
|
||||
list355655.pop(0)
|
||||
# Strip speaker prefix if present
|
||||
translatedText = re.sub(r'^\[.*?\]\s*[|:]\s*', '', translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
# Replace double quotes to avoid breaking the JSON/JS syntax
|
||||
translatedText = translatedText.replace('\\"', "'")
|
||||
translatedText = translatedText.replace('"', "'")
|
||||
|
|
@ -3542,7 +3539,7 @@ def searchCodes(page, pbar, jobList, filename):
|
|||
translatedText = list355655[0]
|
||||
list355655.pop(0)
|
||||
# Strip speaker prefix if present
|
||||
translatedText = re.sub(r'^\[.*?\]\s*[|:]\s*', '', translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
# Replace double quotes to avoid breaking JS syntax
|
||||
translatedText = translatedText.replace('\\"', "'")
|
||||
translatedText = translatedText.replace('"', "'")
|
||||
|
|
@ -4949,15 +4946,15 @@ def translateAI(text, history, history_ctx=None):
|
|||
return name
|
||||
return m.group(0)
|
||||
|
||||
speaker_prefix = re.match(
|
||||
r"^(?P<open>\s*\[)(?P<speaker>(?:\\n\[\d+\]|[^\]\n])+)(?P<close>\]\s*[|:]\s*)",
|
||||
tag_match = re.match(
|
||||
rf"^(?P<open>\s*\[)(?P<speaker>{SPEAKER_BRACKET_INNER})(?P<close>\]\s*[|::]\s*)",
|
||||
s,
|
||||
re.IGNORECASE,
|
||||
)
|
||||
if speaker_prefix:
|
||||
speaker = _VAR_ACTOR_RE.sub(_display_actor_name, speaker_prefix.group("speaker"))
|
||||
body = _VAR_ACTOR_RE.sub(_repl, s[speaker_prefix.end():])
|
||||
return f"{speaker_prefix.group('open')}{speaker}{speaker_prefix.group('close')}{body}"
|
||||
if tag_match:
|
||||
speaker = _VAR_ACTOR_RE.sub(_display_actor_name, tag_match.group("speaker"))
|
||||
body = _VAR_ACTOR_RE.sub(_repl, s[tag_match.end():])
|
||||
return f"{tag_match.group('open')}{speaker}{tag_match.group('close')}{body}"
|
||||
|
||||
return _VAR_ACTOR_RE.sub(_repl, s)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
|
||||
# Globals
|
||||
MODEL = os.getenv("model")
|
||||
|
|
@ -501,10 +502,7 @@ def translateBookmark(data, filename, translatedDataList=None, pbar=None):
|
|||
else:
|
||||
if dataList:
|
||||
translated = dataList[0]
|
||||
if speaker:
|
||||
match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translated)
|
||||
if match:
|
||||
translated = translated.replace(match.group(1), "")
|
||||
translated = strip_speaker_prefix(translated)
|
||||
translated = dazedwrap.wrapText(translated, width=WIDTH)
|
||||
command["data"][i] = translated
|
||||
dataList.pop(0)
|
||||
|
|
@ -1389,10 +1387,7 @@ def translateRecollection(data, filename, translatedDataList=None, pbar=None):
|
|||
translated = dataList[0]
|
||||
|
||||
# Remove speaker
|
||||
if speaker:
|
||||
match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translated)
|
||||
if match:
|
||||
translated = translated.replace(match.group(1), "")
|
||||
translated = strip_speaker_prefix(translated)
|
||||
|
||||
# Textwrap
|
||||
translated = dazedwrap.wrapText(translated, width=WIDTH)
|
||||
|
|
@ -1843,10 +1838,7 @@ def translateMap(data, filename, translatedDataList=None, pbar=None):
|
|||
else:
|
||||
if dataList:
|
||||
translated = dataList[0]
|
||||
if speaker:
|
||||
match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translated)
|
||||
if match:
|
||||
translated = translated.replace(match.group(1), "")
|
||||
translated = strip_speaker_prefix(translated)
|
||||
translated = dazedwrap.wrapText(translated, width=WIDTH)
|
||||
command["data"][i] = translated
|
||||
dataList.pop(0)
|
||||
|
|
@ -1919,10 +1911,7 @@ def translateMap(data, filename, translatedDataList=None, pbar=None):
|
|||
else:
|
||||
if dataList:
|
||||
translated = dataList[0]
|
||||
if speaker:
|
||||
match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translated)
|
||||
if match:
|
||||
translated = translated.replace(match.group(1), "")
|
||||
translated = strip_speaker_prefix(translated)
|
||||
translated = dazedwrap.wrapText(translated, width=WIDTH)
|
||||
command["data"][i] = translated
|
||||
dataList.pop(0)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import SPEAKER_TAG_RE, extract_dialogue_after_speaker, strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# Globals
|
||||
|
|
@ -208,27 +209,20 @@ def translateTxt(data, filename, translatedList):
|
|||
global LOCK, ESTIMATE, FILENAME, PBAR, MISMATCH
|
||||
i = 0
|
||||
|
||||
# Regex
|
||||
lineTextRegex = r"(?:^\[.+?\]:)?(.+)"
|
||||
speakerTextRegex = r"^\[(.+?)\]"
|
||||
|
||||
while i < len(data):
|
||||
# Speaker
|
||||
match = re.search(speakerTextRegex, data[i])
|
||||
match = SPEAKER_TAG_RE.match(data[i])
|
||||
if match:
|
||||
# Get Speaker
|
||||
speakerData = getSpeaker(match.group(1))
|
||||
speaker = speakerData[0]
|
||||
tokens[0] += speakerData[1][0]
|
||||
tokens[1] += speakerData[1][1]
|
||||
data[i] = data[i].replace(match.group(1), speaker)
|
||||
data[i] = data[i].replace(match.group(1), speaker, 1)
|
||||
|
||||
# Dialogue
|
||||
match = re.search(lineTextRegex, data[i])
|
||||
jaString = None
|
||||
if match:
|
||||
# Set String
|
||||
jaString = match.group(1)
|
||||
jaString = extract_dialogue_after_speaker(data[i])
|
||||
if jaString is not None:
|
||||
|
||||
# Pass 1
|
||||
if not translatedList:
|
||||
|
|
@ -254,7 +248,7 @@ def translateTxt(data, filename, translatedList):
|
|||
stringList = None
|
||||
|
||||
# Remove speaker
|
||||
translatedText = re.sub(r"(^\[.+?\]\s?[|:]\s?)", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# # Textwrap
|
||||
# translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# OpenAI initialization centralized in util/translation.py
|
||||
|
|
@ -320,9 +321,7 @@ def translateTyrano(data, filename, translatedList):
|
|||
stringList = None
|
||||
|
||||
# Remove speaker
|
||||
if speaker != "":
|
||||
matchSpeakerList = re.findall(r"^\[?(.+?)\]?\s?[|:]\s?", translatedText)
|
||||
translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Avoid Crashes
|
||||
translatedText = translatedText.replace("[", "(")
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
|
||||
# OpenAI initialization centralized in util/translation.py
|
||||
|
||||
|
|
@ -391,9 +392,7 @@ def searchCodes(events, pbar, jobList, filename):
|
|||
translatedText = stringList[0]
|
||||
|
||||
# Remove speaker
|
||||
matchSpeakerList = re.findall(r"^(\[.+?\]\s?[|:]\s?)\s?", translatedText)
|
||||
if len(matchSpeakerList) > 0:
|
||||
translatedText = translatedText.replace(matchSpeakerList[0], "")
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Textwrap
|
||||
if FIXTEXTWRAP is True:
|
||||
|
|
@ -667,9 +666,7 @@ def searchCodes(events, pbar, jobList, filename):
|
|||
translatedText = list300[0]
|
||||
|
||||
# Remove speaker
|
||||
matchSpeakerList = re.findall(r"^(\[.+?\]\s?[|:]\s?)\s?", translatedText)
|
||||
if len(matchSpeakerList) > 0:
|
||||
translatedText = translatedText.replace(matchSpeakerList[0], "")
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Textwrap
|
||||
if FIXTEXTWRAP is True:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
import tempfile
|
||||
|
||||
# OpenAI initialization centralized in util/translation.py
|
||||
|
|
@ -323,9 +324,7 @@ def translateWOLF(data, translatedList, pbar, filename):
|
|||
translatedList = None
|
||||
|
||||
# Remove speaker
|
||||
matchSpeakerList = re.findall(r"^(\[.+?\]\s?[|:]\s?)\s?", translatedText)
|
||||
if len(matchSpeakerList) > 0:
|
||||
translatedText = translatedText.replace(matchSpeakerList[0], "")
|
||||
translatedText = strip_speaker_prefix(translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from colorama import Fore
|
|||
from tqdm import tqdm
|
||||
|
||||
import util.dazedwrap as dazedwrap
|
||||
from util.speaker_prefix import strip_speaker_prefix
|
||||
from util.translation import (
|
||||
TranslationConfig,
|
||||
calculateCost,
|
||||
|
|
@ -302,10 +303,9 @@ def translateYuris(data, filename):
|
|||
|
||||
def stripSpeakerPrefix(translated_text):
|
||||
"""Same behavior as modules/json.translateJSON: strip [Name]: prefix from TL lines."""
|
||||
match = re.search(r"(^\[.+?\]\s?[|:]\s?)", translated_text)
|
||||
if match:
|
||||
translated_text = translated_text.replace(match.group(1), "")
|
||||
else:
|
||||
before_strip = translated_text
|
||||
translated_text = strip_speaker_prefix(translated_text)
|
||||
if translated_text == before_strip:
|
||||
cjk_m = re.match(
|
||||
r"^[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]+\s*[\((:]\s*",
|
||||
translated_text,
|
||||
|
|
|
|||
44
tests/test_speaker_prefix.py
Normal file
44
tests/test_speaker_prefix.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import unittest
|
||||
|
||||
from util.speaker_prefix import (
|
||||
SPEAKER_TAG_RE,
|
||||
extract_dialogue_after_speaker,
|
||||
strip_speaker_prefix,
|
||||
)
|
||||
|
||||
|
||||
class SpeakerPrefixTests(unittest.TestCase):
|
||||
def test_strip_plain_speaker(self):
|
||||
self.assertEqual(strip_speaker_prefix("[Kurone]: Hello"), "Hello")
|
||||
|
||||
def test_strip_color_coded_speaker(self):
|
||||
line = r"[\C[10]Hp Drink\C[0]]: Received item!"
|
||||
self.assertEqual(strip_speaker_prefix(line), "Received item!")
|
||||
|
||||
def test_strip_fullwidth_colon(self):
|
||||
line = r"[\C[10]HPドリンク\C[0]]:をもらった!"
|
||||
self.assertEqual(strip_speaker_prefix(line), "をもらった!")
|
||||
|
||||
def test_tag_captures_color_coded_speaker(self):
|
||||
line = r"[\C[10]Hp Drink\C[0]]: 【\c[10]HPドリンク\c[0]】をもらった!"
|
||||
m = SPEAKER_TAG_RE.match(line)
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), r"\C[10]Hp Drink\C[0]")
|
||||
|
||||
def test_extract_dialogue_skips_color_speaker(self):
|
||||
line = r"[\C[10]Hp Drink\C[0]]: 【\c[10]HPドリンク\c[0]】をもらった!"
|
||||
self.assertEqual(
|
||||
extract_dialogue_after_speaker(line),
|
||||
"【\\c[10]HPドリンク\\c[0]】をもらった!",
|
||||
)
|
||||
|
||||
def test_actor_variable_in_speaker(self):
|
||||
line = r"[\n[1]]: Actor line"
|
||||
m = SPEAKER_TAG_RE.match(line)
|
||||
self.assertIsNotNone(m)
|
||||
self.assertEqual(m.group(1), r"\n[1]")
|
||||
self.assertEqual(extract_dialogue_after_speaker(line), "Actor line")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
43
util/speaker_prefix.py
Normal file
43
util/speaker_prefix.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""Shared [Speaker]: prefix parsing for dialogue lines.
|
||||
|
||||
Handles RPG Maker control codes inside speaker brackets, e.g.
|
||||
``[\\C[10]Hp Drink\\C[0]]: dialogue`` where inner ``[10]`` must not end the match early.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
# Inner text of [Speaker] — allows \C[n], \c[n], \i[n], \n[actorId], etc.
|
||||
# The control-code branch (\X[...]) keeps the inner [..] from ending the bracket early.
|
||||
SPEAKER_BRACKET_INNER = r"(?:\\[A-Za-z]+\[[^\]]*\]|[^\]\n])+"
|
||||
|
||||
# A bare [Speaker]: / [Speaker]| / [Speaker]: prefix.
|
||||
_PREFIX_PATTERN = rf"^\[{SPEAKER_BRACKET_INNER}\]\s*[|::]\s*"
|
||||
SPEAKER_PREFIX_RE = re.compile(_PREFIX_PATTERN, re.IGNORECASE)
|
||||
|
||||
SPEAKER_TAG_RE = re.compile(
|
||||
rf"^\[({SPEAKER_BRACKET_INNER})\]",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Dialogue body after an optional [Speaker]: prefix (text.py lineTextRegex, etc.)
|
||||
SPEAKER_PREFIX_OPTIONAL_RE = re.compile(
|
||||
rf"(?:{_PREFIX_PATTERN})?(.+)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def strip_speaker_prefix(text: str) -> str:
|
||||
"""Remove a leading ``[Speaker]:`` / ``[Speaker]|`` / ``[Speaker]:`` prefix."""
|
||||
if not text:
|
||||
return text
|
||||
return SPEAKER_PREFIX_RE.sub("", text, count=1)
|
||||
|
||||
|
||||
def extract_dialogue_after_speaker(text: str) -> str | None:
|
||||
"""Return dialogue body after an optional ``[Speaker]:`` prefix, or ``None``."""
|
||||
if not text:
|
||||
return None
|
||||
m = SPEAKER_PREFIX_OPTIONAL_RE.match(text)
|
||||
return m.group(1) if m else None
|
||||
Loading…
Reference in a new issue