Редактировать file_service.py

This commit is contained in:
Markov Andrey
2026-06-30 21:25:32 +00:00
parent e4dc5611a6
commit 3e2f48fc1b

View File

@@ -4,6 +4,7 @@
хирургическая замена в DOCX, создание DOCX из текста, хирургическая замена в DOCX, создание DOCX из текста,
транскрипция аудио через SaluteSpeech. транскрипция аудио через SaluteSpeech.
Все методы, которые могут быть асинхронными, теперь async. Все методы, которые могут быть асинхронными, теперь async.
Добавлено экранирование путей через shlex.quote.
""" """
import asyncio import asyncio
@@ -13,6 +14,7 @@ import logging
import os import os
import re import re
import shutil import shutil
import shlex
import subprocess import subprocess
import tempfile import tempfile
import zipfile import zipfile
@@ -81,8 +83,6 @@ class FileService:
# ---------- Синхронные методы извлечения (вызываются через to_thread) ---------- # ---------- Синхронные методы извлечения (вызываются через to_thread) ----------
def extract_text_from_docx(self, file_path: str) -> str: def extract_text_from_docx(self, file_path: str) -> str:
"""Извлекает текст из DOCX, включая колонтитулы, таблицы и сноски.""" """Извлекает текст из DOCX, включая колонтитулы, таблицы и сноски."""
# ... (код без изменений, полная версия была ранее)
# Я приведу полный код этого метода, чтобы сохранить целостность.
try: try:
doc = DocxReader(file_path) doc = DocxReader(file_path)
full_text = [] full_text = []
@@ -235,6 +235,7 @@ class FileService:
""" """
Асинхронно транскрибирует аудиофайл через SaluteSpeech API. Асинхронно транскрибирует аудиофайл через SaluteSpeech API.
Все блокирующие вызовы (subprocess.run, requests.post) обёрнуты в asyncio.to_thread. Все блокирующие вызовы (subprocess.run, requests.post) обёрнуты в asyncio.to_thread.
Пути экранируются через shlex.quote.
""" """
import uuid import uuid
@@ -247,14 +248,17 @@ class FileService:
if not shutil.which('ffmpeg') and not os.path.exists(ffmpeg_bin): if not shutil.which('ffmpeg') and not os.path.exists(ffmpeg_bin):
return "[Ошибка: на сервере не установлен ffmpeg]" return "[Ошибка: на сервере не установлен ffmpeg]"
# Экранируем пути
safe_file_path = shlex.quote(file_path)
pcm_output = file_path + ".raw" pcm_output = file_path + ".raw"
safe_pcm_output = shlex.quote(pcm_output)
try: try:
# Конвертация ffmpeg # Конвертация ffmpeg
def _run_ffmpeg(): def _run_ffmpeg():
cmd_ffmpeg = [ cmd_ffmpeg = [
ffmpeg_bin, '-y', '-i', file_path, ffmpeg_bin, '-y', '-i', safe_file_path,
'-ac', '1', '-ar', '16000', '-f', 's16le', '-filter:a', 'volume=2.0', pcm_output '-ac', '1', '-ar', '16000', '-f', 's16le', '-filter:a', 'volume=2.0', safe_pcm_output
] ]
return subprocess.run(cmd_ffmpeg, capture_output=True) return subprocess.run(cmd_ffmpeg, capture_output=True)
@@ -276,7 +280,7 @@ class FileService:
'-H', f'Authorization: Bearer {token}', '-H', f'Authorization: Bearer {token}',
'-H', 'Content-Type: audio/x-pcm;bit=16;rate=16000', '-H', 'Content-Type: audio/x-pcm;bit=16;rate=16000',
'-H', f'RqUID: {str(uuid.uuid4())}', '-H', f'RqUID: {str(uuid.uuid4())}',
'--data-binary', f'@{pcm_output}' '--data-binary', f'@{safe_pcm_output}'
] ]
return subprocess.run(curl_cmd, capture_output=True, text=True) return subprocess.run(curl_cmd, capture_output=True, text=True)
@@ -326,7 +330,6 @@ class FileService:
try: try:
def _sync_request(): def _sync_request():
return requests.post(url, headers=headers, data=payload, verify=False) return requests.post(url, headers=headers, data=payload, verify=False)
# ОБЁРТКА: синхронный запрос выполняется в отдельном потоке
response = await asyncio.to_thread(_sync_request) response = await asyncio.to_thread(_sync_request)
token = response.json().get('access_token') token = response.json().get('access_token')
logger.info("SaluteSpeech token получен") logger.info("SaluteSpeech token получен")
@@ -479,7 +482,7 @@ class FileService:
return text, count return text, count
# ============================================================ # ============================================================
# ОСНОВНОЙ МЕТОД теперь асинхронный! # ОСНОВНОЙ МЕТОД
# ============================================================ # ============================================================
async def process_any_file(self, file_path: str) -> Tuple[str, int]: async def process_any_file(self, file_path: str) -> Tuple[str, int]: