whisper (#14)
Some checks are pending
Python application / build (push) Waiting to run

whisper
This commit is contained in:
illian64 2025-10-11 12:56:13 +07:00 committed by GitHub
parent 883a992bc3
commit 07154e93d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 453 additions and 81 deletions

View file

@ -4,7 +4,8 @@ from os import walk
from app import text_splitter, file_processor, dto, log
from app.cache import Cache
from app.params import TranslationParams, TextSplitParams, TextProcessParams, CacheParams, FileProcessingParams
from app.params import TranslationParams, TextSplitParams, TextProcessParams, CacheParams, FileProcessingParams, \
RestLogParams
from app.text_processor import pre_process
from jaa import JaaCore
@ -24,6 +25,7 @@ class AppCore(JaaCore):
self.text_process_params: TextProcessParams | None = None
self.cache_params: CacheParams | None = None
self.file_processing_params: FileProcessingParams | None = None
self.rest_log_params: RestLogParams | None = None
self.translators: dict = {}
self.initialized_translator_engines: dict[str, dto.TranslatePluginInitInfo] = dict()
@ -42,6 +44,7 @@ class AppCore(JaaCore):
init_info.name = cmd
init_info.processing_function = manifest["file_processing"][cmd][1]
init_info.processed_file_name_function = manifest["file_processing"][cmd][2]
init_info.after_processing_function = manifest["file_processing"][cmd][3]
logger.info("Init file processing plugin '%s' for next file extensions: %s",
init_info.name, init_info.supported_extensions)
for ext in init_info.supported_extensions:
@ -163,6 +166,12 @@ class AppCore(JaaCore):
else:
translate_text = req.text
# 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 ""
logger.info(f"Translate text.{log_source}{log_translate}")
return dto.TranslateResp(result=translate_text, parts=translate_parts, error=None)
except ValueError as ve:
return dto.TranslateResp(result=None, parts=None, error=ve.args[0])
@ -237,6 +246,11 @@ class AppCore(JaaCore):
except Exception as e:
log.log_exception("Error proces files: ", e)
return dto.ProcessingFileDirResp(files=list(), error=getattr(e, 'message', repr(e)))
finally:
# clear memory (GPU or RAM) for heavy file processing plugins
for processors_list in self.files_ext_to_processors.values():
for processor in processors_list:
processor.after_processing_function(self)
def process_file(self, req: dto.ProcessingFileDirReq, root: str, file_name: str) -> dto.ProcessingFileResp:
try:

View file

@ -130,6 +130,7 @@ class FileProcessingPluginInitInfo:
plugin_name: str
processing_function: Callable[[Any, ProcessingFileStruct, ProcessingFileDirReq], ProcessingFileResp]
processed_file_name_function: Callable[[Any, ProcessingFileStruct, ProcessingFileDirReq], str]
after_processing_function: Callable[[Any], None]
supported_extensions: set[str] # lower case
def __init__(self, plugin_name: str, supported_extensions: set[str]):

View file

@ -15,7 +15,8 @@ def processed_file_name_def(file_struct: ProcessingFileStruct, req: ProcessingFi
return f'{file_struct.file_name}__{from_lang_part}_{req.to_lang}.{file_struct.file_ext}'
def file_name_from_template(file_struct: ProcessingFileStruct, req: ProcessingFileDirReq, options: dict) -> str:
def file_name_from_template(file_struct: ProcessingFileStruct, req: ProcessingFileDirReq, options: dict,
replace_ext: str = None) -> str:
"""
Generate output file name from template. Template in options, for preserve original and not.
Special parameters in template:
@ -26,14 +27,38 @@ def file_name_from_template(file_struct: ProcessingFileStruct, req: ProcessingFi
:param file_struct: struct with file info
:param req: file process request
:param options: template parameters source
:param replace_ext: replace ext, if not None or empty, instead file_struct.file_ext
:return: output file name
"""
file_ext = file_struct.file_ext if replace_ext is None or replace_ext == "" else replace_ext
template_dict = options["output_file_name_template"]
template = template_dict["preserve_original"] if req.preserve_original_text else template_dict["without_original"]
return file_name_from_predefined_template(file_struct=file_struct, req=req,
template=template, replace_ext=replace_ext)
def file_name_from_predefined_template(file_struct: ProcessingFileStruct, req: ProcessingFileDirReq,
template: str, replace_ext: str = None) -> str:
"""
Generate output file name from template. Template in options, for preserve original and not.
Special parameters in template:
%%source%% - original file name
%%from_lang%% - source language
%%to_lang%% - target language
:param file_struct: struct with file info
:param req: file process request
:param template: template string
:param replace_ext: replace ext, if not None or empty, instead file_struct.file_ext
:return: output file name
"""
file_ext = file_struct.file_ext if replace_ext is None or replace_ext == "" else replace_ext
return ((template.replace("%%source%%", file_struct.file_name)
.replace("%%from_lang%%", req.from_lang)
.replace("%%to_lang%%", req.to_lang))
+ "." + file_struct.file_ext)
.replace("%%from_lang%%", req.from_lang)
.replace("%%to_lang%%", req.to_lang))
+ "." + file_ext)
def get_file_with_path_for_list(init_dir: str, root: str, file_name: str) -> str:

