Some kirikiri updates

This commit is contained in:
dazedanon 2025-09-15 14:30:08 -05:00
parent 88b13fece1
commit 95bf4c0b84

View file

@ -105,25 +105,34 @@ def handleKirikiri(filename, estimate):
return totalString
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:
with open("translated/" + filename, "w", encoding="cp932", errors="ignore") as outFile:
start = time.time()
translatedData = openFiles(filename)
start = time.time()
translatedData = openFiles(filename)
end = time.time()
# Print Result
end = time.time()
if translatedData[0] != []:
outFile.writelines(translatedData[0])
else:
PBAR.write(f"{FILENAME} Failed to write")
os.remove(f"translated/{filename}")
tqdm.write(getResultString(translatedData, end - start, filename))
with LOCK:
TOKENS[0] += translatedData[1][0]
TOKENS[1] += translatedData[1][1]
# Final write safeguard: if for some reason the progress file was
# never written (e.g. no translatable lines triggered saves), write it now.
try:
if translatedData[0]:
os.makedirs("translated", exist_ok=True)
final_path = os.path.join("translated", filename)
# Write directly (small risk window acceptable on final flush)
with open(final_path, "w", encoding="cp932", errors="ignore", newline="\n") as f:
f.writelines(translatedData[0])
except Exception:
traceback.print_exc()
tqdm.write(getResultString(translatedData, end - start, filename))
with LOCK:
TOKENS[0] += translatedData[1][0]
TOKENS[1] += translatedData[1][1]
except Exception:
traceback.print_exc()
os.remove(f"translated/{filename}")
# Don't blindly remove the file; it may contain partial progress.
return "Fail"
return getResultString(["", TOKENS, None], end - start, "TOTAL")
@ -183,24 +192,57 @@ def parseKiriKiri(readFile, filename):
def save_progress_lines(lines, filename, encoding="cp932"):
"""Atomically save current line-based translation progress."""
try:
if ESTIMATE:
return
os.makedirs("translated", exist_ok=True)
tmp_fd, tmp_path = tempfile.mkstemp(prefix=f"{filename}.", suffix=".tmp", dir="translated")
"""Atomically (with retries) save current line-based translation progress.
Rationale:
Windows raises PermissionError if the destination file is open elsewhere.
We avoid holding an open handle outside this function and add a small
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:
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:
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:
if os.path.exists(tmp_path):
# Ensure temp file removed if it still exists
if tmp_path and os.path.exists(tmp_path):
try:
os.remove(tmp_path)
except OSError:
pass
except Exception:
traceback.print_exc()
tmp_fd = None
tmp_path = None
def translateKiriKiri(data, pbar, filename, jobList):
@ -223,6 +265,7 @@ def translateKiriKiri(data, pbar, filename, jobList):
dialogueRegex = r"^\[text\](.*).*\[KeyWait\]|\[\w+\](.*)\[\/\w+\].*\[KeyWait\]"
furiganaRegex = r'(\[eruby\sstr="(.*?)"\stext.*?\])'
choicesRegex = r"^\s*\[button\d\sclickse=sys_decide.*text='(.*?)'.*"
taggedDialogueRegex = r"^\[(?P<tag>[^\s\]/]+)(?:\s[^\]]*)?\](?P<text>.*?)\[/\1\]"
while i < len(data):
speaker = ""
@ -259,6 +302,85 @@ def translateKiriKiri(data, pbar, filename, jobList):
data[i] = data[i].replace(jaString, translatedText)
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
match = re.search(dialogueRegex, data[i])
if match and DIALOGUE: