From d6b163cfd01f2d92334c46a4a8bdbdaa97ac62dc Mon Sep 17 00:00:00 2001 From: ardnaxelas Date: Sat, 28 Sep 2024 00:45:20 +0300 Subject: [PATCH 1/3] v1 --- app/db/db_methods.py | 13 ++++++++ app/db/db_types.py | 15 +++++++++ app/main/parser.py | 25 ++++++++++++-- .../presentations/pptx/presentation_pptx.py | 25 ++++++++++++++ .../reports/docx_uploader/docx_uploader.py | 33 +++++++++++++++++++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/app/db/db_methods.py b/app/db/db_methods.py index d80d92db..31bf4c10 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -21,11 +21,24 @@ logs_collection = db.create_collection( 'logs', capped=True, size=5242880) if not db['logs'] else db['logs'] celery_check_collection = db['celery_check'] # collection for mapping celery_task to check +images_collection = db['images'] # коллекция для хранения изображений def get_client(): return client +def save_image_to_db(check_id, image_data, caption): + from app.db.db_types import Image + + image = Image({ + 'check_id': check_id, + 'image_data': image_data, + 'caption': caption + }) + images_collection.insert_one(image.pack()) + + print("check_id----------",check_id) + # Returns user if user was created and None if already exists def add_user(username, password_hash='', is_LTI=False): diff --git a/app/db/db_types.py b/app/db/db_types.py index eeeb26d9..53dd04b1 100644 --- a/app/db/db_types.py +++ b/app/db/db_types.py @@ -145,3 +145,18 @@ def none_to_false(x): is_ended = none_to_true(self.is_ended) # None for old checks => True, True->True, False->False is_failed = none_to_false(self.is_failed) # None for old checks => False, True->True, False->False return {'is_ended': is_ended, 'is_failed': is_failed} + +class Image(PackableWithId): + def __init__(self, dictionary=None): + super().__init__(dictionary) + dictionary = dictionary or {} + self.check_id = dictionary.get('check_id') # Привязка к check_id + self.caption = dictionary.get('caption', '') # Подпись к изображению + self.image_data = dictionary.get('image_data') # Файл изображения в формате bindata + + def pack(self): + package = super().pack() + package['check_id'] = str(self.check_id) + package['caption'] = self.caption + package['image_data'] = self.image_data + return package diff --git a/app/main/parser.py b/app/main/parser.py index 593b8cfd..07754aab 100644 --- a/app/main/parser.py +++ b/app/main/parser.py @@ -8,8 +8,11 @@ from main.reports.md_uploader import MdUploader from utils import convert_to -logger = logging.getLogger('root_logger') +from os.path import basename +from app.db.db_methods import add_check +from app.db.db_types import Check +logger = logging.getLogger('root_logger') def parse(filepath, pdf_filepath): tmp_filepath = filepath.lower() @@ -19,7 +22,17 @@ def parse(filepath, pdf_filepath): if tmp_filepath.endswith(('.odp', '.ppt')): logger.info(f"Презентация {filepath} старого формата. Временно преобразована в pptx для обработки.") new_filepath = convert_to(filepath, target_format='pptx') - file_object = PresentationPPTX(new_filepath) + + presentation = PresentationPPTX(new_filepath) + + check = Check({ + 'filename': basename(new_filepath), + }) + check_id = add_check(23, check) + presentation.extract_images_with_captions(check_id) + file_object = presentation + + elif tmp_filepath.endswith(('.doc', '.odt', '.docx', )): new_filepath = filepath if tmp_filepath.endswith(('.doc', '.odt')): @@ -28,7 +41,13 @@ def parse(filepath, pdf_filepath): docx = DocxUploader() docx.upload(new_filepath, pdf_filepath) + # Создание проверки + check = Check({ + 'filename': basename(new_filepath), + }) + check_id = add_check(23, check) docx.parse() + docx.extract_images_with_captions(check_id) file_object = docx elif tmp_filepath.endswith('.md' ): @@ -54,4 +73,4 @@ def save_to_temp_file(file): temp_file.write(file.read()) temp_file.close() file.seek(0) - return temp_file.name + return temp_file.name \ No newline at end of file diff --git a/app/main/presentations/pptx/presentation_pptx.py b/app/main/presentations/pptx/presentation_pptx.py index dd909f8c..e78babb0 100644 --- a/app/main/presentations/pptx/presentation_pptx.py +++ b/app/main/presentations/pptx/presentation_pptx.py @@ -1,4 +1,5 @@ from pptx import Presentation +from pptx.enum.shapes import MSO_SHAPE_TYPE from .slide_pptx import SlidePPTX from ..presentation_basic import PresentationBasic @@ -17,3 +18,27 @@ def add_slides(self): def __str__(self): return super().__str__() + + def extract_images_with_captions(self, check_id): + from app.db.db_methods import save_image_to_db + + images_with_captions = [] + + for slide in self.prs.slides: + for shape in slide.shapes: + if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: + image_data = shape.image.blob # Бинарные данные изображения + caption = "" + + # Определение подписи. Предполагается, что подпись находится рядом с изображением + if shape.has_text_frame: + caption = shape.text.strip() + else: + # Альтернативный способ: поиск текстового поля рядом с изображением + pass + + # Сохранение изображения и подписи в MongoDB + save_image_to_db(check_id, image_data, caption) + images_with_captions.append({"image_data": image_data, "caption": caption}) + + return images_with_captions \ No newline at end of file diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index ac30dee4..d988fed2 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -242,6 +242,39 @@ def show_chapters(self, work_type): chapters_str += "    " + header["text"] + "
" return chapters_str + def extract_images_with_captions(self, check_id): + from app.db.db_methods import save_image_to_db + images_with_captions = [] + + # Получение всех встроенных фигур (inline_shapes) + for inline_shape in self.file.inline_shapes: + # Проверка, является ли встроенный объект изображением + if inline_shape.type == 3: # Тип 3 соответствует изображению (PICTURE) + # Извлечение бинарных данных изображения + image_stream = inline_shape._inline.graphic.graphicData.pic.blipFill.blip.embed + image_data = self.file.part.related_parts[image_stream].blob # Бинарные данные изображения + + # Инициализация подписи + caption = "" + + # Поиск параграфа, следующего за изображением, для извлечения подписи + for i, paragraph in enumerate(self.file.paragraphs): + # Проверяем, находится ли изображение в параграфе + inline_shape_xml = inline_shape._inline.xml + if inline_shape_xml in paragraph._element.xml: + # Если есть следующий параграф, предположим, что это подпись + if i + 1 < len(self.file.paragraphs): + next_paragraph = self.file.paragraphs[i + 1].text.strip() + if next_paragraph: + caption = next_paragraph + break # Найдено изображение, больше искать не нужно + + # Сохранение изображения и подписи в MongoDB + save_image_to_db(check_id, image_data, caption) + images_with_captions.append({"image_data": image_data, "caption": caption}) + + return images_with_captions + def main(args): file = args.file From 88f199cabec056a2c658a82ea1cf996cd032fa13 Mon Sep 17 00:00:00 2001 From: ardnaxelas Date: Mon, 30 Sep 2024 01:30:16 +0300 Subject: [PATCH 2/3] v1.1 --- app/db/db_methods.py | 2 - app/main/parser.py | 20 ++++++- .../presentations/pptx/presentation_pptx.py | 56 ++++++++++++------- .../reports/docx_uploader/docx_uploader.py | 55 +++++++++--------- 4 files changed, 82 insertions(+), 51 deletions(-) diff --git a/app/db/db_methods.py b/app/db/db_methods.py index 31bf4c10..e63f1760 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -37,8 +37,6 @@ def save_image_to_db(check_id, image_data, caption): }) images_collection.insert_one(image.pack()) - print("check_id----------",check_id) - # Returns user if user was created and None if already exists def add_user(username, password_hash='', is_LTI=False): diff --git a/app/main/parser.py b/app/main/parser.py index 07754aab..fb60a19d 100644 --- a/app/main/parser.py +++ b/app/main/parser.py @@ -15,6 +15,8 @@ logger = logging.getLogger('root_logger') def parse(filepath, pdf_filepath): + from app.db.db_methods import files_info_collection + tmp_filepath = filepath.lower() try: if tmp_filepath.endswith(('.odp', '.ppt', '.pptx')): @@ -28,7 +30,13 @@ def parse(filepath, pdf_filepath): check = Check({ 'filename': basename(new_filepath), }) - check_id = add_check(23, check) + + file_id = 0 + file = files_info_collection.find_one({'name': basename(new_filepath)}) + if file: + file_id = file['_id'] + + check_id = add_check(file_id, check) presentation.extract_images_with_captions(check_id) file_object = presentation @@ -41,11 +49,17 @@ def parse(filepath, pdf_filepath): docx = DocxUploader() docx.upload(new_filepath, pdf_filepath) - # Создание проверки + check = Check({ 'filename': basename(new_filepath), }) - check_id = add_check(23, check) + + file_id = 0 + file = files_info_collection.find_one({'name': basename(new_filepath)}) + if file: + file_id = file['_id'] + + check_id = add_check(file_id, check) docx.parse() docx.extract_images_with_captions(check_id) file_object = docx diff --git a/app/main/presentations/pptx/presentation_pptx.py b/app/main/presentations/pptx/presentation_pptx.py index e78babb0..34f2081a 100644 --- a/app/main/presentations/pptx/presentation_pptx.py +++ b/app/main/presentations/pptx/presentation_pptx.py @@ -1,3 +1,5 @@ +from io import BytesIO + from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE_TYPE @@ -22,23 +24,37 @@ def __str__(self): def extract_images_with_captions(self, check_id): from app.db.db_methods import save_image_to_db - images_with_captions = [] - - for slide in self.prs.slides: - for shape in slide.shapes: - if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: - image_data = shape.image.blob # Бинарные данные изображения - caption = "" - - # Определение подписи. Предполагается, что подпись находится рядом с изображением - if shape.has_text_frame: - caption = shape.text.strip() - else: - # Альтернативный способ: поиск текстового поля рядом с изображением - pass - - # Сохранение изображения и подписи в MongoDB - save_image_to_db(check_id, image_data, caption) - images_with_captions.append({"image_data": image_data, "caption": caption}) - - return images_with_captions \ No newline at end of file + # Проход по каждому слайду в презентации + for slide in self.slides: + image_found = False + image_data = None + caption_text = None + + # Проход по всем шейпам на слайде + for shape in slide.slide.shapes: # Используем slide.slide для доступа к текущему слайду + if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: # Тип 13 соответствует PICTURE + image_found = True + image_part = shape.image # Получаем объект изображения + + # Извлекаем бинарные данные изображения + image_stream = image_part.blob + image_data = BytesIO(image_stream) + print(f"Изображение найдено на слайде {slide.index}") + + # Если мы нашли изображение, ищем следующий непустой текст как подпись + if image_found: + for shape in slide.slide.shapes: + if not shape.has_text_frame: + continue + text = shape.text.strip() + if text: # Находим непустое текстовое поле (предположительно, это подпись) + caption_text = text + # Сохраняем изображение и его подпись + save_image_to_db(check_id, image_data.getvalue(), caption_text) + print(f"Подпись найдена: '{caption_text}' на слайде {slide.index}") + break # Предполагаем, что это подпись к текущему изображению + + # Сброс флага и данных изображения для следующего цикла + image_found = False + image_data = None + caption_text = None diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index d988fed2..7c462207 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -244,36 +244,39 @@ def show_chapters(self, work_type): def extract_images_with_captions(self, check_id): from app.db.db_methods import save_image_to_db - images_with_captions = [] - # Получение всех встроенных фигур (inline_shapes) - for inline_shape in self.file.inline_shapes: - # Проверка, является ли встроенный объект изображением - if inline_shape.type == 3: # Тип 3 соответствует изображению (PICTURE) - # Извлечение бинарных данных изображения - image_stream = inline_shape._inline.graphic.graphicData.pic.blipFill.blip.embed - image_data = self.file.part.related_parts[image_stream].blob # Бинарные данные изображения + image_found = False + image_data = None - # Инициализация подписи - caption = "" + # Проход по всем параграфам документа + for i, paragraph in enumerate(self.file.paragraphs): + # Проверяем, есть ли в параграфе встроенные объекты + for run in paragraph.runs: + if "graphic" in run._element.xml: # Это может быть изображение + image_found = True - # Поиск параграфа, следующего за изображением, для извлечения подписи - for i, paragraph in enumerate(self.file.paragraphs): - # Проверяем, находится ли изображение в параграфе - inline_shape_xml = inline_shape._inline.xml - if inline_shape_xml in paragraph._element.xml: - # Если есть следующий параграф, предположим, что это подпись - if i + 1 < len(self.file.paragraphs): - next_paragraph = self.file.paragraphs[i + 1].text.strip() - if next_paragraph: - caption = next_paragraph - break # Найдено изображение, больше искать не нужно + # Извлечение бинарных данных изображения + image_streams = run._element.findall('.//a:blip', namespaces={ + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) + for image_stream in image_streams: + embed_id = image_stream.get( + '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') + if embed_id: + image_data = self.file.part.related_parts[embed_id].blob - # Сохранение изображения и подписи в MongoDB - save_image_to_db(check_id, image_data, caption) - images_with_captions.append({"image_data": image_data, "caption": caption}) - - return images_with_captions + # Если мы уже нашли изображение, ищем следующий непустой параграф для подписи + if image_found: + # Переход к следующему параграфу + next_paragraph_index = i + 1 + while next_paragraph_index < len(self.file.paragraphs): + next_paragraph_text = self.file.paragraphs[next_paragraph_index].text.strip() + if next_paragraph_text: # Находим непустой параграф + # Сохраняем изображение и его подпись + save_image_to_db(check_id, image_data, next_paragraph_text) + break + next_paragraph_index += 1 + image_found = False # Сброс флага, чтобы искать следующее изображение + image_data = None # Очистка данных изображения def main(args): From 5ecde02ca5b19355a9065b750b8c7253c4fb9de1 Mon Sep 17 00:00:00 2001 From: ardnaxelas Date: Mon, 30 Sep 2024 11:42:50 +0300 Subject: [PATCH 3/3] v2: edit cases --- app/db/db_methods.py | 1 + .../presentations/pptx/presentation_pptx.py | 6 ++-- .../reports/docx_uploader/docx_uploader.py | 34 ++++++++++++++----- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/db/db_methods.py b/app/db/db_methods.py index e63f1760..841085b4 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -36,6 +36,7 @@ def save_image_to_db(check_id, image_data, caption): 'caption': caption }) images_collection.insert_one(image.pack()) + print(str(check_id) + " " + str(caption)) # Returns user if user was created and None if already exists diff --git a/app/main/presentations/pptx/presentation_pptx.py b/app/main/presentations/pptx/presentation_pptx.py index 34f2081a..a8b8581f 100644 --- a/app/main/presentations/pptx/presentation_pptx.py +++ b/app/main/presentations/pptx/presentation_pptx.py @@ -30,16 +30,15 @@ def extract_images_with_captions(self, check_id): image_data = None caption_text = None - # Проход по всем шейпам на слайде + # Проход по всем фигурам на слайде for shape in slide.slide.shapes: # Используем slide.slide для доступа к текущему слайду - if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: # Тип 13 соответствует PICTURE + if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: image_found = True image_part = shape.image # Получаем объект изображения # Извлекаем бинарные данные изображения image_stream = image_part.blob image_data = BytesIO(image_stream) - print(f"Изображение найдено на слайде {slide.index}") # Если мы нашли изображение, ищем следующий непустой текст как подпись if image_found: @@ -51,7 +50,6 @@ def extract_images_with_captions(self, check_id): caption_text = text # Сохраняем изображение и его подпись save_image_to_db(check_id, image_data.getvalue(), caption_text) - print(f"Подпись найдена: '{caption_text}' на слайде {slide.index}") break # Предполагаем, что это подпись к текущему изображению # Сброс флага и данных изображения для следующего цикла diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index 7c462207..be65067d 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -252,8 +252,7 @@ def extract_images_with_captions(self, check_id): for i, paragraph in enumerate(self.file.paragraphs): # Проверяем, есть ли в параграфе встроенные объекты for run in paragraph.runs: - if "graphic" in run._element.xml: # Это может быть изображение - image_found = True + if "graphic" in run._element.xml: # может быть изображение # Извлечение бинарных данных изображения image_streams = run._element.findall('.//a:blip', namespaces={ @@ -262,19 +261,36 @@ def extract_images_with_captions(self, check_id): embed_id = image_stream.get( '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') if embed_id: + image_found = True image_data = self.file.part.related_parts[embed_id].blob # Если мы уже нашли изображение, ищем следующий непустой параграф для подписи if image_found: # Переход к следующему параграфу next_paragraph_index = i + 1 - while next_paragraph_index < len(self.file.paragraphs): - next_paragraph_text = self.file.paragraphs[next_paragraph_index].text.strip() - if next_paragraph_text: # Находим непустой параграф - # Сохраняем изображение и его подпись - save_image_to_db(check_id, image_data, next_paragraph_text) - break - next_paragraph_index += 1 + + # Проверяем, есть ли следующий параграф + if next_paragraph_index < len(self.file.paragraphs): + while next_paragraph_index < len(self.file.paragraphs): + next_paragraph = self.file.paragraphs[next_paragraph_index] + next_paragraph_text = next_paragraph.text.strip() + + # Проверка, не содержит ли следующий параграф также изображение + contains_image = any( + "graphic" in run._element.xml for run in next_paragraph.runs + ) + + # Если параграф не содержит изображения и текст не пуст, то это подпись + if not contains_image and next_paragraph_text: + # Сохраняем изображение и его подпись + save_image_to_db(check_id, image_data, next_paragraph_text) + break + else: + save_image_to_db(check_id, image_data, "picture without caption") + break + else: + save_image_to_db(check_id, image_data, "picture without caption") + image_found = False # Сброс флага, чтобы искать следующее изображение image_data = None # Очистка данных изображения