View file

@ -92,6 +92,13 @@ class FileProcessingTextFormat:
return self.translate_prefix + text + self.translate_postfix
@dataclass
class RestLogParams:
translate_req_text: bool
translate_resp_text: bool
translate_validation_errors: bool
def read_plugin_translate_params(manifest: dict):
manifest["options"]["translation_params_struct"] = read_translation_params(manifest)
manifest["options"]["text_split_params_struct"] = read_text_split_params(manifest)
@ -202,4 +209,14 @@ def read_plugin_file_processing_text_format(options: dict):
)
def read_rest_log_params(manifest: dict) -> RestLogParams:
options = manifest["options"]
return RestLogParams(
translate_req_text=options["rest_log_params"]["translate_req_text"],
translate_resp_text=options["rest_log_params"]["translate_resp_text"],
translate_validation_errors=options["rest_log_params"]["translate_validation_errors"],
)
tp: TranslateProgress = TranslateProgress(unit="part", ascii=True, desc="translate parts: ")

View file

@ -110,6 +110,11 @@
* * default_to_lang - двухбуквенный код языка, будет использован, как язык, на который необходимо перевести, если он не был явно указан.
* * sleep_after_translate - время в секундах (может быть дробным, например, 0.1), которое севрис будет ожидать после завершения перевода.
* Группа **rest_log_params** - параметры логирования запросов и ответов перевода.
* * translate_req_text - логирование исходного текста
* * translate_resp_text - логирование текста перевода
* * translate_validation_errors - логирование ошибок в текстах запросов
* **v** - служебное значение, версия плагина. При повышении версии плагин дополнит файл конфигурации новыми параметрами.
### Как текст разбивается на части при использовании параметра split_expected_length

View file

