Some kirikiri updates
This commit is contained in:
parent
88b13fece1
commit
95bf4c0b84
1 changed files with 147 additions and 25 deletions
|
|
@ -105,25 +105,34 @@ def handleKirikiri(filename, estimate):
|
||||||
return totalString
|
return totalString
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
# We no longer keep the destination file open during translation because
|
||||||
|
# incremental progress saves (save_progress_lines) need to atomically
|
||||||
|
# replace the file on Windows. Holding an open handle prevents os.replace
|
||||||
|
# from succeeding (WinError 5 Access is denied).
|
||||||
try:
|
try:
|
||||||
with open("translated/" + filename, "w", encoding="cp932", errors="ignore") as outFile:
|
start = time.time()
|
||||||
start = time.time()
|
translatedData = openFiles(filename)
|
||||||
translatedData = openFiles(filename)
|
end = time.time()
|
||||||
|
|
||||||
# Print Result
|
# Final write safeguard: if for some reason the progress file was
|
||||||
end = time.time()
|
# never written (e.g. no translatable lines triggered saves), write it now.
|
||||||
if translatedData[0] != []:
|
try:
|
||||||
outFile.writelines(translatedData[0])
|
if translatedData[0]:
|
||||||
else:
|
os.makedirs("translated", exist_ok=True)
|
||||||
PBAR.write(f"{FILENAME} Failed to write")
|
final_path = os.path.join("translated", filename)
|
||||||
os.remove(f"translated/{filename}")
|
# Write directly (small risk window acceptable on final flush)
|
||||||
tqdm.write(getResultString(translatedData, end - start, filename))
|
with open(final_path, "w", encoding="cp932", errors="ignore", newline="\n") as f:
|
||||||
with LOCK:
|
f.writelines(translatedData[0])
|
||||||
TOKENS[0] += translatedData[1][0]
|
except Exception:
|
||||||
TOKENS[1] += translatedData[1][1]
|
traceback.print_exc()
|
||||||
|
|
||||||
|
tqdm.write(getResultString(translatedData, end - start, filename))
|
||||||
|
with LOCK:
|
||||||
|
TOKENS[0] += translatedData[1][0]
|
||||||
|
TOKENS[1] += translatedData[1][1]
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
os.remove(f"translated/{filename}")
|
# Don't blindly remove the file; it may contain partial progress.
|
||||||
return "Fail"
|
return "Fail"
|
||||||
|
|
||||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||||
|
|
@ -183,24 +192,57 @@ def parseKiriKiri(readFile, filename):
|
||||||
|
|
||||||
|
|
||||||
def save_progress_lines(lines, filename, encoding="cp932"):
|
def save_progress_lines(lines, filename, encoding="cp932"):
|
||||||
"""Atomically save current line-based translation progress."""
|
"""Atomically (with retries) save current line-based translation progress.
|
||||||
try:
|
|
||||||
if ESTIMATE:
|
Rationale:
|
||||||
return
|
Windows raises PermissionError if the destination file is open elsewhere.
|
||||||
os.makedirs("translated", exist_ok=True)
|
We avoid holding an open handle outside this function and add a small
|
||||||
tmp_fd, tmp_path = tempfile.mkstemp(prefix=f"{filename}.", suffix=".tmp", dir="translated")
|
exponential backoff retry loop to handle transient locks (e.g. AV scanners).
|
||||||
|
"""
|
||||||
|
if ESTIMATE:
|
||||||
|
return
|
||||||
|
|
||||||
|
max_attempts = 5
|
||||||
|
backoff = 0.05 # seconds
|
||||||
|
tmp_fd = None
|
||||||
|
tmp_path = None
|
||||||
|
|
||||||
|
for attempt in range(1, max_attempts + 1):
|
||||||
try:
|
try:
|
||||||
|
os.makedirs("translated", exist_ok=True)
|
||||||
|
|
||||||
|
# Create temp file every attempt (prior one cleaned in finally).
|
||||||
|
tmp_fd, tmp_path = tempfile.mkstemp(prefix=f"{filename}.", suffix=".tmp", dir="translated")
|
||||||
with os.fdopen(tmp_fd, "w", encoding=encoding, newline="\n", errors="ignore") as tmp_file:
|
with os.fdopen(tmp_fd, "w", encoding=encoding, newline="\n", errors="ignore") as tmp_file:
|
||||||
tmp_file.writelines(lines)
|
tmp_file.writelines(lines)
|
||||||
os.replace(tmp_path, os.path.join("translated", filename))
|
tmp_file.flush()
|
||||||
|
os.fsync(tmp_file.fileno())
|
||||||
|
|
||||||
|
dest_path = os.path.join("translated", filename)
|
||||||
|
try:
|
||||||
|
os.replace(tmp_path, dest_path)
|
||||||
|
except PermissionError as e:
|
||||||
|
# Retry on Windows-specific sharing violation
|
||||||
|
if attempt < max_attempts:
|
||||||
|
time.sleep(backoff)
|
||||||
|
backoff *= 2
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
# Success, break loop
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
if attempt == max_attempts:
|
||||||
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(tmp_path):
|
# Ensure temp file removed if it still exists
|
||||||
|
if tmp_path and os.path.exists(tmp_path):
|
||||||
try:
|
try:
|
||||||
os.remove(tmp_path)
|
os.remove(tmp_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
tmp_fd = None
|
||||||
traceback.print_exc()
|
tmp_path = None
|
||||||
|
|
||||||
|
|
||||||
def translateKiriKiri(data, pbar, filename, jobList):
|
def translateKiriKiri(data, pbar, filename, jobList):
|
||||||
|
|
@ -223,6 +265,7 @@ def translateKiriKiri(data, pbar, filename, jobList):
|
||||||
dialogueRegex = r"^\[text\](.*).*\[KeyWait\]|\[\w+\](.*)\[\/\w+\].*\[KeyWait\]"
|
dialogueRegex = r"^\[text\](.*).*\[KeyWait\]|\[\w+\](.*)\[\/\w+\].*\[KeyWait\]"
|
||||||
furiganaRegex = r'(\[eruby\sstr="(.*?)"\stext.*?\])'
|
furiganaRegex = r'(\[eruby\sstr="(.*?)"\stext.*?\])'
|
||||||
choicesRegex = r"^\s*\[button\d\sclickse=sys_decide.*text='(.*?)'.*"
|
choicesRegex = r"^\s*\[button\d\sclickse=sys_decide.*text='(.*?)'.*"
|
||||||
|
taggedDialogueRegex = r"^\[(?P<tag>[^\s\]/]+)(?:\s[^\]]*)?\](?P<text>.*?)\[/\1\]"
|
||||||
|
|
||||||
while i < len(data):
|
while i < len(data):
|
||||||
speaker = ""
|
speaker = ""
|
||||||
|
|
@ -259,6 +302,85 @@ def translateKiriKiri(data, pbar, filename, jobList):
|
||||||
data[i] = data[i].replace(jaString, translatedText)
|
data[i] = data[i].replace(jaString, translatedText)
|
||||||
save_progress_lines(data, filename)
|
save_progress_lines(data, filename)
|
||||||
|
|
||||||
|
# Tagged dialogue lines e.g., [思考 storage="..."]text[/思考]
|
||||||
|
tagged = re.match(taggedDialogueRegex, data[i])
|
||||||
|
if tagged and DIALOGUE:
|
||||||
|
tag_name = tagged.group('tag')
|
||||||
|
jaString = tagged.group('text')
|
||||||
|
|
||||||
|
# Pass 1: enqueue with speaker from closing tag
|
||||||
|
if not setData:
|
||||||
|
# Remove inline wrapping
|
||||||
|
jaString_clean = jaString.replace("[r]", " ")
|
||||||
|
# Remove furigana
|
||||||
|
matchList = re.findall(furiganaRegex, jaString_clean)
|
||||||
|
if matchList:
|
||||||
|
for fm in matchList:
|
||||||
|
jaString_clean = jaString_clean.replace(fm[0], fm[1])
|
||||||
|
|
||||||
|
# Resolve speaker via getSpeaker
|
||||||
|
resolved = getSpeaker(tag_name)
|
||||||
|
tag_speaker = resolved[0]
|
||||||
|
tokens[0] += resolved[1][0]
|
||||||
|
tokens[1] += resolved[1][1]
|
||||||
|
|
||||||
|
if tag_speaker:
|
||||||
|
stringList.append(f"[{tag_speaker}]: {jaString_clean.strip()}")
|
||||||
|
else:
|
||||||
|
stringList.append(jaString_clean.strip())
|
||||||
|
|
||||||
|
# Pass 2: apply translated text back between tags
|
||||||
|
else:
|
||||||
|
if len(stringList) > 0:
|
||||||
|
translatedText = stringList[0]
|
||||||
|
stringList.pop(0)
|
||||||
|
# Remove Speaker label if present
|
||||||
|
translatedText = re.sub(r"\[.*?\]:\s", "", translatedText)
|
||||||
|
# Wrap and convert newlines to [r]
|
||||||
|
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||||
|
translatedText = translatedText.replace("\n", "[r]")
|
||||||
|
# Replace quotes as per convention
|
||||||
|
data[i] = data[i].replace("'", '"')
|
||||||
|
translatedText = translatedText.replace('"', "'")
|
||||||
|
# Replace only inner content
|
||||||
|
data[i] = data[i].replace(jaString, translatedText)
|
||||||
|
save_progress_lines(data, filename)
|
||||||
|
|
||||||
|
# Simple narrative line handling: translate each whitespace-led, non-tag, non-command line independently.
|
||||||
|
# This avoids reflowing or merging blocks, preventing misplaced text.
|
||||||
|
if not re.match(r"^\[", data[i]) and not data[i].lstrip().startswith("@"):
|
||||||
|
if re.match(r"^[ \t\u3000]+", data[i]):
|
||||||
|
# Skip standalone markers like [▼]
|
||||||
|
if data[i].strip() == "[▼]":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Pass 1
|
||||||
|
if not setData:
|
||||||
|
line_content = data[i].rstrip("\n")
|
||||||
|
# Remove inline wrapping markers and glyph markers
|
||||||
|
line_content = line_content.replace("[r]", " ")
|
||||||
|
line_content = re.sub(r"\[▼\]", "", line_content)
|
||||||
|
# Remove furigana blocks
|
||||||
|
matchList = re.findall(furiganaRegex, line_content)
|
||||||
|
if matchList:
|
||||||
|
for fm in matchList:
|
||||||
|
line_content = line_content.replace(fm[0], fm[1])
|
||||||
|
cleaned = line_content.strip()
|
||||||
|
if cleaned:
|
||||||
|
stringList.append(cleaned)
|
||||||
|
# Pass 2
|
||||||
|
else:
|
||||||
|
if len(stringList) > 0:
|
||||||
|
translatedText = stringList[0]
|
||||||
|
stringList.pop(0)
|
||||||
|
translatedText = re.sub(r"\[.*?\]:\s", "", translatedText)
|
||||||
|
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
|
||||||
|
translatedText = translatedText.replace("\n", "[r]")
|
||||||
|
indent_match = re.match(r"^([ \t\u3000]+)", data[i])
|
||||||
|
indent = indent_match.group(1) if indent_match else ""
|
||||||
|
data[i] = f"{indent}{translatedText}\n"
|
||||||
|
save_progress_lines(data, filename)
|
||||||
|
|
||||||
# Dialogue
|
# Dialogue
|
||||||
match = re.search(dialogueRegex, data[i])
|
match = re.search(dialogueRegex, data[i])
|
||||||
if match and DIALOGUE:
|
if match and DIALOGUE:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue