diff --git a/openav/api/preprocess_audio.py b/openav/api/preprocess_audio.py index 0d16688..a4e61f3 100644 --- a/openav/api/preprocess_audio.py +++ b/openav/api/preprocess_audio.py @@ -37,7 +37,7 @@ from openav import rsrs # Ресурсы библиотеки from openav.modules.core.logging import ARG_PATH_TO_LOGS -from openav.modules.lab.audio import SAMPLING_RATE_MS, PAD_MODE_MS, DPI, COLOR_GRADIENTS +from openav.modules.lab.audio import SAMPLING_RATE_MS, PAD_MODE_MS, DPI, COLOR_GRADIENTS, EXT_AUDIO # ###################################################################################################################### @@ -74,7 +74,7 @@ class RunPreprocessAudio(MessagesPreprocessAudio): def __post_init__(self): super().__post_init__() # Выполнение конструктора из суперкласса - self._all_layer_in_yaml = 17 # Общее количество настроек в конфигурационном файле + self._all_layer_in_yaml = 18 # Общее количество настроек в конфигурационном файле # Регистратор логирования с указанным именем self._logger_run_preprocess_audio: logging.Logger = logging.getLogger(__class__.__name__) @@ -166,8 +166,15 @@ def _valid_yaml_config(self, config: Dict[str, Union[str, bool, int, float]], ou # 1. Скрытие метаданных # 2. Скрытие версий установленных библиотек # 3. Включение установки отступов с обеих сторон относительно центра аудиодорожки - # 4. Сохранение сырых данных мел-спектрограммы в формате .npy - if key == "hide_metadata" or key == "hide_libs_vers" or key == "center" or key == "save_raw_data": + # 4. Очистка директории для сохранения аудиоданных после предобработки + # 5. Сохранение сырых данных мел-спектрограммы в формате .npy + if ( + key == "hide_metadata" + or key == "hide_libs_vers" + or key == "center" + or key == "clear_dir_audio" + or key == "save_raw_data" + ): # Проверка значения if type(val) is not bool: continue @@ -193,12 +200,13 @@ def _valid_yaml_config(self, config: Dict[str, Union[str, bool, int, float]], ou # Проход по всем подразделам текущего раздела for v in val: # Проверка значения - if type(v) is not str or not v: + if type(v) is not str or not v or (v in EXT_AUDIO) is False: + curr_valid_layer_2 += 100 continue curr_valid_layer_2 += 1 - if curr_valid_layer_2 > 0: + if curr_valid_layer_2 <= len(EXT_AUDIO): curr_valid_layer += 1 # 1. Путь к директории набора данных @@ -350,7 +358,7 @@ def run(self, metadata: ModuleType = openav, resources: ModuleType = rsrs, out: out (bool): Печатать процесс выполнения Returns: - bool: **True** если детектирование речевой активности в аудиовизуальном сигнале произведено успешно, + bool: **True** если предобработка речевых аудиоданных произведена успешно, в обратном случае **False** """ diff --git a/openav/api/preprocess_video.py b/openav/api/preprocess_video.py new file mode 100644 index 0000000..9bb4a99 --- /dev/null +++ b/openav/api/preprocess_video.py @@ -0,0 +1,392 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Предобработка речевых видеоданных +""" + +import os +import sys + +PATH_TO_SOURCE = os.path.abspath(os.path.dirname(__file__)) +PATH_TO_ROOT = os.path.join(PATH_TO_SOURCE, "..", "..") + +sys.path.insert(0, os.path.abspath(PATH_TO_ROOT)) + +# ###################################################################################################################### +# Импорт необходимых инструментов +# ###################################################################################################################### +# Подавление Warning +import warnings + +for warn in [UserWarning, FutureWarning]: + warnings.filterwarnings("ignore", category=warn) + +from dataclasses import dataclass # Класс данных + +import logging # Логирование Функции создающие итераторы для эффективного цикла + +# Типы данных +from typing import Dict, Union, Any +from types import ModuleType + +# Персональные +import openav # Библиотека в целом +from openav.modules.trml.shell import Shell # Работа с Shell +from openav.modules.lab.build import Run # Сборка библиотеки +from openav import rsrs # Ресурсы библиотеки + +from openav.modules.core.logging import ARG_PATH_TO_LOGS +from openav.modules.lab.video import DPI, COLOR_MODE, EXT_VIDEO + + +# ###################################################################################################################### +# Сообщения +# ###################################################################################################################### +@dataclass +class MessagesPreprocessVideo(Run): + """Класс для сообщений""" + + # ------------------------------------------------------------------------------------------------------------------ + # Конструктор + # ------------------------------------------------------------------------------------------------------------------ + + def __post_init__(self): + super().__post_init__() # Выполнение конструктора из суперкласса + + self._description: str = self._("Предобработка речевых видеоданных") + self._description_time: str = "{}" * 2 + self._description + self._em + "{}" + + self._check_config_file_valid = self._("Проверка данных на валидность") + self._em + + +# ###################################################################################################################### +# Выполняем только в том случае, если файл запущен сам по себе +# ###################################################################################################################### +@dataclass +class RunPreprocessVideo(MessagesPreprocessVideo): + """Класс для предобработки речевых видеоданных""" + + # ------------------------------------------------------------------------------------------------------------------ + # Конструктор + # ------------------------------------------------------------------------------------------------------------------ + + def __post_init__(self): + super().__post_init__() # Выполнение конструктора из суперкласса + + self._all_layer_in_yaml = 11 # Общее количество настроек в конфигурационном файле + + # Регистратор логирования с указанным именем + self._logger_run_preprocess_video: logging.Logger = logging.getLogger(__class__.__name__) + + # ------------------------------------------------------------------------------------------------------------------ + # Внутренние методы (защищенные) + # ------------------------------------------------------------------------------------------------------------------ + + def _build_args(self, description: str, conv_to_dict: bool = True, out=True) -> Dict[str, Any]: + """ + Args: + description (str): Описание парсера командной строки + conv_to_dict (bool): Преобразование списка аргументов командной строки в словарь + out (bool): Печатать процесс выполнения + + Returns: + Dict[str, Any]: Словарь со списком аргументов командной стройки + """ + + # Выполнение функции из суперкласса + super().build_args(description=description, conv_to_dict=False, out=out) + + if self._ap is None: + return {} + + # Добавление аргументов в парсер командной строки + self._ap.add_argument( + "--config", required=True, metavar=self._("ФАЙЛ"), help=self._("Путь к конфигурационному файлу") + ) + + self._ap.add_argument( + ARG_PATH_TO_LOGS, + required=False, + metavar=self._("ФАЙЛ"), + help=self._("Путь к директории для сохранения LOG файлов"), + ) + + self._ap.add_argument( + "--automatic_update", + action="store_true", + help=self._( + "Автоматическая проверка конфигурационного файла в момент работы программы (работает при заданном" + ) + + " --config", + ) + self._ap.add_argument( + "--no_clear_shell", action="store_false", help=self._("Не очищать консоль перед выполнением") + ) + + # Преобразование списка аргументов командной строки в словарь + if conv_to_dict is True: + args, _ = self._ap.parse_known_args() + return vars(args) # Преобразование списка аргументов командной строки в словарь + + def _valid_yaml_config(self, config: Dict[str, Union[str, bool, int, float]], out: bool = True) -> bool: + """Проверка настроек JSON на валидность + + Args: + config (Dict[str, Union[str, bool, int, float]]): Словарь из JSON файла + out (bool): Печатать процесс выполнения + + Returns: + bool: **True** если конфигурационный файл валиден, в обратном случае **False** + """ + + # Проверка аргументов + if type(config) is not dict or type(out) is not bool: + try: + raise TypeError + except TypeError: + self.inv_args(__class__.__name__, self._valid_yaml_config.__name__, out=out) + return False + + # Конфигурационный файл пуст + if not config: + try: + raise TypeError + except TypeError: + self.message_error(self._config_empty, space=self._space, out=out) + return False + + # Вывод сообщения + self.message_info(self._check_config_file_valid, space=self._space, out=out) + + curr_valid_layer = 0 # Валидное количество разделов + + # Проход по всем разделам конфигурационного файла + for key, val in config.items(): + # 1. Скрытие метаданных + # 2. Скрытие версий установленных библиотек + # 3. Очистка директории для сохранения видеоданных после предобработки + # 4. Сохранение сырых данных с областями губ в формате .npy + if key == "hide_metadata" or key == "hide_libs_vers" or key == "clear_dir_video" or key == "save_raw_data": + # Проверка значения + if type(val) is not bool: + continue + + curr_valid_layer += 1 + + # Глубина иерархии для получения данных + if key == "depth": + # Проверка значения + if type(val) is not int or not (1 <= val <= 10): + continue + + curr_valid_layer += 1 + + # Расширения искомых файлов + if key == "ext_search_files": + curr_valid_layer_2 = 0 # Валидное количество подразделов в текущем разделе + + # Проверка значения + if type(val) is not list or len(val) == 0: + continue + + # Проход по всем подразделам текущего раздела + for v in val: + # Проверка значения + if type(v) is not str or not v or (v in EXT_VIDEO) is False: + curr_valid_layer_2 += 100 + continue + + curr_valid_layer_2 += 1 + + if curr_valid_layer_2 <= len(EXT_VIDEO): + curr_valid_layer += 1 + + # 1. Путь к директории набора данных + # 2. Путь к директории набора данных состоящего из изобращений с губами + if key == "path_to_dataset" or key == "path_to_dataset_video": + # Проверка значения + if type(val) is not str or not val: + continue + + curr_valid_layer += 1 + + # DPI + if key == "dpi": + # Проверка значения + if type(val) is not int or (val in DPI) is False: + continue + + curr_valid_layer += 1 + + # Размер кадра с найденной областью губ + if key == "size_lips": + all_layer_2 = 2 # Общее количество подразделов в текущем разделе + curr_valid_layer_2 = 0 # Валидное количество подразделов в текущем разделе + + # Проверка значения + if type(val) is not dict or len(val) == 0: + continue + + # Проход по всем подразделам текущего раздела + for k, v in val.items(): + # Проверка значения + if type(v) is not int or v <= 0: + continue + + # 1. Ширина + # 2. Высота + if k == "width" or k == "height": + curr_valid_layer_2 += 1 + + if all_layer_2 == curr_valid_layer_2: + curr_valid_layer += 1 + + # Цветовая гамма конечного изображения + if key == "color_mode": + # Проверка значения + if type(val) is not str or (val in COLOR_MODE) is False: + continue + + curr_valid_layer += 1 + + # Сравнение общего количества ожидаемых настроек и валидных настроек в конфигурационном файле + if self._all_layer_in_yaml != curr_valid_layer: + try: + raise TypeError + except TypeError: + self.message_error(self._invalid_file, space=self._space, out=out) + return False + + return True # Результат + + def _load_config_yaml( + self, resources: ModuleType = rsrs, config="video_preprocessing.yaml", out: bool = True + ) -> bool: + """Загрузка и проверка конфигурационного файла + + Args: + resources (ModuleType): Модуль с ресурсами + config (str): Конфигурационный файл + out (bool): Печатать процесс выполнения + + Returns: + bool: **True** если конфигурационный файл загружен и валиден, в обратном случае **False** + """ + + # Проверка аргументов + if not isinstance(resources, ModuleType) or type(config) is not str or not config or type(out) is not bool: + try: + raise TypeError + except TypeError: + self.inv_args(__class__.__name__, self._load_config_yaml.__name__, out=out) + return False + + # Конфигурационный файл передан + if self._args["config"] is not None: + config_yaml = self.load_yaml(self._args["config"], False, out) # Загрузка YAML файла + else: + config_yaml = self.load_yaml_resources(resources, config, out) # Загрузка YAML файла из ресурсов модуля + + # Конфигурационный файл не загружен + if not config_yaml: + return False + + # Проверка конфигурационного файла на валидность + res_valid_yaml_config = self._valid_yaml_config(config_yaml, out) + + # Конфигурационный файл не валидный + if res_valid_yaml_config is False: + return False + + # Проход по всем разделам конфигурационного файла + for k, v in config_yaml.items(): + self._args[k] = v # Добавление значения из конфигурационного файла в словарь аргументов командной строки + + return True + + # ------------------------------------------------------------------------------------------------------------------ + # Внешние методы + # ------------------------------------------------------------------------------------------------------------------ + + def run(self, metadata: ModuleType = openav, resources: ModuleType = rsrs, out: bool = True) -> bool: + """Запуск предобработки речевых видеоданных + + Args: + metadata (ModuleType): Модуль из которого необходимо извлечь информацию + resources (ModuleType): Модуль с ресурсами + out (bool): Печатать процесс выполнения + + Returns: + bool: **True** если предобработка речевых видеоданных произведена успешно, + в обратном случае **False** + """ + + # Проверка аргументов + if not isinstance(metadata, ModuleType) or not isinstance(resources, ModuleType) or type(out) is not bool: + try: + raise TypeError + except TypeError: + self.inv_args(__class__.__name__, self.run.__name__, out=out) + return False + + self._args = self._build_args(self._description) # Построение аргументов командной строки + if len(self._args) == 0: + return False + + # Очистка консоли перед выполнением + if self.clear_shell(cls=self._args["no_clear_shell"], out=True) is False: + return False + + # Вывод сообщения + if out is True: + # Приветствие + Shell.add_line() # Добавление линии во весь экран + print(self._description_time.format(self.text_bold, self.color_blue, self.text_end)) + self._logger_run_preprocess_video.info(self._description) + Shell.add_line() # Добавление линии во весь экран + + # Загрузка и проверка конфигурационного файла + if self._load_config_yaml(resources, out=out) is False: + return False + + # Вывод сообщения + if out is True: + Shell.add_line() # Добавление линии во весь экран + + # Информация об библиотеке + if self._args["hide_metadata"] is False and out is True: + self.message_metadata_info(out=out) + Shell.add_line() # Добавление линии во весь экран + + # Версии установленных библиотек + if self._args["hide_libs_vers"] is False and out is True: + self.libs_vers(out=out) + Shell.add_line() # Добавление линии во весь экран + + self.path_to_dataset = self._args["path_to_dataset"] # Путь к директории набора данных + # Путь к директории набора данных состоящего из изобращений с губами + self.path_to_dataset_video = self._args["path_to_dataset_video"] + self.ext_search_files = self._args["ext_search_files"] # Расширения искомых файлов + + self.preprocess_video( + depth=self._args["depth"], # Глубина иерархии для получения данных + # Очистка директории для сохранения изобращений с губами после предобработки + clear_dir_video=self._args["clear_dir_video"], + dpi=self._args["dpi"], # DPI + # Сохранение сырых данных с областями губ в формате .npy + save_raw_data=self._args["save_raw_data"], + out=out, + ) + + return True + + +def main(): + # Запуск предобработки речевых аудиоданных + vad = RunPreprocessVideo(lang="ru", path_to_logs="./openav/logs") + vad.run(out=True) + + +if __name__ == "__main__": + main() diff --git a/openav/modules/core/settings.py b/openav/modules/core/settings.py index c3d1073..60c787b 100644 --- a/openav/modules/core/settings.py +++ b/openav/modules/core/settings.py @@ -46,6 +46,7 @@ EXT_SEARCH_FILES: List[str] = [ "mov", "mp4", + "webm", "wav", ] CHUNK_SIZE: int = 1000000 # Размер загрузки файла из сети за 1 шаг @@ -86,8 +87,12 @@ def __post_init__(self): self.path_to_save_models: str = PATH_TO_SAVE_MODELS # Путь к директории для сохранения моделей self.path_to_dataset: str = PATH_TO_DATASET # Путь к директории набора данных - self.path_to_input_augmentation_directory: str = PATH_TO_INPUT_AUGMENTATION_DIRECTORY # Путь к директории набора данных для обработки - self.path_to_output_augmentation_directory: str = PATH_TO_OUTPUT_AUGMENTATION_DIRECTORY # Путь к директории результирующих данных аугментации + self.path_to_input_augmentation_directory: str = ( + PATH_TO_INPUT_AUGMENTATION_DIRECTORY # Путь к директории набора данных для обработки + ) + self.path_to_output_augmentation_directory: str = ( + PATH_TO_OUTPUT_AUGMENTATION_DIRECTORY # Путь к директории результирующих данных аугментации + ) # Путь к директории набора данных состоящего из фрагментов аудиовизуального сигнала (VAD) self.path_to_dataset_vad: str = PATH_TO_DATASET_VAD # Путь к директории набора данных состоящего из фрагментов аудиовизуального сигнала (VOSK) diff --git a/openav/modules/lab/audio.py b/openav/modules/lab/audio.py index 5a03b1a..de6bc82 100644 --- a/openav/modules/lab/audio.py +++ b/openav/modules/lab/audio.py @@ -96,6 +96,7 @@ PAD_MODE_MS: List[int] = ["constant", "reflect", "replicate", "circular"] # Управление оступами DPI: List[int] = [72, 96, 150, 300, 600, 1200] # DPI COLOR_GRADIENTS: List[str] = ["viridis", "plasma", "inferno", "magma", "cividis"] +EXT_AUDIO: List[str] = ["mov", "mp4", "webm", "wav"] # Расширения искомых файлов # Количество выборок в каждом окне # (512, 1024, 1536 для частоты дискретизации 16000 или 256, 512, 768 для частоты дискретизации 8000) @@ -106,7 +107,7 @@ EXT_AUDIO_AUG: str = "png" # Расширение для сохраняемого аудио EXT_AUDIO_SPEC: str = "png" # Расширение для сохраняемой MelSpectrogram EXT_AUDIO: str = "wav" # Расширение для сохраняемого аудио -EXT_NPY: str = 'npy' # Расширения для сохранения сырых данных MelSpectrogram +EXT_NPY: str = "npy" # Расширения для сохранения сырых данных MelSpectrogram VOSK_SUPPORTED_LANGUAGES: List[str] = ["ru", "en"] # Поддерживаемые языки (Vosk) VOSK_SUPPORTED_DICTS: List[str] = ["small", "big"] # Размеры словарей (Vosk) VOSK_SPEECH_LEFT_PAD_MS: int = 0 # Внутренний левый отступ для итоговых речевых фрагментов @@ -1005,41 +1006,41 @@ def not_saved_files(): return True def __augmentation_check_settings( - self, - crop_px_min: int, - crop_px_max: int, - crop_percent_min: float, - crop_percent_max: float, - flip_lr_probability: float, - flip_ud_probability: float, - blur_min: float, - blur_max: float, - scale_x_min: float, - scale_x_max: float, - scale_y_min: float, - scale_y_max: float, - rotate_min: int, - rotate_max: int, - contrast_min: float, - contrast_max: float, - alpha: float, - count: int, - out: bool, + self, + crop_px_min: int, + crop_px_max: int, + crop_percent_min: float, + crop_percent_max: float, + flip_lr_probability: float, + flip_ud_probability: float, + blur_min: float, + blur_max: float, + scale_x_min: float, + scale_x_max: float, + scale_y_min: float, + scale_y_max: float, + rotate_min: int, + rotate_max: int, + contrast_min: float, + contrast_max: float, + alpha: float, + count: int, + out: bool, ) -> bool: try: # Проверка настроек if (AUGMENTATION_CROP_PX[0] <= crop_px_min <= crop_px_max <= AUGMENTATION_CROP_PX[1]) is False: raise CropPXError if ( - AUGMENTATION_CROP_PERCENT[0] <= crop_percent_min <= crop_percent_max <= AUGMENTATION_CROP_PERCENT[1] + AUGMENTATION_CROP_PERCENT[0] <= crop_percent_min <= crop_percent_max <= AUGMENTATION_CROP_PERCENT[1] ) is False: raise CropPercentsError if ( - AUGMENTATION_FLIP_LR_PROBABILITY[0] <= flip_lr_probability <= AUGMENTATION_FLIP_LR_PROBABILITY[1] + AUGMENTATION_FLIP_LR_PROBABILITY[0] <= flip_lr_probability <= AUGMENTATION_FLIP_LR_PROBABILITY[1] ) is False: raise FlipLRProbabilityError if ( - AUGMENTATION_FLIP_UD_PROBABILITY[0] <= flip_ud_probability <= AUGMENTATION_FLIP_UD_PROBABILITY[1] + AUGMENTATION_FLIP_UD_PROBABILITY[0] <= flip_ud_probability <= AUGMENTATION_FLIP_UD_PROBABILITY[1] ) is False: raise FlipUDProbabilityError if (AUGMENTATION_BLUR[0] <= blur_min <= blur_max <= AUGMENTATION_BLUR[1]) is False: @@ -1049,9 +1050,9 @@ def __augmentation_check_settings( if (AUGMENTATION_SCALE_Y[0] <= scale_y_min <= scale_y_max <= AUGMENTATION_SCALE_Y[1]) is False: raise ScaleError if ( - type(rotate_min) is not int - or type(rotate_max) is not int - or (AUGMENTATION_ROTATE[0] <= rotate_min <= rotate_max <= AUGMENTATION_ROTATE[1]) is False + type(rotate_min) is not int + or type(rotate_max) is not int + or (AUGMENTATION_ROTATE[0] <= rotate_min <= rotate_max <= AUGMENTATION_ROTATE[1]) is False ): raise RotateError if (AUGMENTATION_CONTRAST[0] <= contrast_min <= contrast_max <= AUGMENTATION_CONTRAST[1]) is False: @@ -1144,28 +1145,28 @@ def __augmentation_check_settings( return True def __augmentation_validate_arguments( - self, - depth: int, - crop_px_min: int, - crop_px_max: int, - crop_percent_min: float, - crop_percent_max: float, - flip_lr_probability: float, - flip_ud_probability: float, - blur_min: float, - blur_max: float, - scale_x_min: float, - scale_x_max: float, - scale_y_min: float, - scale_y_max: float, - rotate_min: int, - rotate_max: int, - contrast_min: float, - contrast_max: float, - alpha: float, - count: int, - clear_diraug: bool, - out: bool, + self, + depth: int, + crop_px_min: int, + crop_px_max: int, + crop_percent_min: float, + crop_percent_max: float, + flip_lr_probability: float, + flip_ud_probability: float, + blur_min: float, + blur_max: float, + scale_x_min: float, + scale_x_max: float, + scale_y_min: float, + scale_y_max: float, + rotate_min: int, + rotate_max: int, + contrast_min: float, + contrast_max: float, + alpha: float, + count: int, + clear_diraug: bool, + out: bool, ) -> bool: try: # Проверка аргументов @@ -1280,7 +1281,7 @@ def __augmentation_process_files(self, paths: List[str], clear_diraug: bool, out # Локальный путь self.__local_path = lambda lp: os.path.join( - *Path(lp).parts[-abs((len(Path(lp).parts) - len(Path(self.path_to_input_augmentation_directory).parts))):] + *Path(lp).parts[-abs((len(Path(lp).parts) - len(Path(self.path_to_input_augmentation_directory).parts))) :] ) # Проход по всем найденным аудиовизуальных файлам diff --git a/openav/modules/lab/video.py b/openav/modules/lab/video.py index e91ebcc..43e1e2f 100644 --- a/openav/modules/lab/video.py +++ b/openav/modules/lab/video.py @@ -16,16 +16,29 @@ from dataclasses import dataclass # Класс данных +import os # Взаимодействие с файловой системой +import numpy as np # Научные вычисления +import re # Регулярные выражения +import filetype # Определение типа файла и типа MIME + # Типы данных from typing import List +from pathlib import Path # Работа с путями в файловой системе + # Персональные +from openav.modules.core.exceptions import ( + IsNestedCatalogsNotFoundError, +) from openav.modules.file_manager.json_manager import Json # Класс для работы с Json # Метрики оценки нейросетевой модели METRICS_VIDEO: List[str] = [ "accuracy", ] +DPI: List[int] = [72, 96, 150, 300, 600, 1200] # DPI +COLOR_MODE: List[str] = ["gray", "rgb"] # Цветовая гамма конечного изображения +EXT_VIDEO: List[str] = ["mov", "mp4", "webm"] # Расширения искомых файлов # ###################################################################################################################### @@ -47,6 +60,18 @@ class VideoMessages(Json): def __post_init__(self): super().__post_init__() # Выполнение конструктора из суперкласса + self._subfolders_search: str = ( + self._('Поиск вложенных директорий в директории "{}" (глубина вложенности: {})') + self._em + ) + self._subfolders_not_found: str = self._("В указанной директории вложенные директории не найдены") + self._em + self._files_av_find: str = ( + self._('Поиск файлов с расширениями "{}" в директории "{}" (глубина вложенности: {})') + self._em + ) + + self.preprocess_video_files: str = self._("Предобработка речевых видеоданных") + self._em + + self._preprocess_true: str = self._("Все файлы успешно предобработаны") + self._em + # ###################################################################################################################### # Видео @@ -67,6 +92,12 @@ class Video(VideoMessages): def __post_init__(self): super().__post_init__() # Выполнение конструктора из суперкласса + # ----------------------- Только для внутреннего использования внутри класса + + self.__dataset_preprocess_video: List[str] = [] # Пути до директорий с изображениями губ + self.__unprocessed_files: List[str] = [] # Пути к файлам из которых области губ не извлечены + self.__not_saved_files: List[str] = [] # Пути к файлам которые не сохранились при обработке + # ------------------------------------------------------------------------------------------------------------------ # Свойства # ------------------------------------------------------------------------------------------------------------------ @@ -82,3 +113,222 @@ def __post_init__(self): # ------------------------------------------------------------------------------------------------------------------ # Внешние методы # ------------------------------------------------------------------------------------------------------------------ + + def preprocess_video( + self, + depth: int = 1, + dpi: int = 1200, + save_raw_data: bool = True, + clear_dir_video: bool = False, + out: bool = True, + ) -> bool: + """Предобработка речевых видеоданных + + Args: + depth (int): Глубина иерархии для получения данных + dpi (int): DPI + save_raw_data (bool): Сохранение сырых данных с областями губ в формате .npy + clear_dir_video (bool): Очистка директории для сохранения видео данных после предобработки + out (bool) Отображение + + Returns: + bool: **True** если предобработка речевых видеоданных произведена, в обратном случае + **False** + """ + + try: + # Проверка аргументов + if ( + type(depth) is not int + or depth < 1 + or type(dpi) is not int + or (dpi in DPI) is False + or type(save_raw_data) is not bool + or type(clear_dir_video) is not bool + or type(out) is not bool + ): + raise TypeError + except TypeError: + self.inv_args(__class__.__name__, self.preprocess_video.__name__, out=out) + return False + else: + # Информационное сообщение + self.message_info( + self._subfolders_search.format( + self.message_line(self.path_to_dataset), + self.message_line(str(depth)), + ), + out=out, + ) + + # Создание директории, где хранятся данные + if self.create_folder(self.path_to_dataset, out=False) is False: + return False + + # Получение вложенных директорий, где хранятся данные + nested_paths = self.get_paths(self.path_to_dataset, depth=depth, out=False) + + # Вложенные директории не найдены + try: + if len(nested_paths) == 0: + raise IsNestedCatalogsNotFoundError + except IsNestedCatalogsNotFoundError: + self.message_error(self._subfolders_not_found, space=self._space, out=out) + return False + + # Информационное сообщение + self.message_info( + self._files_av_find.format( + self.message_line(", ".join(x.replace(".", "") for x in self.ext_search_files)), + self.message_line(self.path_to_dataset), + self.message_line(str(depth)), + ), + out=out, + ) + + paths = [] # Пути до визуальных файлов + + # Проход по всем вложенным директориям + for nested_path in nested_paths: + # Формирование списка с видеофайлами + for p in Path(nested_path).glob("*"): + # Добавление текущего пути к видеофайлу в список + if p.suffix.lower() in self.ext_search_files: + paths.append(p.resolve()) + + # Директория с набором данных не содержит визуальных файлов с необходимыми расширениями + try: + self.__len_paths = len(paths) # Количество визуальных файлов + + if self.__len_paths == 0: + raise TypeError + except TypeError: + self.message_error(self._files_not_found, space=self._space, out=out) + return False + except Exception: + self.message_error(self._unknown_err, space=self._space, out=out) + return False + else: + pass + + # Очистка директории для сохранения фрагментов визуального сигнала + if clear_dir_video is True and os.path.exists(self.path_to_dataset_video) is True: + if self.clear_folder(self.path_to_dataset_video, out=False) is False: + return False + + self.__dataset_preprocess_video = [] # Пути до директорий с изображениями губ + + self.__unprocessed_files = [] # Пути к файлам из которых области губ не извлечены + + # Информационное сообщение + self.message_info(self.preprocess_video_files, out=out) + + # Локальный путь + self.__local_path = lambda lp: os.path.join( + *Path(lp).parts[-abs((len(Path(lp).parts) - len(Path(self.path_to_dataset).parts))) :] + ) + + # Проход по всем найденным визуальных файлам + for i, path in enumerate(paths): + self.__curr_path = path # Текущий визуальный файл + self.__i = i + 1 # Счетчик + + self.message_progressbar( + self._curr_progress.format( + self.__i, + self.__len_paths, + round(self.__i * 100 / self.__len_paths, 2), + self.message_line(self.__local_path(self.__curr_path)), + ), + space=self._space, + out=out, + ) + + self.__splitted_path = str(self.__curr_path.parent.relative_to(Path(self.path_to_dataset))).strip() + + self.__curr_path = str(self.__curr_path) + + # Пропуск невалидных значений + if not self.__splitted_path or re.search(r"\s", self.__splitted_path) is not None: + continue + + # Тип файла + kind = filetype.guess(self.__curr_path) + + # try: + # # Видео или аудио + # if kind.mime.startswith("video/") is True or kind.mime.startswith("audio/") is True: + # # Формирование мел-спектрограммы + # waveform, sample_rate = librosa.load(self.__curr_path, sr=sample_rate) + # waveform = torch.Tensor(waveform) + + # torchaudio_melspec = torchaudio.transforms.MelSpectrogram( + # sample_rate=sample_rate, + # n_fft=n_fft, + # win_length=None, + # hop_length=hop_length, + # center=center, + # pad_mode=pad_mode, + # power=power, + # norm=norm, + # onesided=True, + # n_mels=n_mels, + # f_max=None, + # )(waveform) + + # # Преобразование мел-спектрограммы в децибелы + # melspectogram_db_transform = torchaudio.transforms.AmplitudeToDB() + # melspec_db = melspectogram_db_transform(torchaudio_melspec) + + # # Преобразование мел-спектрограммы в numpy-массив + # melspec_np = melspec_db.numpy() + + # # Текущее время (TimeStamp) + # # см. datetime.fromtimestamp() + # self.__curr_ts = str(datetime.now().timestamp()).replace(".", "_") + + # # Путь до мел-спектрограммы + # melspec_path = os.path.join( + # self.path_to_dataset_audio, + # Path(self.__curr_path).stem + "_" + self.__curr_ts + "." + EXT_AUDIO_SPEC, + # ) + + # if not os.path.exists(self.path_to_dataset_audio): + # # Директория не создана + # if self.create_folder(self.path_to_dataset_audio, out=False) is False: + # raise FileNotFoundError + + # # Нормализация значений мел-спектрограммы в диапазон [0, 1] + # melspec_np = (melspec_np - melspec_np.min()) / (melspec_np.max() - melspec_np.min()) + + # # Переворот массива по вертикали + # melspec_np = np.flip(melspec_np, axis=0) + + # # Применение цветовой карты + # # color_gradients: viridis, plasma, inferno, magma, cividis + # cmap = cm.get_cmap(color_gradients) + # melspec_rgb = cmap(melspec_np)[:, :, :3] # Извлечение только RGB-каналов + + # # Нормализация значений в диапазон [0, 255] + # melspec_rgb = (melspec_rgb * 255).astype("uint8") + + # # Создание и сохранение изображения с помощью Pillow + # img = Image.fromarray(melspec_rgb) + # img.save(melspec_path, dpi=(dpi, dpi)) + + # if save_raw_data: + # # Сохранение сырых данных мел-спектрограммы в формате .npy + # raw_data_path = melspec_path.replace("." + EXT_AUDIO_SPEC, "." + EXT_NPY) + # np.save(raw_data_path, melspec_np) + # except Exception: + # self.__unprocessed_files.append(self.__curr_path) + # self.message_progressbar(close=True, out=out) + # continue + + self.message_progressbar(close=True, out=out) + + # Файлы на которых предварительная обработка не отработала + unprocessed_files_unique = np.unique(np.array(self.__unprocessed_files)).tolist() + + if len(unprocessed_files_unique) == 0 and len(self.__not_saved_files) == 0: + self.message_true(self._preprocess_true, space=self._space, out=out) diff --git a/openav/rsrs/video_preprocessing.yaml b/openav/rsrs/video_preprocessing.yaml index d9653d0..06e6239 100644 --- a/openav/rsrs/video_preprocessing.yaml +++ b/openav/rsrs/video_preprocessing.yaml @@ -2,14 +2,16 @@ hide_metadata: false hide_libs_vers: false path_to_dataset: "/Users/dl/@DmitryRyumin/Databases/LRW_TEST" -depth: 1 +depth: 2 ext_search_files: - mov - mp4 - webm -- wav path_to_dataset_video: "./dataset_video" -width: 112 -height: 112 -# gray or rgb +dpi: 600 +size_lips: + width: 112 + height: 112 color_mode: gray +clear_dir_video: true +save_raw_data: true \ No newline at end of file