commit 0810f20173e1deb48786996e72430d98934f77b4
Author: Egor Deev <67710823+IGlek@users.noreply.github.com>
Date: Tue May 6 17:29:20 2025 +0300
v. 0.1
diff --git a/api_app/__init__.py b/api_app/__init__.py
new file mode 100644
index 0000000..2126d76
--- /dev/null
+++ b/api_app/__init__.py
@@ -0,0 +1,2 @@
+# api_project/api_app/__init__.py
+# Пустой файл инициализации для Python-пакета
diff --git a/api_app/grpc_client/__init__.py b/api_app/grpc_client/__init__.py
new file mode 100644
index 0000000..48f03f0
--- /dev/null
+++ b/api_app/grpc_client/__init__.py
@@ -0,0 +1,2 @@
+# api_project/api_app/grpc_client/__init__.py
+# Пустой файл инициализации для Python-пакета
diff --git a/api_app/grpc_client/client.py b/api_app/grpc_client/client.py
new file mode 100644
index 0000000..2b1d7fe
--- /dev/null
+++ b/api_app/grpc_client/client.py
@@ -0,0 +1,90 @@
+# api_project/api_app/grpc_client/client.py
+import grpc
+import os
+import sys
+
+# Добавляем путь для импорта сгенерированных протофайлов
+current_dir = os.path.dirname(os.path.abspath(__file__))
+proto_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(current_dir))), 'proto')
+sys.path.append(proto_dir)
+
+# Пробуем импортировать сгенерированные протофайлы
+# Если не получится, используем заглушки
+try:
+ import text_service_pb2
+ import text_service_pb2_grpc
+ print("Успешно импортированы сгенерированные proto файлы")
+except ImportError:
+ print("Не удалось импортировать сгенерированные proto файлы, используем заглушки")
+
+ # Создаем заглушки
+ class TextRequest:
+ def __init__(self, text):
+ self.text = text
+
+ class TextResponse:
+ def __init__(self, processed_text, success, error):
+ self.processed_text = processed_text
+ self.success = success
+ self.error = error
+
+ class TextProcessorStub:
+ def __init__(self, channel):
+ self.channel = channel
+
+ def ProcessText(self, request):
+ # Эмулируем ответ от gRPC сервера
+ return TextResponse(
+ processed_text=f"ЗАГЛУШКА ОБРАБОТКИ ТЕКСТА: {request.text}",
+ success=True,
+ error=""
+ )
+
+ # Создаем модуль заглушки
+ class text_service_pb2:
+ TextRequest = TextRequest
+ TextResponse = TextResponse
+
+ class text_service_pb2_grpc:
+ TextProcessorStub = TextProcessorStub
+
+def send_to_grpc_server(text: str) -> dict:
+ """
+ Отправляет текст на gRPC сервер для обработки
+
+ Args:
+ text: Текст для обработки
+
+ Returns:
+ dict: Результат обработки
+ """
+ try:
+ # Создаем соединение с сервером
+ # Для заглушки это не обязательно, но оставим для совместимости
+ try:
+ channel = grpc.insecure_channel('localhost:50051')
+ except NameError:
+ # Если grpc не импортирован, используем заглушку
+ channel = "dummy_channel"
+
+ # Создаем клиент
+ stub = text_service_pb2_grpc.TextProcessorStub(channel)
+
+ # Создаем запрос
+ request = text_service_pb2.TextRequest(text=text)
+
+ # Отправляем запрос
+ response = stub.ProcessText(request)
+
+ # Возвращаем результат
+ return {
+ 'processed_text': response.processed_text,
+ 'success': response.success,
+ 'error': response.error if hasattr(response, 'error') and response.error else None
+ }
+ except Exception as e:
+ return {
+ 'processed_text': None,
+ 'success': False,
+ 'error': str(e)
+ }
diff --git a/api_app/migrations/0001_initial.py b/api_app/migrations/0001_initial.py
new file mode 100644
index 0000000..1cca455
--- /dev/null
+++ b/api_app/migrations/0001_initial.py
@@ -0,0 +1,33 @@
+# Generated by Django 5.1.3 on 2025-05-06 12:35
+
+import api_app.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AudioFile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('file', models.FileField(upload_to=api_app.models.get_file_path)),
+ ('uploaded_at', models.DateTimeField(auto_now_add=True)),
+ ('processed_text', models.TextField(blank=True, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='DocumentFile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('file', models.FileField(upload_to=api_app.models.get_file_path)),
+ ('uploaded_at', models.DateTimeField(auto_now_add=True)),
+ ('processed_text', models.TextField(blank=True, null=True)),
+ ],
+ ),
+ ]
diff --git a/api_app/migrations/__init__.py b/api_app/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/api_app/models.py b/api_app/models.py
new file mode 100644
index 0000000..74f9482
--- /dev/null
+++ b/api_app/models.py
@@ -0,0 +1,25 @@
+# api_project/api_app/models.py
+from django.db import models
+import uuid
+import os
+
+def get_file_path(instance, filename):
+ ext = filename.split('.')[-1]
+ filename = f"{uuid.uuid4()}.{ext}"
+ return os.path.join('uploads', filename)
+
+class AudioFile(models.Model):
+ file = models.FileField(upload_to=get_file_path)
+ uploaded_at = models.DateTimeField(auto_now_add=True)
+ processed_text = models.TextField(blank=True, null=True)
+
+ def __str__(self):
+ return f"Audio {self.id} - {self.uploaded_at}"
+
+class DocumentFile(models.Model):
+ file = models.FileField(upload_to=get_file_path)
+ uploaded_at = models.DateTimeField(auto_now_add=True)
+ processed_text = models.TextField(blank=True, null=True)
+
+ def __str__(self):
+ return f"Document {self.id} - {self.uploaded_at}"
diff --git a/api_app/services/__init__.py b/api_app/services/__init__.py
new file mode 100644
index 0000000..ae36dba
--- /dev/null
+++ b/api_app/services/__init__.py
@@ -0,0 +1,2 @@
+# api_project/api_app/services/__init__.py
+# Пустой файл инициализации для Python-пакета
diff --git a/api_app/services/scan.py b/api_app/services/scan.py
new file mode 100644
index 0000000..513ed6f
--- /dev/null
+++ b/api_app/services/scan.py
@@ -0,0 +1,66 @@
+import pdfplumber
+import docx
+import csv
+import os
+from io import StringIO, BytesIO
+from PIL import Image
+import base64
+
+
+def extract_text_tables(file_path: str) -> str:
+ result = ""
+ if file_path.endswith(".pdf"):
+ with pdfplumber.open(file_path) as pdf:
+ for page in pdf.pages:
+ text = page.extract_text()
+ if text:
+ result += "
" + text.replace("\n", "
") + "
"
+
+ tables = page.extract_tables()
+ if tables:
+ for table in tables:
+ csv_output = StringIO()
+ csv_writer = csv.writer(csv_output)
+ csv_writer.writerows(table)
+ result += f"{csv_output.getvalue()}"
+
+ # Извлечение изображений
+ if page.images:
+ for img in page.images:
+ img_data = img["stream"].get_data()
+ encoded_img = base64.b64encode(img_data).decode("utf-8")
+ result += f'
'
+
+ elif file_path.endswith(".docx"):
+ doc = docx.Document(file_path)
+
+ text_data = []
+ table_data = []
+ image_data = []
+
+ for para in doc.paragraphs:
+ if para.text.strip():
+ text_data.append(f"{para.text}
")
+
+ for table in doc.tables:
+ csv_output = StringIO()
+ csv_writer = csv.writer(csv_output)
+ for row in table.rows:
+ csv_writer.writerow([cell.text.strip() for cell in row.cells])
+ table_data.append(f"{csv_output.getvalue()}")
+
+ # Извлечение изображений
+ for rel in doc.part.rels:
+ if "image" in doc.part.rels[rel].target_ref:
+ image_data_blob = doc.part.rels[rel].target_part.blob
+ encoded_img = base64.b64encode(image_data_blob).decode("utf-8")
+ image_data.append(f'
')
+
+ if text_data:
+ result += "".join(text_data)
+ if table_data:
+ result += "".join(table_data)
+ if image_data:
+ result += "".join(image_data)
+
+ return result
diff --git a/api_app/services/vosk_recognizer.py b/api_app/services/vosk_recognizer.py
new file mode 100644
index 0000000..da99f44
--- /dev/null
+++ b/api_app/services/vosk_recognizer.py
@@ -0,0 +1,51 @@
+import os, wave, vosk, ffmpeg
+
+MODEL_PATH = r"models/vosk-model-small-ru-0.22"
+FFMPEG_PATH = r"models/ffmpeg/bin/ffmpeg.exe"
+
+def convert_audio_to_wav(input_file, output_file, FFMPEG_PATH):
+ try:
+ (
+ ffmpeg
+ .input(input_file)
+ .output(output_file, format='wav', acodec='pcm_s16le', ar='16000', ac=1,
+ af='acompressor,afftdn,dynaudnorm,aresample=16000') # 16kHz для Vosk
+ .global_args('-loglevel', 'quiet')
+ .run(cmd=FFMPEG_PATH, overwrite_output=True)
+ )
+ print(f"Конвертация завершена: {output_file}")
+ except ffmpeg.Error as e:
+ print("Ошибка при конвертации:", e.stderr.decode())
+
+
+vosk.SetLogLevel(-1)
+
+def recognize_speech(audio_path) -> str:
+ if not os.path.exists(MODEL_PATH):
+ print("Ошибка: Модель не найдена!")
+ return ""
+
+ model = vosk.Model(MODEL_PATH)
+
+ if audio_path.split('.')[-1] != "wav":
+ convert.convert_audio_to_wav(audio_path, "audio.wav", FFMPEG_PATH)
+ audio_path = "audio.wav"
+ else:
+ with wave.open(audio_path, "rb") as wf:
+ if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getframerate() != 16000:
+ convert.convert_audio_to_wav(audio_path, "audio.wav", FFMPEG_PATH)
+ audio_path = "audio.wav"
+
+
+ with wave.open(audio_path, "rb") as wf: # использование vosk
+ recognizer = vosk.KaldiRecognizer(model, wf.getframerate())
+ while True:
+ data = wf.readframes(3200)
+ if not data:
+ break
+ recognizer.AcceptWaveform(data)
+
+ if audio_path == "audio.wav":
+ os.remove(audio_path)
+
+ return recognizer.FinalResult().split(": \"")[-1][:-3]
diff --git a/api_app/urls.py b/api_app/urls.py
new file mode 100644
index 0000000..772fd41
--- /dev/null
+++ b/api_app/urls.py
@@ -0,0 +1,8 @@
+# api_project/api_app/urls.py
+from django.urls import path
+from .views import AudioToTextView, DocumentToTextView
+
+urlpatterns = [
+ path('audio-to-text/', AudioToTextView.as_view(), name='audio-to-text'),
+ path('document-to-text/', DocumentToTextView.as_view(), name='document-to-text'),
+]
diff --git a/api_app/views.py b/api_app/views.py
new file mode 100644
index 0000000..eb820ad
--- /dev/null
+++ b/api_app/views.py
@@ -0,0 +1,77 @@
+# api_project/api_app/views.py
+import os
+from rest_framework import status
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework.parsers import MultiPartParser, FormParser
+from django.conf import settings
+
+from .models import AudioFile, DocumentFile
+from .services.vosk_recognizer import recognize_speech
+from .services.scan import extract_text_tables
+from .grpc_client.client import send_to_grpc_server
+
+class AudioToTextView(APIView):
+ parser_classes = (MultiPartParser, FormParser)
+
+ def post(self, request, *args, **kwargs):
+ audio_file = request.FILES.get('audio')
+
+ if not audio_file:
+ return Response({'error': 'Нет аудио файла'}, status=status.HTTP_400_BAD_REQUEST)
+
+ audio_model = AudioFile(file=audio_file)
+ audio_model.save()
+
+ try:
+ file_path = os.path.join(settings.MEDIA_ROOT, audio_model.file.name)
+
+ text = recognize_speech(file_path)
+
+ audio_model.processed_text = text
+ audio_model.save()
+
+ grpc_response = send_to_grpc_server(text)
+
+ return Response({
+ 'text': text,
+ 'grpc_response': grpc_response
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+class DocumentToTextView(APIView):
+ parser_classes = (MultiPartParser, FormParser)
+
+ def post(self, request, *args, **kwargs):
+ document_file = request.FILES.get('document')
+
+ if not document_file:
+ return Response({'error': 'Нет документа'}, status=status.HTTP_400_BAD_REQUEST)
+
+ file_ext = os.path.splitext(document_file.name)[1].lower()
+ if file_ext not in ['.pdf', '.docx']:
+ return Response({'error': 'Поддерживаются только PDF и DOCX файлы'},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ doc_model = DocumentFile(file=document_file)
+ doc_model.save()
+
+ try:
+ file_path = os.path.join(settings.MEDIA_ROOT, doc_model.file.name)
+
+ text = extract_text_tables(file_path)
+
+ doc_model.processed_text = text
+ doc_model.save()
+
+ grpc_response = send_to_grpc_server(text)
+
+ return Response({
+ 'text': text,
+ 'grpc_response': grpc_response
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
\ No newline at end of file
diff --git a/api_project/__init__.py b/api_project/__init__.py
new file mode 100644
index 0000000..3f63b69
--- /dev/null
+++ b/api_project/__init__.py
@@ -0,0 +1,2 @@
+# api_project/api_project/__init__.py
+# Пустой файл инициализации для Python-пакета
diff --git a/api_project/settings.py b/api_project/settings.py
new file mode 100644
index 0000000..58e6a56
--- /dev/null
+++ b/api_project/settings.py
@@ -0,0 +1,69 @@
+import os
+from pathlib import Path
+
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+SECRET_KEY = 'django-insecure-)+yykzv8cr7dbc38g2#x(8*ifs@+-f_fyan9!c%mmxg1$ekztq'
+
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ # 'django.contrib.staticfiles',
+ 'rest_framework', # Добавляем DRF
+ 'api_app', # Наше API приложение
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'api_project.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'api_project.wsgi.application'
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+# Путь для загрузки файлов
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+# Настройки для REST Framework
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': [
+ 'rest_framework.permissions.AllowAny', # Для тестирования, в продакшне лучше ограничить
+ ]
+}
diff --git a/api_project/urls.py b/api_project/urls.py
new file mode 100644
index 0000000..eae7c34
--- /dev/null
+++ b/api_project/urls.py
@@ -0,0 +1,13 @@
+# api_project/api_project/urls.py
+from django.contrib import admin
+from django.urls import path, include
+from django.conf import settings
+from django.conf.urls.static import static
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path('api/', include('api_app.urls')),
+]
+
+if settings.DEBUG:
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/api_project/wsgi.py b/api_project/wsgi.py
new file mode 100644
index 0000000..47d233a
--- /dev/null
+++ b/api_project/wsgi.py
@@ -0,0 +1,14 @@
+# api_project/api_project/wsgi.py
+"""
+WSGI config for api_project project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api_project.settings')
+
+application = get_wsgi_application()
diff --git a/db.sqlite3 b/db.sqlite3
new file mode 100644
index 0000000..03b0f53
Binary files /dev/null and b/db.sqlite3 differ
diff --git a/manage.py b/manage.py
new file mode 100644
index 0000000..3c82615
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api_project.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/media/uploads/audio.ogg b/media/uploads/audio.ogg
new file mode 100644
index 0000000..916885e
Binary files /dev/null and b/media/uploads/audio.ogg differ
diff --git a/media/uploads/Деев Е.В. Резюме.pdf b/media/uploads/Деев Е.В. Резюме.pdf
new file mode 100644
index 0000000..386f7d7
Binary files /dev/null and b/media/uploads/Деев Е.В. Резюме.pdf differ
diff --git a/proto/text_service.proto b/proto/text_service.proto
new file mode 100644
index 0000000..b69f11d
--- /dev/null
+++ b/proto/text_service.proto
@@ -0,0 +1,18 @@
+// proto/text_service.proto
+syntax = "proto3";
+
+package text_service;
+
+service TextProcessor {
+ rpc ProcessText(TextRequest) returns (TextResponse) {}
+}
+
+message TextRequest {
+ string text = 1;
+}
+
+message TextResponse {
+ string processed_text = 1;
+ bool success = 2;
+ string error = 3;
+}
diff --git a/proto/text_service_pb2.py b/proto/text_service_pb2.py
new file mode 100644
index 0000000..6311ce1
--- /dev/null
+++ b/proto/text_service_pb2.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: text_service.proto
+# Protobuf Python Version: 5.29.0
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 5,
+ 29,
+ 0,
+ '',
+ 'text_service.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12text_service.proto\x12\x0ctext_service\"\x1b\n\x0bTextRequest\x12\x0c\n\x04text\x18\x01 \x01(\t\"F\n\x0cTextResponse\x12\x16\n\x0eprocessed_text\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t2W\n\rTextProcessor\x12\x46\n\x0bProcessText\x12\x19.text_service.TextRequest\x1a\x1a.text_service.TextResponse\"\x00\x62\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'text_service_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_TEXTREQUEST']._serialized_start=36
+ _globals['_TEXTREQUEST']._serialized_end=63
+ _globals['_TEXTRESPONSE']._serialized_start=65
+ _globals['_TEXTRESPONSE']._serialized_end=135
+ _globals['_TEXTPROCESSOR']._serialized_start=137
+ _globals['_TEXTPROCESSOR']._serialized_end=224
+# @@protoc_insertion_point(module_scope)
diff --git a/proto/text_service_pb2_grpc.py b/proto/text_service_pb2_grpc.py
new file mode 100644
index 0000000..4f08cdc
--- /dev/null
+++ b/proto/text_service_pb2_grpc.py
@@ -0,0 +1,97 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+import text_service_pb2 as text__service__pb2
+
+GRPC_GENERATED_VERSION = '1.71.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + f' but the generated code in text_service_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
+
+
+class TextProcessorStub(object):
+ """Missing associated documentation comment in .proto file."""
+
+ def __init__(self, channel):
+ """Constructor.
+
+ Args:
+ channel: A grpc.Channel.
+ """
+ self.ProcessText = channel.unary_unary(
+ '/text_service.TextProcessor/ProcessText',
+ request_serializer=text__service__pb2.TextRequest.SerializeToString,
+ response_deserializer=text__service__pb2.TextResponse.FromString,
+ _registered_method=True)
+
+
+class TextProcessorServicer(object):
+ """Missing associated documentation comment in .proto file."""
+
+ def ProcessText(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+
+def add_TextProcessorServicer_to_server(servicer, server):
+ rpc_method_handlers = {
+ 'ProcessText': grpc.unary_unary_rpc_method_handler(
+ servicer.ProcessText,
+ request_deserializer=text__service__pb2.TextRequest.FromString,
+ response_serializer=text__service__pb2.TextResponse.SerializeToString,
+ ),
+ }
+ generic_handler = grpc.method_handlers_generic_handler(
+ 'text_service.TextProcessor', rpc_method_handlers)
+ server.add_generic_rpc_handlers((generic_handler,))
+ server.add_registered_method_handlers('text_service.TextProcessor', rpc_method_handlers)
+
+
+ # This class is part of an EXPERIMENTAL API.
+class TextProcessor(object):
+ """Missing associated documentation comment in .proto file."""
+
+ @staticmethod
+ def ProcessText(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/text_service.TextProcessor/ProcessText',
+ text__service__pb2.TextRequest.SerializeToString,
+ text__service__pb2.TextResponse.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..32690df
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,9 @@
+django==4.2.6
+djangorestframework==3.14.0
+grpcio==1.58.0
+grpcio-tools==1.58.0
+pdfplumber==0.10.2
+python-docx==0.8.11
+Pillow==10.0.1
+vosk==0.3.45
+ffmpeg-python==0.2.0
diff --git a/тесты.txt b/тесты.txt
new file mode 100644
index 0000000..c3da715
--- /dev/null
+++ b/тесты.txt
@@ -0,0 +1,13 @@
+python manage.py runserver
+
+------------------------------------------
+
+Тест расширения для сканирования:
+
+curl -X POST -F "document=@C:\Users\egord\Desktop\API EasyAccess\api_project\media\uploads\Деев Е.В. Резюме.pdf" http://localhost:8000/api/document-to-text/
+
+------------------------------------------
+
+Тест для аудио транскрипции:
+
+curl -X POST -F audio=@"C:\Users\egord\Desktop\API EasyAccess\api_project\media\uploads\audio.ogg" http://localhost:8000/api/audio-to-text/
\ No newline at end of file