integrations (#16)

This commit is contained in:
illian64 2025-10-18 19:00:21 +07:00 committed by GitHub
parent cde657a761
commit 9252db4a28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 416 additions and 20 deletions

View file

@ -168,8 +168,8 @@ class AppCore(JaaCore):
# log request / response text
if self.rest_log_params is not None and (self.rest_log_params.translate_resp_text or self.rest_log_params.translate_req_text):
log_source = "\nSource:\n" + req.text if self.rest_log_params.translate_req_text else ""
log_translate = "\nResult:\n" + translate_text if self.rest_log_params.translate_resp_text else ""
log_source = "\n\tSource:\n" + req.text if self.rest_log_params.translate_req_text else ""
log_translate = "\n\tResult:\n" + translate_text if self.rest_log_params.translate_resp_text else ""
logger.info(f"Translate text.{log_source}{log_translate}")
return dto.TranslateResp(result=translate_text, parts=translate_parts, error=None)

View file

View file

View file

@ -22,6 +22,10 @@
[Перевод файлов](processing_files.md)
## Интеграции
Интеграция с внешними приложениями. [Документация](../../integrations/readme.md).
## API
Проект поддерживает Swagger-UI, в котором можно увидеть все методы API.
@ -38,20 +42,21 @@
## Структура проекта
* `/app` - папка с python-файлами, которые используются плагинами и API-контроллером.
* `/cache` - папка для сохранения файла базы кэша по умолчанию. [Документация](doc/ru/options.md).
* `/cache` - папка для сохранения файла базы кэша по умолчанию. [Документация](options.md).
* `/doc` - документация
* `/files_processing` - директория для обработки/перевода файлов по умолчанию.
в `/files_processing/in` помещаются файлы для обработки, в папке `/files_processing/out` создаются результаты обработки.
[Документация](doc/ru/processing_files.md).
[Документация](processing_files.md).
* integrations - данные по интеграции с другими приложениями. [Документация](integrations.md).
* `/models` - папка по умолчанию для размещения моделей для перевода, таких как madlad-400 или nllb-200.
[Документация](doc/ru/translate_text.md).
* `/options` - папка с настройками сервиса и плагинов. [Документация](doc/ru/options.md).
[Документация](translate_text.md).
* `/options` - папка с настройками сервиса и плагинов. [Документация](options.md).
* `/plugins` - папка с python-файлами плагинов, перевода и обработки файлов.
* `/resources` - папка с файлами ресурсов проекта, такими, как файл конфигурация логов, файлы миграции базы данных кэша.
* `/static` - папка с html, css, js файлами для веб-интерфейса.
* `/test` - файлы unit-тестов для исходного кода.
* `compose.yaml` - файл с настройками для запуска Docker-контейнера. [Документация](doc/ru/install.md).
* `Dockerfile` - файл с настройками создания Docker-контейнера. [Документация](doc/ru/install.md).
* `compose.yaml` - файл с настройками для запуска Docker-контейнера. [Документация](install.md).
* `Dockerfile` - файл с настройками создания Docker-контейнера. [Документация](install.md).
* `jaa.py` - библиотека управления плагинами.
* `requirements.txt` - внешние зависимости проекта.

View file

@ -0,0 +1,45 @@
# LunaTranslator
## English
Project site: https://docs.lunatranslator.org/en/
---
## Русский
Сайт проекта: https://docs.lunatranslator.org/ru/
### Установка
Для интеграции нужно:
* Открыть настройки `LunaTranslator`.
* Перейти в пункт `Перевод`
* Найти подраздел `Прочее`.
* Найти пункт `Пользовательский перевод`.
* Включить перевод.
Далее можно или скопировать-вставить текст, или заменить файл, что более просто и исключит вероятность ошибок при вставке текста.
**Вариант с заменой файла.**
* Перейти в папку уть_устанвоки_LunaTranslator\userconfig`.
* Скопировать файл из папки интеграции `selfbuild.py` в папку уть_устанвоки_LunaTranslator\userconfig`.
**Вариант с копированием-вставкой текста**
* Нажать кнопку редактирования (кнопка с карандашом, после кнопки с кистью).
* Выбрать любой текстовый редактор для открытия файла (для windows можно использовать Блокнот).
* Скопировать содержимое файла `selfbuild.py` из папки интеграции в открытый ранее файл.
* Сохранить файл.
### Параметры
В начале файла можно увидеть параметры, под текстом `Configuration variables`.
* **llm_translate__translate_path** - url приложения llm-translate + `/translate`, по умолчанию `http://127.0.0.1:4990/translate`.
* **llm_translate__context** - дополнительный контекст для перевода. Может быть пустым. Нужно обратить внимание, что не все плагины для перевода llm-translate поддерживают контекст.
* **llm_translate__use_languages_from_luna_translate** - использовать параметры языков из LunaTranslator.
* **llm_translate__from_lang** - двухбуквенный код языка оригинала.
Если не выбран параметр `llm_translate__use_languages_from_luna_translate`, то будет использоваться значение из этого параметра.
Может быть пустым, тогда llm-translate сам подставит язык из своих параметров.
* **llm_translate__to_lang** - двухбуквенный код языка, на который нужно выполнить перевод.
Если не выбран параметр `llm_translate__use_languages_from_luna_translate`, то будет использоваться значение из этого параметра.
Может быть пустым, тогда llm-translate сам подставит язык из своих параметров.

View file

@ -0,0 +1,33 @@
import requests
from translator.basetranslator import basetrans
# -------------------------------------------------------------------------
# Configuration variables
# -------------------------------------------------------------------------
llm_translate__translate_path = "http://127.0.0.1:4990/translate"
llm_translate__context = "CONTEXT: This text (or word) for translation is the dialogue of characters in a computer game.\n"
llm_translate__use_languages_from_luna_translate = True
llm_translate__from_lang = ""
llm_translate__to_lang = ""
# -------------------------------------------------------------------------
class TS(basetrans):
def translate(self, content: str):
req = {
"text": content,
"from_lang": self.srclang if llm_translate__use_languages_from_luna_translate else llm_translate__from_lang,
"to_lang": self.tgtlang if llm_translate__use_languages_from_luna_translate else llm_translate__to_lang,
"context": llm_translate__context,
}
try:
resp = requests.post(llm_translate__translate_path, json=req).json()
if resp.get("result"):
return resp["result"]
elif resp.get("error"):
return resp["error"]
else:
return "Unknown error"
except Exception as e:
raise Exception("Unknown error, text:" + content + ", e: " + str(e))

17
integrations/readme.md Normal file
View file

@ -0,0 +1,17 @@
# Integrations
## English
The folders contain instructions or scripts for integrating with external applications.
A more detailed description, usage instructions, and available parameters are located in the folder for the specific integration.
---
# Интеграции
## Русский
В папках находятся инструкции или скрипты для интеграции с внешними приложениями.
Более подробное описание, способ использования, доступные параметры - находится в папке с конкретной интеграцией.

View file

@ -0,0 +1,161 @@
init 99 python:
import sys
#--------------------------------------------------------------------------
# Configuration variables
#--------------------------------------------------------------------------
llm_translate__translate_path = "http://127.0.0.1:4990/translate"
llm_translate__preserve_original_text = True
llm_translate__translate_text_all = False # not recommended
llm_translate__translate_font_name = "DejaVuSans.ttf"
llm_translate__translate_font_size = 22
llm_translate__translate_font_format_tag = "i"
llm_translate__replace_new_line_to_space = True
llm_translate__translate_text_delimiter = "\n"
llm_translate__from_lang = ""
llm_translate__to_lang = ""
llm_translate__context = "CONTEXT: This text for translation is the dialogue of characters in a computer game.\n"
#--------------------------------------------------------------------------
# module variables
llm_translate__translate_toggle_value = True # enable / disable translate
llm_translate__translated_text_dict = {} # cache of translated text
def llm_translate__fill_variables_values(src):
"""
Set values for variables in text.
:param src: text
:return: text with filled variables values
"""
s = src
s = renpy.substitutions.substitute(s, scope = None, force = True, translate = False)
if isinstance(s, (bytes, str)):
return s
return s[0]
#
def llm_translate__preprocess_text(src):
"""
Preprocess text - remove tags, fill variables values
:param src: text
:return: preprocessed text
"""
s = src
if llm_translate__replace_new_line_to_space:
s = s.replace("{p}", " ")
s = s.replace("\n", " ")
else:
s = s.replace("{p}", "\n")
s = llm_translate__fill_variables_values(s)
s = renpy.translation.dialogue.notags_filter(s) #remove tags {}
return s
def llm_translate__wrap_text_tag(text, tag, value = None):
"""
Formatting text with tags.
:param text: text
:param tag: tag
:param value: tag value (optional)
:return: text with tags
"""
if tag is None or tag == "":
return text
if value is None:
return "{" + tag + "}" + text + "{/" + tag + "}"
else:
return "{" + tag + "=" + str(value) + "}" + text + "{/" + tag + "}"
# translate request with module requests - for python v3
def llm_translate__request_python_v3(src, s):
"""
Request to translate text.
:param src: source text
:param s: preprocessed text
:return: translate result
"""
import requests
req = {
"text": s,
"llm_translate__from_lang": llm_translate__from_lang,
"llm_translate__to_lang": llm_translate__to_lang,
"context": llm_translate__context,
}
resp = requests.post(url=llm_translate__translate_path, json=req).json()
if resp.get("result"):
translate = resp["result"].replace("\n", "{p}")
translate_with_tags = translate
translate_with_tags = llm_translate__wrap_text_tag(translate_with_tags, llm_translate__translate_font_format_tag)
translate_with_tags = llm_translate__wrap_text_tag(translate_with_tags, "font", llm_translate__translate_font_name)
translate_with_tags = llm_translate__wrap_text_tag(translate_with_tags, "size", llm_translate__translate_font_size)
if llm_translate__preserve_original_text:
result = src + llm_translate__translate_text_delimiter + translate_with_tags
else:
result = translate_with_tags
return result
else:
return resp["error"]
#
def llm_translate__translate_text(src):
"""
Main function to translate
:param src: text
:return: translate / original text and translate
"""
s = src
# text is empty or translate disable - return
if s is None or s == "" or not llm_translate__translate_toggle_value:
return s
# preprocess text and again validate to empty
s = llm_translate__preprocess_text(s)
if s is None or s == "":
return src
dict_translated_value = llm_translate__translated_text_dict.get(s, None)
if dict_translated_value is None:
translate_result = llm_translate__request_python_v3(src, s)
llm_translate__translated_text_dict[s] = translate_result
else:
return dict_translated_value
# enable or disable translate
def llm_translate__toggle_translate():
global llm_translate__translate_toggle_value
value = not llm_translate__translate_toggle_value
llm_translate__translate_toggle_value = value
if not value: #clear cache
llm_translate__translated_text_dict.clear()
# apply replace text
if llm_translate__translate_text_all:
config.replace_text = llm_translate__translate_text
else:
config.say_menu_text_filter = llm_translate__translate_text
# translate request with module requests - for python v3
def llm_translate__request_python_v2(src, s):
return src
# button to enable or disable translate
screen toggle_tr_button():
hbox:
style_prefix "quick"
xalign 0.0
yalign 0.0
textbutton _("Tr: " + str(llm_translate__translate_toggle_value)) action Function(llm_translate__toggle_translate)
init python:
config.overlay_screens.append("toggle_tr_button")

View file

@ -0,0 +1,49 @@
# Ren'py
## English
[Ren'Py](https://www.renpy.org/) - is a game engine for creating games, typically text-based novels.
## Русский
[Ren'Py](https://www.renpy.org/) - игровой движок для создания игр, как правило - текстовых новелл.
Поддерживаются игры на Python 3, то есть с [версией Ren'Py 8.x.y](https://www.renpy.org/doc/html/implementation/two_and_three.html).
**Внимание. Эта интеграция может привести к некорректной работе игры.**
Некоторые игры могут основывать логику работы скрипта игры на тексте, который плагин переводит.
Исправить это не представляется возможным, делать специальную обработку под каждую игру слишком сложно.
В таком случае можно или временно выключить плагин и включить его обратно, или использовать другие варианты, например [LunaTranslator](../luna-translator/readme.md).
### Установка
Необходимо добавить файл `_llm-translate-integration.rpy` в папку с игрой по следующему пути:
уть_устанвоки_игры\game`.
Если установка прошла успешно, то в игре, в диалогах (не в главном меню), в верхнем левом углу появится кнопка Tr: True.
Нажатием на эту кнопку можно включить или выключить перевод.
### Параметры
В начале файла можно увидеть параметры, под текстом `Configuration variables`.
* **llm_translate__translate_path** - url приложения llm-translate + `/translate`, по умолчанию `http://127.0.0.1:4990/translate`.
* **llm_translate__preserve_original_text** - сохранять оригинальный текст. Рекомендуется, потому что при работе интеграция удаляет форматирование текста - цвет, начертание и т.д.
* **llm_translate__translate_text_all** - переводить весь текст или только текст в диалогах. Перевод всего текста не рекомендуется, так как больше вероятности, что это может сломать игру.
* **llm_translate__translate_font_name** - шрифт перевода. Шрифт должен поддерживать символы языка, на который происходит перевод.
Шрифт должен находиться в папке `fonts` уть_устанвоки_игры\game\fonts` или быть установлен в системе (более надежный и простой вариант).
* **llm_translate__translate_font_size** - размер шрифта перевода.
* **llm_translate__translate_font_format_tag** - тэг текста перевода. По умолчанию `i` - курсив.
* **llm_translate__replace_new_line_to_space** = Заменять символ новой строки пробелом. Может быть полезно, если текста очень много и текст с переводом не помещается в окно вывода.
* **llm_translate__translate_text_delimiter** - символ-разделитель между исходным и переведенным текстом (если включен параметр `llm_translate__preserve_original_text`).
* **llm_translate__from_lang** - двухбуквенный код языка оригинала.
Если не выбран параметр `llm_translate__use_languages_from_luna_translate`, то будет использоваться значение из этого параметра.
Может быть пустым, тогда llm-translate сам подставит язык из своих параметров.
* **llm_translate__to_lang** - двухбуквенный код языка, на который нужно выполнить перевод.
Если не выбран параметр `llm_translate__use_languages_from_luna_translate`, то будет использоваться значение из этого параметра.
Может быть пустым, тогда llm-translate сам подставит язык из своих параметров.
* **llm_translate__context** - дополнительный контекст для перевода. Может быть пустым. Нужно обратить внимание, что не все плагины для перевода llm-translate поддерживают контекст.

View file

@ -0,0 +1,50 @@
# xunity-autotranslator
## English
Project site: https://github.com/bbepis/XUnity.AutoTranslator
## Русский
Сайт проекта: https://github.com/bbepis/XUnity.AutoTranslator
### Установка
Установка на примере [BepInEx](https://github.com/bbepis/BepInEx).
* Необходимо загрузить `xunity-autotranslator` и `BepInEx`, поместить их в папку с игрой, согласно инструкциям этих проектов.
* Запустить игру и закрыть.
* Если плагины для перевода были установлены правильно, то будет создан файл по пути
`папка_сгрой/BepInEx/config/AutoTranslatorConfig.ini`
* Необходимо открыть этот файл `AutoTranslatorConfig.ini` в любом текстовом редакторе (для windows можно использовать Блокнот).
* Найти следующие параметры и задать им следующие значения:
Для включения интеграции с llm-translate:
```
[Service]
Endpoint=CustomTranslate
```
Для выбора языков - двухбуквенный код языка оригинала и языка, на который нужно выполнить перевод.
```
[General]
Language=ru
FromLanguage=en
```
Для настройки url приложения llm-translate.
```
[Custom]
Url=http://127.0.0.1:4990/translate/xunity-autotranslator
EnableShortDelay=False
DisableSpamChecks=True
```
В запросе можно передать контекст дял перевода, тогда параметр Url будет выглядеть примерно так:
```
Url=http://127.0.0.1:4990/translate/xunity-autotranslator?context=Context - This text (or word) for translation is the dialogue of characters in a computer game.
```

33
main.py
View file

@ -2,9 +2,9 @@ import sys
from contextlib import asynccontextmanager
import uvicorn
from fastapi import FastAPI, Request, status
from fastapi import FastAPI, Request, status, Query
from fastapi.exceptions import RequestValidationError
from starlette.responses import JSONResponse
from starlette.responses import JSONResponse, Response
from starlette.staticfiles import StaticFiles
from app import dto, log, cuda
@ -169,6 +169,35 @@ async def translate_sugoi_like_post(text: str, from_lang: str = "", to_lang: str
return dto.SugoiLikeGetResp(trans=translate.result)
@app.get("/translate/xunity-autotranslator")
async def translate_sugoi_like_post( text: str, from_lang: str = Query("", alias="from"), to: str = "",
context: str = None, translator_plugin: str = "") -> Response:
"""
Translate text. Request and response for xunity-autotranslator - https://github.com/bbepis/XUnity.AutoTranslator
:param str text: text to translate.
:param str from_lang (from in Request Param - from is reserved word in python): from language (2 symbols, like "en").
May be empty (will be replaced to "default_from_lang" from options)
:param str to: to language (2 symbols, like "en").
May be empty (will be replaced to "default_to_lang" from options)
:param str context: additional context to translate (if model has context support)
:param str translator_plugin: plugin to use. If blank, default will be used.
If not initialized (not in "default_translate_plugin" and not in "init_on_start" from options - throw error)
:return: translated text string
"""
request = dto.TranslateCommonRequest(text=text, context=context, from_lang=from_lang, to_lang=to,
translator_plugin=translator_plugin)
translate = core.translate(request)
return Response(content=translate.result, media_type="text/plain")
@app.get("/process-files-list")
async def process_files_list(recursive_sub_dirs: bool) -> dto.ProcessingFileDirListResp:
"""

View file

@ -1,16 +1,16 @@
import datetime
import gc
import inspect
import os
import torch
from nemo.collections.asr.models import ASRModel, EncDecRNNTModel
from pydub import AudioSegment
from app import file_processor, cuda
from app import file_processor, cuda, log
from app.app_core import AppCore
from app.dto import ProcessingFileDirReq, ProcessingFileResp, FileProcessingPluginInitInfo, ProcessingFileStruct
logger = log.logger()
plugin_name = os.path.basename(__file__)[:-3] # calculating modname
model: EncDecRNNTModel | None = None
@ -98,14 +98,19 @@ def file_processing(core: AppCore, file_struct: ProcessingFileStruct, req: Proce
audio.export(resampled_audio_file, format="wav", parameters=["-ar", "16000", "-ac", "1"])
try:
# need to get function params - canary model supports "source_lang", parapet model - doesn't
param_names = [name for name, _ in inspect.signature(model.transcribe).parameters.items()]
if "source_lang" in param_names and "target_lang" in param_names:
try: # canary model supports "source_lang", parapet model - doesn't
transcribe = model.transcribe(audio=[resampled_audio_file], source_lang=req.from_lang, target_lang=req.from_lang,
timestamps=True, batch_size=options["batch_size"])
else:
transcribe = model.transcribe(audio=[resampled_audio_file],
timestamps=True, batch_size=options["batch_size"])
except Exception as possible_param_exc:
if "argument 'source_lang'" in str(possible_param_exc) or "argument 'target_lang'" in str(possible_param_exc):
logger.info("It seems the model " + options["model"] + " does not support source_lang / target_lang params. Result text wiil be in english. Try to repeat request without params.")
transcribe = model.transcribe(audio=[resampled_audio_file],
timestamps=True, batch_size=options["batch_size"])
else:
log.log_exception("Error transcribe.", possible_param_exc)
transcribe = None
if not transcribe or not isinstance(transcribe, list) or not transcribe[0] or not hasattr(transcribe[0], 'timestamp') or not transcribe[0].timestamp or 'segment' not in transcribe[0].timestamp:
return file_processor.get_processing_file_resp_error(
file_in=file_struct.file_name_ext, path_in=file_struct.path_in, error_msg="Can't get transcribe")
@ -117,7 +122,7 @@ def file_processing(core: AppCore, file_struct: ProcessingFileStruct, req: Proce
out_file_name = processed_file_name(core=core, file_struct=file_struct, req=req)
with open(file_struct.path_file_out(out_file_name), "w") as f:
f.write(srt_content)
if options["translate_after_processing"]:
if options["translate_after_processing"] and req.from_lang != req.to_lang:
return translate_after_processing(core=core, req=req, file_name_ext=out_file_name)
else:
return file_processor.get_processing_file_resp_ok(file_struct=file_struct, file_out=out_file_name)

View file

@ -97,7 +97,7 @@ def file_processing(core: AppCore, file_struct: ProcessingFileStruct, req: Proce
writer = utils.get_writer('srt', file_struct.path_out)
writer(transcribe, out_file_name, {})
if options["translate_after_processing"]:
if options["translate_after_processing"] and req.from_lang != req.to_lang:
return translate_after_processing(core=core, req=req, file_name_ext=out_file_name)
else:
return file_processor.get_processing_file_resp_ok(file_struct=file_struct, file_out=out_file_name)

View file

@ -17,6 +17,8 @@ The MIT License
Поддерживается как перевод текста через веб-интерфейс или запросы к API, так и перевод файлов.
Поддерживаются интеграции с другими приложениями, такими, как [LunaTranslator](https://docs.lunatranslator.org/en/), игры на движке renpy.
Более подробно - в [документации](doc/ru/readme.md).
Распространяется по MIT лицензии.