Редактировать file_service.py
This commit is contained in:
@@ -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]:
|
||||||
|
|||||||
Reference in New Issue
Block a user