@ -0,0 +1,38 @@
# Плагин обработки файлов: media
Часть параметров, общих для всех плагинов, описана [здесь](../processing_files.md).
## Параметры плагина
* **model** - модель для распознавания звука. Модели можно увидеть например [здесь](https://github.com/openai/whisper/blob/main/whisper/__init__.py).
Вот этот список: tiny.en, tiny, base.en, base, small.en, small, medium.en, medium, large-v1, large-v2, large-v3, large, large-v3-turbo, turbo
* **cuda** - `true` - использовать видеокарту (быстрее), `false` - использовать cpu (медленнее).
* **cuda_device_index** - если в системе несколько видеокарт, можно выбрать ту, в которую будет загружена модель.
Номер и имя видеокарты указывается при старте приложения в логе, вида `INFO GPU #0: NVIDIA GeForce RTX 4090`.
`0` - указываемый в параметре номер.
* **unload_model_after_processing** - выгружать модель из памяти после завершения обработки списка файлов. Можно использовать, если на карте мало памяти.
* **translate_after_processing** - переводить субтитры сразу после распознавания. По умолчанию, будет запущен обработчик [srt-Файлов](file_srt.md).
* **output_file_name_template** - шаблон для имени файла.
Следующие пункты подробно описаны в документации к Whisper, например здесь https://github.com/openai/whisper/blob/main/whisper/transcribe.py, поэтому ниже общее описание.
* **temperature** - массив значений точности распознавания.
* **condition_on_previous_text** - основываться на предыдущем тексте.
* **word_timestamps** - временные метки для слов - нужно для корректного создания субтитров.
* **hallucination_silence_threshold** - порог галлюцинаций (додумывания текста) при распознавании.
* **carry_initial_prompt** - если True, то значение `initial_prompt` будет добавлено в каждый вызов функции декодирования.
* **initial_prompt** - подсказки дял перевода, например, чтобы помочь распознавать трудные слова.
* **compression_ratio_threshold** - считать распознавание неудачным при значении выше этого параметра.
* **logprob_threshold** - если средняя логарифмическая вероятность ниже этого значения, считать распознавание неудачным

View file

@ -0,0 +1,15 @@
# Плагин обработки файлов: srt
Часть параметров, общих для всех плагинов, описана [здесь](../processing_files.md).
## Параметры плагина
* **text_format** - параметры форматирования текста в обрабатываемом файле.
* * **original_prefix** - символы, которые будут подставлены в начале оригинального текста.
* * **original_postfix** - символы, которые будут подставлены в конце оригинального текста.
* * **translate_prefix** - символы, которые будут подставлены в начале переведенного текста.
* * **translate_postfix** - символы, которые будут подставлены в конце переведенного текста.
* **remove_src_filename_postfix** - удаление из имени исходного файла постфикса. Постфикс может пригодиться, чтобы выбирать по части имени исходные и переведенные файлы.
* **translate_delimiter** - разделитель между оригиналом и переводом.

View file

@ -17,6 +17,10 @@
* **file_txt** - [документация](plugins-file-processing/file_txt.md). Перевод книг в формате txt.
* **file_media_whisper** - [документация](plugins-file-processing/file_media_whisper.md). Распознавание текста в медиа-файлах. Поддерживаются практически все файлы, которые может прочитать ffmpeg.
* **file_srt** - [документация](plugins-file-processing/file_txt.md). Перевод субтитров srt. Можно найти существующие субтитры, или распознать звуковую дорожку через плагин `file_media_whisper`.
## Параметры, которые задаются в основной конфигурации
Некоторые параметры не настраиваются в плагинах, например, пути для исходных / обработанных файлов.

View file

@ -6,67 +6,6 @@
Модули могут работать независимо, используя только функционал сервиса, или вызывать внешние приложения.
Параметры, использование моделей и прочее более подробно указано в документации для каждого плагина отдельно.
## Перевод через REST API
Сервис поддерживает перевод через REST-запросы. Просмотреть API можно здесь по адресу host:port/docs,
по умолчанию здесь: http://127.0.0.1:4990/docs
Базовый запрос для перевода: POST http://127.0.0.1:4990/translate
Минимальное работающее тело запроса выглядит так:
```json
{
"text": "hi"
}
```
Для такого запроса будут автоматически выбраны язык оригинала, язык на который нужно выполнить перевод,
плагин для перевода - все это задается через [параметры](options.md).
Все параметры для перевода можно переопределить вручную.
```json
{
"text": "hi",
"context": "context",
"from_lang": "en",
"to_lang": "ru",
"translator_plugin": "lm_studio"
}
```
В этом запросе:
* **text** - текст для перевода
* **context** - дополнительный контекст. Например, можно указать, чтобы какое-то имя переводилось определенным образом,
или дать понять, какого стиля переводимый текст - художественный, научный, юридический и т. д.
* **from_lang** - двухбуквенный код языка оригинала. Коды можно посмотреть в файле [common.js](../../static/common.js) проекта.
* **to_lang** - двухбуквенный код языка, на который нужно выполнить перевод.
* **translator_plugin** - плагин дял перевода. Он должен быть загружен в сервис.
Также можно выполнить GET запрос и передат ьвсе параметры в него, например
http://127.0.0.1:4990/translate?text=hi&from_lang=en&to_lang=ru&context=context&translator_plugin=lm_studio
Ответ:
```json
{
"result": "Здравствуйте!",
"parts": [
{
"text": "hi",
"translate": "Здравствуйте!",
"paragraph_end": true
}
],
"error": null
}
```
* **result** - результат перевода.
* **parts** - если текст в процессе перевода был разбит на несколько частей, в этом массиве будут представлены
все части, и перевод каждой из частей. Это может пригодиться для понимания того, почему перевод был выполнен
не полностью и подобрать более подходящие параметры разбиения текста на части.
* * **parts[].text** - часть исходного текста, на исходном языке, после преобразований.
* * **parts[].translate** - перевод части текста
* * **parts[].paragraph_end** является ли часть последним предложением в параграфе (необходимо для соединения частей в поле результата `result`).
* **error** - если перевод был выполнен успешно - null, иначе - текст ошибки.
## Список поддерживаемых плагинов
* **kobold_cpp** - [проект](https://github.com/LostRuins/koboldcpp), [документация](plugins-translate/kobold_cpp.md).
@ -96,4 +35,76 @@ http://127.0.0.1:4990/translate?text=hi&from_lang=en&to_lang=ru&context=context&
* **no_translate** - этот плагин не является переводчиком, поэтому не имеет параметров.
Возвращает в ответе переданный в запросе текст без изменений.
Его можно использовать для отладки при работе с параметрами и разработке функционала сервиса.
Его можно использовать для отладки при работе с параметрами и разработке функционала сервиса.
## Перевод через REST API
Сервис поддерживает перевод через REST-запросы. Просмотреть API можно здесь по адресу host:port/docs,
по умолчанию здесь: http://127.0.0.1:4990/docs
Базовый запрос для перевода: POST http://127.0.0.1:4990/translate
Минимальное работающее тело запроса выглядит так:
```json
{
"text": "hi"
}
```
Для такого запроса будут автоматически выбраны язык оригинала, язык на который нужно выполнить перевод,
плагин для перевода - все это задается через [параметры](options.md).
Все параметры для перевода можно переопределить вручную.
```json
{
"text": "hi",
"context": "context",
"from_lang": "en",
"to_lang": "ru",
"translator_plugin": "lm_studio"
}
```
В этом запросе:
* **text** - текст для перевода
* **context** - дополнительный контекст. Например, можно указать, чтобы какое-то имя переводилось определенным образом,
или дать понять, какого стиля переводимый текст - художественный, научный, юридический и т. д.
* **from_lang** - двухбуквенный код языка оригинала. Коды можно посмотреть в файле [common.js](../../static/common.js) проекта.
* **to_lang** - двухбуквенный код языка, на который нужно выполнить перевод.
* **translator_plugin** - плагин дял перевода. Он должен быть загружен в сервис.
Также можно выполнить GET запрос и передать все параметры в него, например
http://127.0.0.1:4990/translate?text=hi&from_lang=en&to_lang=ru&context=context&translator_plugin=lm_studio
Ответ:
```json
{
"result": "Здравствуйте!",
"parts": [
{
"text": "hi",
"translate": "Здравствуйте!",
"paragraph_end": true
}
],
"error": null
}
```
* **result** - результат перевода.
* **parts** - если текст в процессе перевода был разбит на несколько частей, в этом массиве будут представлены
все части, и перевод каждой из частей. Это может пригодиться для понимания того, почему перевод был выполнен
не полностью и подобрать более подходящие параметры разбиения текста на части.
* * **parts[].text** - часть исходного текста, на исходном языке, после преобразований.
* * **parts[].translate** - перевод части текста
* * **parts[].paragraph_end** является ли часть последним предложением в параграфе (необходимо для соединения частей в поле результата `result`).
* **error** - если перевод был выполнен успешно - null, иначе - текст ошибки.
### Запросы sugoi-like
Это запросы в формате [Sugoi-Japanese-Translator](https://github.com/leminhyen2/Sugoi-Japanese-Translator),
нужны для интеграции с другими приложениями, которые поддерживают sugoi-translator - таким образом, указав в настройках
внешнего приложения адрес этого приложения, можно осуществить интеграцию.
Например, sugoi-translator поддерживают:
* [translator-plusplus](https://dreamsavior.net/translator-plusplus/)
* [LunaTranslator](https://docs.lunatranslator.org/en/)
Поддерживается GET и POST варианты запросов, оба этих запроса находятся по пути `/translate/sugoi-like`.

24
main.py
View file

@ -2,12 +2,16 @@ import sys
from contextlib import asynccontextmanager
import uvicorn
from fastapi import FastAPI
from fastapi import FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
from starlette.responses import JSONResponse
from starlette.staticfiles import StaticFiles
from app import dto, log
from app import dto, log, cuda
from app.app_core import AppCore
from app.cuda import cuda_info
APP_VERSION = "0.8.0"
core: AppCore
logger = log.logger()
@ -18,7 +22,7 @@ async def lifespan(fast_api: FastAPI):
logger.info("Starting llm-translate")
app.mount('/', StaticFiles(directory='static', html=True), name='static')
cuda_info()
cuda.cuda_info()
try:
global core
@ -35,6 +39,14 @@ async def lifespan(fast_api: FastAPI):
app = FastAPI(lifespan=lifespan)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
exc_str = f'{exc}'.replace('\n', ' ').replace(' ', ' ')
if core.rest_log_params is not None and core.rest_log_params.translate_validation_errors:
logger.error(f"Error validating request: {request}: {exc_str}")
return JSONResponse(content={"error": exc_str}, status_code=status.HTTP_400_BAD_REQUEST)
@app.get("/translate")
async def translate_get(text: str, from_lang: str = "", to_lang: str = "", context: str = None,
translator_plugin: str = "") -> dto.TranslateResp:
@ -95,7 +107,7 @@ async def translate_post(req: dto.TranslateReq) -> dto.TranslateResp:
async def translate_sugoi_like_post(req: dto.SugoiLikePostReq, from_lang: str = "", to_lang: str = "", context: str = None,
translator_plugin: str = "") -> list[str]:
"""
Translate text. Request and response like a sugoi translator - https://github.com/leminhyen2/Sugoi-Japanese-Translator/tree/main
Translate text. Request and response like a sugoi translator - https://github.com/leminhyen2/Sugoi-Japanese-Translator
This allows the query to be used in integration programs such as a Translator++ - http://dreamsavior.net/docs/translator/
:param req:
@ -132,7 +144,7 @@ async def translate_sugoi_like_post(req: dto.SugoiLikePostReq, from_lang: str =
async def translate_sugoi_like_post(text: str, from_lang: str = "", to_lang: str = "", context: str = None,
translator_plugin: str = "") -> dto.SugoiLikeGetResp:
"""
Translate text. Request and response like a sugoi translator - https://github.com/leminhyen2/Sugoi-Japanese-Translator/tree/main
Translate text. Request and response like a sugoi translator - https://github.com/leminhyen2/Sugoi-Japanese-Translator
This allows the query to be used in integration programs such as a Translator++ - http://dreamsavior.net/docs/translator/
:param str text: text to translate.

View file

@ -72,6 +72,12 @@ manifest = {
"paragraph_join_str": "\n",
},
},
"rest_log_params": {
"translate_req_text": True,
"translate_resp_text": True,
"translate_validation_errors": True,
}
},
}
@ -91,5 +97,6 @@ def start_with_options(core: AppCore, manifest: dict):
core.text_process_params = params.read_text_process_params(manifest)
core.cache_params = params.read_cache_params(manifest)
core.file_processing_params = params.read_file_processing_params(manifest)
core.rest_log_params = params.read_rest_log_params(manifest)
return manifest

View file

@ -39,7 +39,7 @@ def start(core: AppCore):
},
"file_processing": {
"file_epub_translate": (init, file_processing, processed_file_name)
"file_epub_translate": (init, file_processing, processed_file_name, after_processing)
}
}
@ -96,3 +96,7 @@ def processed_file_name(core: AppCore, file_struct: ProcessingFileStruct, req: P
options = core.plugin_options(plugin_name)
return file_processor.file_name_from_template(file_struct=file_struct, req=req, options=options)
def after_processing(core: AppCore) -> None:
pass

View file

@ -36,7 +36,7 @@ def start(core: AppCore):
},
"file_processing": {
"file_epub_translate": (init, file_processing, processed_file_name)
"file_epub_translate": (init, file_processing, processed_file_name, after_processing)
}
}
@ -72,3 +72,7 @@ def processed_file_name(core: AppCore, file_struct: ProcessingFileStruct, req: P
options = core.plugin_options(plugin_name)
return file_processor.file_name_from_template(file_struct=file_struct, req=req, options=options)
def after_processing(core: AppCore) -> None:
pass

View file

@ -0,0 +1,130 @@
import gc
import os
import torch
import whisper
from whisper import utils
from app import file_processor, cuda
from app.app_core import AppCore
from app.dto import ProcessingFileDirReq, ProcessingFileResp, FileProcessingPluginInitInfo, ProcessingFileStruct
plugin_name = os.path.basename(__file__)[:-3] # calculating modname
model: whisper.Whisper | None = None
def start(core: AppCore):
manifest = { # plugin settings
"name": "Subtitle extractor for media files (Whisper)", # name
"version": "1.0", # version
"default_options": {
"enabled": True,
"model": "large-v3-turbo",
"cuda": True,
"cuda_device_index": 0,
"unload_model_after_processing": True,
"translate_after_processing": True,
"temperature": [0.0, 0.2, 0.4, 0.6],
"condition_on_previous_text": False,
"no_speech_threshold": 0.6,
"word_timestamps": True,
"hallucination_silence_threshold": 1,
"carry_initial_prompt": False,
"initial_prompt": "",
"compression_ratio_threshold": 2.4,
"logprob_threshold": -1.0,
"output_file_name_template": "%%source%%.src_sub",
},
"file_processing": {
"file_media_whisper_processing": (init, file_processing, processed_file_name, after_processing)
},
"default_extension_processor": {
"mpeg": True,
"mpg": True,
"mp4": True,
"mp3": True,
"avi": True,
"wav": True,
"mkv": True,
"vob": True,
"ac3": True,
"mpa": True,
"ogg": True,
},
}
return manifest
def start_with_options(core: AppCore, manifest: dict):
pass
def init(core: AppCore) -> FileProcessingPluginInitInfo:
ext = {"mpeg", "mpg", "mp3", "mp4", "avi", "wav", "mkv", "vob", "ac3", "mpa", "ogg"}
return FileProcessingPluginInitInfo(plugin_name=plugin_name, supported_extensions=ext)
def file_processing(core: AppCore, file_struct: ProcessingFileStruct, req: ProcessingFileDirReq) -> ProcessingFileResp:
options = core.plugin_options(plugin_name)
global model
if model is None:
model = whisper.load_model(name=options["model"], device=cuda.get_device_with_gpu_num(options))
temperature: list[float] = options["temperature"]
transcribe = model.transcribe(audio=file_struct.path_file_in(), language=req.from_lang, verbose=False,
temperature=tuple(temperature),
condition_on_previous_text=options["condition_on_previous_text"],
no_speech_threshold=options["no_speech_threshold"],
word_timestamps=options["word_timestamps"],
hallucination_silence_threshold=options["hallucination_silence_threshold"],
carry_initial_prompt=options["carry_initial_prompt"],
initial_prompt=options["initial_prompt"],
compression_ratio_threshold=options["compression_ratio_threshold"],
logprob_threshold=options["logprob_threshold"]
)
if transcribe:
out_file_name = processed_file_name(core=core, file_struct=file_struct, req=req)
writer = utils.get_writer('srt', file_struct.path_out)
writer(transcribe, out_file_name, {})
if options["translate_after_processing"]:
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)
else:
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")
def processed_file_name(core: AppCore, file_struct: ProcessingFileStruct, req: ProcessingFileDirReq) -> str:
options = core.plugin_options(plugin_name)
template: str = options["output_file_name_template"]
return file_processor.file_name_from_predefined_template(file_struct=file_struct, req=req,
template=template, replace_ext="srt")
def translate_after_processing(core: AppCore, req: ProcessingFileDirReq, file_name_ext: str) -> ProcessingFileResp:
return core.process_file(req=req, root=req.directory_out, file_name=file_name_ext)
def after_processing(core: AppCore) -> None:
options = core.plugin_options(plugin_name)
global model
if options["unload_model_after_processing"] and model is not None:
model = None
if options["cuda"]:
torch.cuda.empty_cache()
else:
gc.collect()

View file

@ -0,0 +1,82 @@
import os
import pysrt
from app import file_processor, params
from app.app_core import AppCore
from app.dto import ProcessingFileDirReq, ProcessingFileResp, FileProcessingPluginInitInfo, ProcessingFileStruct
plugin_name = os.path.basename(__file__)[:-3] # calculating modname
def start(core: AppCore):
manifest = { # plugin settings
"name": "Translator for srt files", # name
"version": "1.0", # version
"default_options": {
"enabled": True,
"text_format": {
"original_prefix": "",
"original_postfix": "",
"translate_prefix": "<i>",
"translate_postfix": "</i>",
},
"remove_src_filename_postfix": ".src_sub",
"translate_delimiter": "\n",
"output_file_name_template": {
"preserve_original": "%%source%%.%%from_lang%%_%%to_lang%%",
"without_original": "%%source%%.%%to_lang%%",
},
"default_extension_processor": {
"srt": True
},
},
"file_processing": {
"file_srt_translate": (init, file_processing, processed_file_name, after_processing)
}
}
return manifest
def start_with_options(core: AppCore, manifest: dict):
pass
def init(core: AppCore) -> FileProcessingPluginInitInfo:
return FileProcessingPluginInitInfo(plugin_name=plugin_name, supported_extensions={"srt"})
def file_processing(core: AppCore, file_struct: ProcessingFileStruct, req: ProcessingFileDirReq) -> ProcessingFileResp:
options = core.plugin_options(plugin_name)
text_format = params.read_plugin_file_processing_text_format(options)
subs = pysrt.open(file_struct.path_file_in())
for sub in subs:
text = sub.text
translate_req = req.translate_req(text, "")
translate_text = core.translate(translate_req).result
translate_text_format = text_format.translate_text(translate_text)
if req.preserve_original_text:
original_text_format = text_format.original_text(text)
sub.text = f'{original_text_format}{options["translate_delimiter"]}{translate_text_format}'
out_file_name = processed_file_name(core=core, file_struct=file_struct, req=req)
subs.save(file_struct.path_file_out(out_file_name))
return file_processor.get_processing_file_resp_ok(file_struct=file_struct, file_out=out_file_name)
def processed_file_name(core: AppCore, file_struct: ProcessingFileStruct, req: ProcessingFileDirReq) -> str:
options = core.plugin_options(plugin_name)
src_postfix = options["remove_src_filename_postfix"]
return file_processor.file_name_from_template(file_struct=file_struct, req=req, options=options).replace(src_postfix + ".", ".")
def after_processing(core: AppCore) -> None:
pass

View file

@ -32,7 +32,7 @@ def start(core: AppCore):
},
"file_processing": {
"file_txt_translate": (init, file_processing, processed_file_name)
"file_txt_translate": (init, file_processing, processed_file_name, after_processing)
}
}
@ -84,9 +84,10 @@ def file_processing(core: AppCore, file_struct: ProcessingFileStruct, req: Proce
def processed_file_name(core: AppCore, file_struct: ProcessingFileStruct, req: ProcessingFileDirReq) -> str:
options = core.plugin_options(plugin_name)
ext = "md" if options["markdown_output"] else None
file_name = file_processor.file_name_from_template(file_struct=file_struct, req=req, options=options)
if options["markdown_output"]:
file_name = file_name[:-3] + "md"
return file_processor.file_name_from_template(file_struct=file_struct, req=req, options=options, replace_ext=ext)
return file_name
def after_processing(core: AppCore) -> None:
pass

View file

@ -20,7 +20,7 @@ def start(core: AppCore):
"default_options": {
"custom_url": "http://localhost:1234", #
"prompt": "You are a professional translator. Your task is to translate a text provided below from %%from_lang%% to %%to_lang%%.\n%%context_prompt%%\nINSTRUCTION:Carefully analyze the context. Pay special attention to Terminology, Style, Consistency. Provide only the translation. Do not include any additional information, explanations, notes, or comments in your response. The output should be the pure translated text only.\nTEXT TO TRANSLATE:",
"prompt": "You are a professional translator. Your task is to translate a text (or word) provided below from %%from_lang%% to %%to_lang%%.\n%%context_prompt%%\nINSTRUCTION:Carefully analyze the context. Pay special attention to Terminology, Style, Consistency. Provide only the translation. Do not include any additional information, explanations, notes, or comments in your response. The output should be the pure translated text only.\nTEXT TO TRANSLATE:",
"prompt_postfix": "",
"prompt_no_think_postfix": False,
"use_library_for_request": True,

View file

@ -2,6 +2,8 @@
torch
torchvision
torchaudio
openai-whisper == 20250625
pysrt == 1.1.2
uvicorn == 0.34.2
uvicorn[standard] == 0.34.2