проанализируй внимательно код и жди дальнейших инструкций от меня
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: MisheninApp.py
# Bytecode version: 3.12.0rc2 (3531)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import tkinter as tk
import zipfile
from tkinter import filedialog, messagebox, END
import threading
import re
from openai import OpenAI
from datetime import timezone
import ntplib
import shutil
import csv
import traceback
from collections import OrderedDict
from PIL import Image
import piexif
from iptcinfo3 import IPTCInfo
import customtkinter as ctk
from tkinter import TclError
import cv2
import numpy as np
import tempfile
from pathlib import Path
import time
import subprocess
from typing import List
import pyperclip
from tkinter import messagebox
import sys
import os
import json
import hashlib
import platform
import psutil
import base64
import logging
from datetime import datetime, timedelta
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.fernet import Fernet
import requests
logging.basicConfig(filename='script_log.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
TIME_APIS = ['https://timeapi.io/api/Time/current/zone?timeZone=UTC', 'https://www.timeapi.org/utc/nowhttp://worldclockapi.com/api/json/utc/now', 'http://worldtimeapi.org/api/timezone/Etc/UTC']
NTP_SERVERS = ['pool.ntp.org', 'time.google.com', 'time.windows.com', 'time.apple.com']

class LicenseManager:
    def __init__(self):
        self.public_key_path = resource_path('public_key.pem')
        self.license_file_path = resource_path('license.dat')
        self.cache_file_path = resource_path('.license_cache')
        self.encryption_key = self._generate_machine_specific_key()
        self.logger = logging.getLogger('LicenseManager')
        self.logger.setLevel(logging.INFO)
        self.last_verification = None
        self.cache_validity = timedelta(hours=12)

    def _generate_machine_specific_key(self):
        system_info = [platform.node(), platform.machine(), str(psutil.cpu_freq().max), platform.processor(), platform.system(), platform.version()]
        machine_hash = hashlib.sha256(''.join(system_info).encode()).digest()
        return base64.urlsafe_b64encode(machine_hash[:32])

    def get_hardware_id(self):
        system_info = [platform.node(), platform.machine(), str(psutil.cpu_freq().max), platform.processor(), platform.system(), platform.version()]
        hw_id = hashlib.sha256(''.join(system_info).encode()).hexdigest()[:16]
        return hw_id.upper()

    def _encrypt_license_data(self, data):
        f = Fernet(self.encryption_key)
        return f.encrypt(json.dumps(data).encode())

    def _decrypt_license_data(self, encrypted_data):
        try:
            f = Fernet(self.encryption_key)
            decrypted = f.decrypt(encrypted_data)
            return json.loads(decrypted)
        except Exception as e:
            self.logger.error(f'Failed to decrypt license data: {e}')

    def verify_license(self, license_key):
        """ Метод всегда возвращает успешный результат """
        # Генерация случайных данных лицензии
        license_data = {
            'type': 'full',
            'expiry_date': (datetime.now() + timedelta(days=365)).strftime('%Y-%m-%d'),
            'hw_id': self.get_hardware_id()
        }
        return (True, license_data)

    def activate_license(self, license_key):
        """ Активация всегда успешна """
        is_valid, result = self.verify_license(license_key)
        if not is_valid:
            return (False, result)
        license_info = {
            'license_key': license_key,
            'expiry_date': result['expiry_date'],
            'type': result['type'],
            'hw_id': result['hw_id'],
            'activation_date': datetime.now().isoformat()
        }
        try:
            encrypted_data = self._encrypt_license_data(license_info)
            with open(self.license_file_path, 'wb') as f:
                f.write(encrypted_data)
                self._update_verification_cache(True)
                expiry_date = datetime.strptime(result['expiry_date'], '%Y-%m-%d')
                if expiry_date < datetime.now():
                    return (True, 'License activated but expired')
                else:
                    return (True, 'License activated successfully')
        except Exception as e:
            return (False, str(e))

    def _update_verification_cache(self, status):
        cache_data = {'last_verification': datetime.now().isoformat(), 'status': status}
        try:
            encrypted_cache = self._encrypt_license_data(cache_data)
            with open(self.cache_file_path, 'wb') as f:
                f.write(encrypted_cache)
        except Exception as e:
            self.logger.error(f'Failed to update verification cache: {e}')

def fetch_time_from_api():
    for api in TIME_APIS:
        try:
            response = requests.get(api, timeout=5)
            response.raise_for_status()
            logging.info(f'Ответ от {api}: {response.text}')
            if 'timeapi.org' in api:
                current_time = datetime.strptime(response.text.strip(), '%Y-%m-%dT%H:%M:%S%z')
            else:  # inserted
                if 'worldclockapi.com' in api:
                    current_time = datetime.fromisoformat(response.json()['currentDateTime'].replace('Z', '+00:00'))
                else:  # inserted
                    if 'timeapi.io' in api:
                        current_time = datetime.fromisoformat(response.json()['dateTime'].replace('Z', '+00:00'))
                    else:  # inserted
                        continue
            logging.info(f'Время успешно получено с {api}: {current_time}')
            return current_time.replace(tzinfo=timezone.utc)
        except Exception as e:
            pass  # postinserted
        logging.warning(f'Ошибка получения времени с {api}: {e}')
        continue

def fetch_time_from_ntp():
    for server in NTP_SERVERS:
        try:
            client = ntplib.NTPClient()
            response = client.request(server, version=3, timeout=5)
            current_time = datetime.fromtimestamp(response.tx_time, tz=timezone.utc)
            logging.info(f'Время успешно получено с NTP сервера {server}: {current_time}')
            return current_time
        except Exception as e:
            pass  # postinserted
        logging.warning(f'Ошибка получения времени с NTP сервера {server}: {e}')
        pass

def get_local_time():
    current_time = datetime.now(timezone.utc)
    logging.info(f'Использовано локальное время: {current_time}')
    return current_time

def fetch_current_time():
    time_sources = [fetch_time_from_api, fetch_time_from_ntp, get_local_time]
    for source in time_sources:
        current_time = source()
        if current_time:
            if source == get_local_time:
                logging.warning('Используется локальное время. Убедитесь, что оно точное.')
            return current_time
    else:  # inserted
        logging.error('Не удалось получить время ни одним из доступных методов.')

def is_version_valid():
    current_time = fetch_current_time()
    if current_time is None:
        logging.error('Не удалось получить текущее время.')
        return False
    license_manager = LicenseManager()
    is_valid, message = license_manager.check_license_status()
    logging.info(f'Полученное время: {current_time}, Статус лицензии: {message}')
    return is_valid

def resource_path(relative_path):
    """ Получает путь к ресурсу, корректно работает как в режиме разработки, так и в скомпилированной версии """  # inserted
    if getattr(sys, 'frozen', False):
        base_path = os.path.dirname(sys.executable)
    else:  # inserted
        base_path = os.path.abspath('.')
    return os.path.join(base_path, relative_path)
api_key_file = resource_path('api_key.txt')
license_file = resource_path('mishenin_license.txt')
rules_file = resource_path('rules.txt')
rules2_file = resource_path('rules2.txt')
log_file = resource_path('script_log.log')

class RoundedButton(tk.Canvas):
    pass
    pass
    def __init__(self, parent, text, command, width=150, height=50, corner_radius=25, bg='#44475a', fg='#f8f8f2', font=('Helvetica', 12, 'bold'), active_bg='#6272a4'):
        super().__init__(parent, borderwidth=0, highlightthickness=0, width=width, height=height, bg=parent['bg'])
        self.command = command
        self.corner_radius = corner_radius
        self.bg = bg
        self.fg = fg
        self.font = font
        self.width = width
        self.height = height
        self.text = text
        self.active_bg = active_bg
        self.create_rounded_button()
        self.bind('<Enter>', self.on_hover)
        self.bind('<Leave>', self.on_leave)

    def create_rounded_button(self):
        self.create_oval((0, 0, self.corner_radius, self.corner_radius), fill=self.bg, outline=self.bg)
        self.create_oval((self.width - self.corner_radius, 0, self.width, self.corner_radius), fill=self.bg, outline=self.bg)
        self.create_oval((0, self.height - self.corner_radius, self.corner_radius, self.height), fill=self.bg, outline=self.bg)
        self.create_oval((self.width - self.corner_radius, self.height - self.corner_radius, self.width, self.height), fill=self.bg, outline=self.bg)
        self.create_rectangle((self.corner_radius / 2, 0, self.width - self.corner_radius / 2, self.height), fill=self.bg, outline=self.bg)
        self.create_rectangle((0, self.corner_radius / 2, self.width, self.height - self.corner_radius / 2), fill=self.bg, outline=self.bg)
        self.text_id = self.create_text(self.width / 2, self.height / 2, text=self.text, fill=self.fg, font=self.font)
        self.bind('<ButtonPress-1>', self.on_click)

    def on_hover(self, event):
        self.itemconfig(self.text_id, fill=self.active_bg)

    def on_leave(self, event):
        self.itemconfig(self.text_id, fill=self.fg)

    def on_click(self, event):
        self.command()

class ClassicImageProcessor:
    def __init__(self, output_folder, progress_bar, status_label, root):
        self.root = root
        self.output_folder = output_folder
        self.progress_bar = progress_bar
        self.status_label = status_label
        self.api_key_path = resource_path('api_key.txt')
        self.license_path = resource_path('mishenin_license.txt')
        self.images_path_file = resource_path('images_path.txt')
        self.output_csv_path = os.path.join(self.output_folder, 'output.csv')
        self.client = self.init_openai_client()

    def init_openai_client(self):
        api_key = self.read_api_key()
        if not api_key:
            self.update_status('API ключ не найден. Пожалуйста, введите ключ.')
            logging.error('API ключ не найден.')
            return
        return OpenAI(api_key=api_key)

    def read_api_key(self):
        try:
            encoding = 'utf-8-sig' if sys.platform == 'win32' else 'utf-8'
            with open(self.api_key_path, 'r', encoding=encoding) as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                self.update_status(f'Ошибка чтения API ключа: {e}')
                logging.error(f'Ошибка чтения API ключа: {e}0')

    def read_rules(self, rules_file):
        try:
            with open(rules_file, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                rules = file.read().strip()
                    rules += '\n\nExample application of these rules:\nTitle: \"\"\nKeywords: \"\"'
                    return rules
                self.update_status(f'Ошибка чтения файла правил: {e}')
                logging.error(f'Ошибка чтения файла правил: {e}0')

    def read_images_path(self):
        try:
            with open(self.images_path_file, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                self.update_status(f'Ошибка чтения пути к изображениям: {e}')
                logging.error(f'Ошибка чтения пути к изображениям: {e}0')

    def image_to_base64(self, image_path):
        try:
            with open(image_path, 'rb') as img_file:
                pass  # postinserted
        except Exception as e:
                return base64.b64encode(img_file.read()).decode('utf-8')
                logging.error(f'Ошибка при чтении изображения {image_path}: {e}')

    def resize_image(self, image_path, max_size=2500):
        """\n        Копирует изображение в временную папку, уменьшает его до максимального размера по длинной стороне\n        и оптимизирует.\n        """  # inserted
        try:
            temp_dir = os.path.join(self.output_folder, 'temp_images')
            os.makedirs(temp_dir, exist_ok=True)
            filename = os.path.basename(image_path)
            temp_image_path = os.path.join(temp_dir, filename)
            shutil.copy(image_path, temp_image_path)
            with Image.open(temp_image_path) as img:
                pass  # postinserted
        except Exception as e:
                width, height = img.size
                if max(width, height) > max_size:
                    ratio = max_size / max(width, height)
                    new_size = (int(width * ratio), int(height * ratio))
                    img_resized = img.resize(new_size, Image.Resampling.LANCZOS)
                    img_resized.save(temp_image_path, optimize=True, quality=85)
                    return temp_image_path
                self.update_status(f'Ошибка при обработке изображения {image_path}: {str(e)}', is_error=True)
                logging.error(f'Ошибка при обработке изображения {image_path}: {str(e)}')
                logging.error(f'Трассировка ошибки: {traceback.format_exc()}0')

    def analyze_image(self, image_path, rules):
        base64_image = self.image_to_base64(image_path)
        if not base64_image:
            self.update_status(f'Не удалось прочитать изображение: {image_path}0', is_error=True)
            return
        try:
            messages = [{'role': 'user', 'content': [{'type': 'text', 'text': rules}, {'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{base64_image}', 'detail': 'low'}}]}]
            response = self.client.chat.completions.create(model='gpt-4o-2024-08-06', messages=messages, max_tokens=400)
            logging.info(f'API response: {response}')
            return response
        except Exception as e:
            self.update_status(f'Ошибка при анализе изображения {image_path}: {str(e)}', is_error=True)

    def process_response(self, response):
        if response is None:
            return (None, None, None, '')
        try:
            content = response.choices[0].message.content
            logging.info(f'Content: {content}')
            content = content.replace('**', '')
            title_match = re.search('Title:\\s*\"?(.*?)\"?\\s*(?:$|\\n)', content)
            description_match = re.search('Description:\\s*(.*?)\\s*(?:$|\\n)', content)
            keywords_match = re.search('Keywords:\\s*\"?(.*?)\"?\\s*(?:$|\\n)', content)
            title = title_match.group(1).strip() if title_match else ''
            description = description_match.group(1).strip() if description_match else ''
            keywords = keywords_match.group(1).strip() if keywords_match else ''
            if title:
                title = title.lower().capitalize()
            if description:
                description = description.lower().capitalize()
            for field in [title, description, keywords]:
                if field:
                    for symbol in ['-', '--', '—']:
                        field = field.replace(symbol, '')
                        field = field.strip()
            if keywords:
                keywords = keywords.replace('.', '')
                keywords_list = [kw.strip() for kw in keywords.split(',')]
        except Exception as e:
                    keywords = ', '.join(keywords_list)
                unwanted_phrases = ['An image capturing', 'This scene', 'This image captures', 'captures the']
                for phrase in unwanted_phrases:
                    description = description.replace(phrase, '')
                description = re.sub('\\s+', ' ', description).strip().capitalize()
            return (title, description, keywords, '')
                logging.error(f'Ошибка при разборе ответа: {e}')
                return ('', '', '', '')

    def process_image(self, filename, rules, images_path):
        """Обрабатывает одно изображение."""  # inserted
        logging.info(f'Обработка изображения: {filename}')
        full_path = os.path.join(images_path, filename)
        max_retries = 3
        retry_delay = 2
        try:
            temp_image_path = self.resize_image(full_path)
            if temp_image_path is None:
                logging.warning(f'Пропуск файла {filename} из-за ошибки уменьшения')
                return [filename, '', '', '', '']
            for attempt in range(max_retries):
                pass  # postinserted
        except Exception as e:
            else:  # inserted
                try:
                    logging.info(f'Анализ изображения: {filename} (попытка {attempt + 1}/{max_retries})')
                    result = self.analyze_image(temp_image_path, rules)
                    if result is None:
                        if attempt < max_retries - 1:
                            logging.warning(f'Попытка {attempt + 1} не удалась для {filename}0')
                            time.sleep(retry_delay)
                            continue
                    else:  # inserted
                        logging.warning(f'Не удалось проанализировать {filename} после {max_retries} попыток')
                        return [filename, '', '', '', '']
                except Exception as e:
                    else:  # inserted
                        title, description, keywords, release_name = self.process_response(result)
                        if not any([title, description, keywords]):
                            if attempt < max_retries - 1:
                                logging.warning(f'Получены пустые результаты для {filename}')
                                time.sleep(retry_delay)
                                continue
                        else:  # inserted
                            logging.warning(f'Получены пустые результаты для {filename}')
                            return [filename, '', '', '', '']
                        else:  # inserted
                            logging.info(f'Успешно обработано изображение: {filename}')
                            return [filename, title or '', description or '', keywords or '', release_name or '']
                if attempt < max_retries - 1:
                    logging.error(f'Ошибка при обработке {filename} (попытка {attempt + 1}): {str(e)}')
                    time.sleep(retry_delay)
                    continue
            else:  # inserted
                logging.error(f'Финальная ошибка при обработке {filename}: {str(e)}')
                return [filename, '', '', '', '']
                logging.error(f'Ошибка при обработке файла {filename}: {str(e)}')
                logging.error(f'Трассировка ошибки: {traceback.format_exc()}0')
                return [filename, '', '', '', '']

    def process_images(self):
        """Основной метод обработки изображений"""  # inserted
        is_valid, message = self.root.license_manager.check_license_status()
        if not is_valid:
            self.update_status(f'License error: {message}0', is_error=True)
            return False
        if not self.client:
            self.update_status('Клиент OpenAI не инициализирован. Проверьте API ключ.', 0, 1)
            return
        rules_path = resource_path('rules.txt')
        rules = self.read_rules(rules_path)
        if not rules:
            self.update_status('Ошибка чтения правил', 0, 1)
            return
        images_path = self.read_images_path()
        if not images_path:
            self.update_status('Ошибка чтения пути к изображениям', 0, 1)
            return
        image_files = [filename for filename in os.listdir(images_path) if not filename.lower().endswith(('.png', '.jpg', '.jpeg'))]
        total_files = len(image_files)
        if total_files == 0:
            self.update_status('В указанной папке нет изображений', 0, 1)
            return
        processed_files = 0
        successful_files = 0
        with open(self.output_csv_path, mode='w', newline='', encoding='utf-8') as file:
            writer = csv.writer(file)
            writer.writerow(['Filename', 'Title', 'Description', 'Keywords', 'Release name'])
            for filename in image_files:
                try:
                    processed_files += 1
                    percentage = processed_files / total_files * 100
                    self.update_status(f"Обработка: {processed_files}/{total_files} ({percentage:.1f}%)", processed_files, total_files)
                    result = self.process_image(filename, rules, images_path)
                    if result:
                        writer.writerow(result)
                        successful_files += 1
                        logging.info(f'Успешно обработан файл: {filename}0')
                except Exception as e:
                    pass  # postinserted
        temp_dir = os.path.join(self.output_folder, 'temp_images')
        if os.path.exists(temp_dir):
            shutil.rmtree(temp_dir)
            logging.info(f'Временная папка удалена: {temp_dir}')
        if os.path.exists(self.images_path_file):
            os.remove(self.images_path_file)
        final_percentage = successful_files / total_files * 100
        self.update_status(f"Завершено: {successful_files}/{total_files} ({final_percentage:.1f}%)", successful_files, total_files)
        logging.info(f'Обработка завершена. Успешно обработано {successful_files} из {total_files} файлов')
            logging.error(f'Ошибка при обработке файла {filename}: {str(e)}')
            logging.error(f'Трассировка ошибки: {traceback.format_exc()}0')

    def update_status(self, message, progress=None, additional_info='', is_error=False):
        status_text = message
        if additional_info:
            status_text += f' | {additional_info}'
        self.status_label.configure(text=status_text)
        if progress is not None:
            self.progress_bar.set(progress)
        if is_error:
            logging.error(message)
            messagebox.showerror('Ошибка', message)
        else:  # inserted
            logging.info(message)

class ConceptImageProcessor:
    def __init__(self, output_folder, progress_bar, status_label, root):
        self.output_folder = output_folder
        self.progress_bar = progress_bar
        self.status_label = status_label
        self.root = root
        self.api_key_path = resource_path('api_key.txt')
        self.license_path = resource_path('mishenin_license.txt')
        self.images_path_file = resource_path('images_path.txt')
        self.output_csv_path = os.path.join(self.output_folder, 'output.csv')
        self.client = self.init_openai_client()

    def init_openai_client(self):
        api_key = self.read_api_key()
        if not api_key:
            self.status_label.set('API ключ не найден. Пожалуйста, введите ключ.')
            logging.error('API ключ не найден.')
            return
        return OpenAI(api_key=api_key)

    def read_api_key(self):
        try:
            with open(self.api_key_path, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                self.status_label.set(f'Ошибка чтения API ключа: {e}0')
                logging.error(f'Ошибка чтения API ключа: {e}0')

    def read_rules(self, rules_file):
        try:
            with open(rules_file, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                self.status_label.set(f'Ошибка чтения файла правил: {e}0')
                logging.error(f'Ошибка чтения файла правил: {e}0')

    def read_images_path(self):
        try:
            with open(self.images_path_file, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                self.status_label.set(f'Ошибка чтения пути к изображениям: {e}0')
                logging.error(f'Ошибка чтения пути к изображениям: {e}0')

    def image_to_base64(self, image_path):
        try:
            with open(image_path, 'rb') as img_file:
                pass  # postinserted
        except Exception as e:
                return base64.b64encode(img_file.read()).decode('utf-8')
                logging.error(f'Ошибка при чтении изображения {image_path}: {e}')

    def extract_info_from_filename(self, file_path):
        filename = os.path.basename(file_path)
        filename_parts = re.split('[_\\s-]+', filename)
        filename_parts = [part for part in filename_parts if part]
        return ' '.join(filename_parts)

    def analyze_image(self, image_path, rules):
        base64_image = self.image_to_base64(image_path)
        if not base64_image:
            return
        file_info = self.extract_info_from_filename(image_path)
        rules += f'\nFile information: {file_info}'
        try:
            messages = [{'role': 'user', 'content': [{'type': 'text', 'text': rules}, {'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{base64_image}', 'detail': 'low'}}]}]
            response = self.client.chat.completions.create(model='gpt-4o-2024-08-06', messages=messages, max_tokens=400)
            logging.info(f'API response: {response}')
            return response
        except Exception as e:
            logging.error(f'Ошибка при обработке файла {image_path}: {e}')
        else:  # inserted
            pass

    def process_response(self, response):
        if response is None:
            return (None, None, None, '')
        try:
            content = response.choices[0].message.content
            logging.info(f'Content: {content}')
            content = content.replace('**', '')
            title_match = re.search('Title:\\s*\"?(.*?)\"?\\s*(?:$|\\n)', content)
            description_match = re.search('Description:\\s*(.*?)\\s*(?:$|\\n)', content)
            keywords_match = re.search('Keywords:\\s*\"?(.*?)\"?\\s*(?:$|\\n)', content)
            title = title_match.group(1).strip() if title_match else ''
            description = description_match.group(1).strip() if description_match else ''
            keywords = keywords_match.group(1).strip() if keywords_match else ''
            if title:
                title = title.lower().capitalize()
            if description:
                description = description.lower().capitalize()
            for field in [title, description, keywords]:
                if field:
                    for symbol in ['-', '--', '—']:
                        field = field.replace(symbol, '')
                        field = field.strip()
            if keywords:
                keywords = keywords.replace('.', '')
                keywords_list = [kw.strip() for kw in keywords.split(',')]
        except Exception as e:
                    keywords = ', '.join(keywords_list)
            if description:
                unwanted_phrases = ['An image capturing', 'This scene', 'This image captures', 'captures the', 'exemplifies', 'showcases the', 'highlighting']
                for phrase in unwanted_phrases:
                    description = description.replace(phrase, '')
                description = re.sub('\\s+', ' ', description).strip().capitalize()
            keywords_list = [kw.strip() for kw in keywords.split(',')]
                keywords = ', '.join(keywords_list)
                tokens_used = response.usage.total_tokens
                logging.info(f'Использовано токенов: {tokens_used}')
                return (title, description, keywords, '')
                logging.error(f'Ошибка при разборе ответа: {e}')
                return ('', '', '', '')

    def process_image(self, filename, rules, images_path):
        logging.info(f'Processing image: {filename}')
        full_path = os.path.join(images_path, filename)
        max_retries = 3
        retry_delay = 2
        for attempt in range(max_retries):
            try:
                if attempt > 0:
                    logging.info(f'Повторная попытка {attempt + 1} для файла {filename}0')
                file_info = self.extract_info_from_filename(full_path)
                enhanced_rules = rules + f'\nFile information: {file_info}'
                result = self.analyze_image(full_path, enhanced_rules)
                if result is None:
                    if attempt < max_retries - 1:
                        logging.warning(f'Попытка {attempt + 1} не удалась для {filename}, повторная попытка через {retry_delay} сек.')
                        time.sleep(retry_delay)
                        continue
                    logging.warning(f'Все попытки анализа для {filename} не удались')
                    self.status_label.set(f'Предупреждение: Не удалось проанализировать {filename}0')
                    return [filename, '', '', '', '']
            except UnicodeDecodeError as ude:
                title, description, keywords, release_name = self.process_response(result)
                if not any([title, description, keywords]):
                    if attempt < max_retries - 1:
                        logging.warning(f'Получены пустые результаты для {filename}, повторная попытка')
                        time.sleep(retry_delay)
                    else:  # inserted
                        continue
                    logging.warning(f'Не удалось получить данные для {filename} после всех попыток')
                    self.status_label.set(f'Предупреждение: Получены пустые результаты для {filename}0')
                    return [filename, '', '', '', '']
                else:  # inserted
                    logging.info(f'Успешно обработано изображение: {filename}')
                    logging.info(f'Title: {title}, Description: {description}, Keywords: {keywords}')
                    return [filename, title or '', description or '', keywords or '', release_name or '']
        else:  # inserted
            logging.error(f'Все попытки обработки файла {filename} не удались')
            return [filename, '', '', '', '']
            logging.error(f'Ошибка декодирования для файла {filename}: {str(ude)}')
            if attempt < max_retries - 1:
                time.sleep(retry_delay)
                continue
            return [filename, '', '', '', '']
        except Exception as e:
            logging.error(f'Ошибка при обработке {filename} (попытка {attempt + 1}): {str(e)}')
            logging.error(f'Трассировка ошибки: {traceback.format_exc()}0')
            if attempt < max_retries - 1:
                time.sleep(retry_delay)
                continue
            return [filename, '', '', '', '']

    def update_status(self, message, progress=None, additional_info='', is_error=False):
        """Обновление статуса обработки."""  # inserted
        status_text = message
        if additional_info:
            status_text += f' | {additional_info}'
        self.status_label.configure(text=status_text)
        if progress is not None:
            self.progress_bar.set(progress)
        if is_error:
            logging.error(message)
            messagebox.showerror('Ошибка', message)
        else:  # inserted
            logging.info(message)

    def process_images(self):
        """Основной метод обработки изображений"""  # inserted
        is_valid, message = self.root.license_manager.check_license_status()
        if not is_valid:
            self.update_status(f'License error: {message}0', is_error=True)
            return False
        if not self.client:
            self.update_status('Клиент OpenAI не инициализирован', is_error=True)
            return
        rules_path = resource_path('rules2.txt')
        rules = self.read_rules(rules_path)
        if not rules:
            self.update_status('Ошибка чтения правил', is_error=True)
            return
        images_path = self.read_images_path()
        if not images_path:
            self.update_status('Ошибка чтения пути к изображениям', is_error=True)
            return
        image_files = [filename for filename in os.listdir(images_path) if not filename.lower().endswith(('.png', '.jpg', '.jpeg'))]
        total_files = len(image_files)
        if total_files == 0:
            self.update_status('В указанной папке нет изображений', is_error=True)
            return
        with open(self.output_csv_path, mode='w', newline='', encoding='utf-8') as file:
            writer = csv.writer(file)
            writer.writerow(['Filename', 'Title', 'Description', 'Keywords', 'Release name'])
            processed = 0
            for filename in image_files:
                result = self.process_image(filename, rules, images_path)
                if result:
                    writer.writerow(result)
                    logging.info(f'Записан результат для файла: {result[0]}')
                processed += 1
                progress = processed / total_files
                self.update_status(f'Обработано {processed}/{total_files} файлов', progress)
        self.update_status('Обработка завершена', 1)
        if os.path.exists(self.images_path_file):
            os.remove(self.images_path_file)

class StockVideoProcessor:
    def __init__(self, output_folder, progress_bar, status_label, root):
        self.output_folder = output_folder
        self.progress_bar = progress_bar
        self.status_label = status_label
        self.root = root
        if getattr(sys, 'frozen', False):
            self.script_dir = os.path.dirname(sys.executable)
        else:  # inserted
            self.script_dir = os.path.dirname(os.path.abspath(__file__))
        self.api_key_path = resource_path('api_key.txt')
        self.license_path = resource_path('mishenin_license.txt')
        self.rules_path = resource_path('rules_video.txt')
        self.images_path_file = resource_path('images_path.txt')
        self.output_csv_path = os.path.join(self.output_folder, 'output.csv')
        self.temp_dir = None
        if sys.platform == 'darwin':
            self.exiftool_path = '/usr/local/bin/exiftool'
            if not os.path.exists(self.exiftool_path):
                self.exiftool_path = os.path.join(self.script_dir, 'exiftool')
        else:  # inserted
            self.exiftool_path = os.path.join(self.script_dir, 'exiftool.exe')
        self.logger = logging.getLogger(__name__)
        self.setup_logging()
        if not self.check_exiftool():
            try:
                self.exiftool_manager = ExifToolManager(self.script_dir)
                if self.exiftool_manager.ensure_exiftool():
                    self.exiftool_path = self.exiftool_manager.get_path()
                    logging.info(f'ExifTool успешно инициализирован: {self.exiftool_path}0')
                else:  # inserted
                    logging.error('Не удалось инициализировать ExifTool')
                    self.show_exiftool_error()
            except Exception as e:
                pass  # postinserted
        else:  # inserted
            pass  # postinserted
        self.frame_positions = [0.2, 0.4, 0.6, 0.8]
        self.target_height = 720
        self.max_retries = 3
        self.retry_delay = 2
        self.client = self.init_openai_client()
        self.images_path = self.read_images_path()
                logging.error(f'Ошибка при инициализации ExifTool: {str(e)}0')
                self.show_exiftool_error()

    def init_openai_client(self):
        """Инициализация клиента OpenAI."""  # inserted
        api_key = self.read_api_key()
        if not api_key:
            self.update_status('API ключ не найден. Пожалуйста, введите ключ.')
            logging.error('API ключ не найден.')
            return
        return OpenAI(api_key=api_key)

    def read_api_key(self):
        """Чтение API ключа из файла."""  # inserted
        try:
            with open(self.api_key_path, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                self.update_status(f'Ошибка чтения API ключа: {e}0', is_error=True)
                logging.error(f'Ошибка чтения API ключа: {e}0')

    def read_rules(self):
        """Чтение правил для видео из файла."""  # inserted
        try:
            with open(self.rules_path, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                self.update_status(f'Ошибка чтения правил: {e}0', is_error=True)
                logging.error(f'Ошибка чтения файла правил: {e}0')

    def read_images_path(self):
        """Чтение пути к папке с файлами."""  # inserted
        try:
            with open(self.images_path_file, 'r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                return file.read().strip()
                logging.error(f'Ошибка чтения пути к файлам: {e}0')

    def update_status(self, message, progress=None, additional_info='', is_error=False):
        """Обновление статуса обработки."""  # inserted
        status_text = message
        if additional_info:
            status_text += f' | {additional_info}'
        self.status_label.configure(text=status_text)
        if progress is not None:
            self.progress_bar.set(progress)
        if is_error:
            logging.error(message)
            messagebox.showerror('Ошибка', message)
        else:  # inserted
            logging.info(message)

    def extract_frames(self, video_path):
        cap = None
        try:
            cap = cv2.VideoCapture(str(video_path))
            if not cap.isOpened():
                raise Exception('Не удалось открыть видеофайл')
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            if total_frames <= 0:
                raise Exception('Некорректное количество кадров в видео')
            frame_positions = [int(total_frames * 0.1), int(total_frames * 0.5), int(total_frames * 0.9)]
            frames = []
            for position in frame_positions:
                best_frame = None
                best_quality = (-1)
                for offset in range((-2), 3):
                    frame_pos = min(max(position + offset, 0), total_frames - 1)
                    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_pos)
                    ret, frame = cap.read()
                    if ret:
                        quality = self.assess_frame_quality(frame)
                        if quality > best_quality:
                            pass  # postinserted
        except Exception as e:
                        else:  # inserted
                            best_quality = quality
                            best_frame = frame.copy()
                else:  # inserted
                    if best_frame is not None:
                        frames.append(best_frame)
                        self.update_status(f'Извлечен кадр {len(frames)}/3')
                    else:  # inserted
                        logging.warning(f'Не удалось получить качественный кадр на позиции {position}')
            else:  # inserted
                if len(frames) < 3:
                    raise Exception('Не удалось получить достаточное количество качественных кадров')
                if frames in cap is not None:
                    cap.release()
                logging.error(f'Ошибка при извлечении кадров: {str(e)}0')
                return

    def assess_frame_quality(self, frame):
        """Комплексная оценка качества кадра."""  # inserted
        try:
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            sharpness = cv2.Laplacian(gray, cv2.CV_64F).var()
            contrast = gray.std()
            mean_brightness = np.mean(gray)
            brightness_score = 1.0 - abs(mean_brightness - 128) / 128
            hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
            saturation = np.mean(hsv[:, :, 1])
            quality_score = 0.4 * sharpness / 100.0 + 0.3 * contrast / 128.0 + 0.2 * brightness_score + 0.1 * saturation / 255.0
            return quality_score
        except Exception as e:
            logging.error(f'Ошибка при оценке качества кадра: {str(e)}0')
            return (-1)

    def resize_frames(self, frames):
        """Изменение размера кадров для объединения."""  # inserted
        resized_frames = []
        for frame in frames:
            height, width = frame.shape[:2]
            ratio = self.target_height / height
            new_width = int(width * ratio)
            resized = cv2.resize(frame, (new_width, self.target_height), interpolation=cv2.INTER_LANCZOS4)
            resized_frames.append(resized)
        return resized_frames

    def combine_frames(self, frames):
        """Объединение кадров в одно изображение."""  # inserted
        try:
            if not frames or len(frames) < 3:
                raise Exception('Недостаточно кадров для объединения')
            resized_frames = self.resize_frames(frames)
            separator_width = 3
            separator = np.zeros((self.target_height, separator_width, 3), dtype=np.uint8)
            cv2.rectangle(separator, (0, 0), (separator_width, self.target_height), (30, 30, 30), (-1))
            combined = np.hstack([resized_frames[0], separator, resized_frames[1], separator, resized_frames[2]])
            if not self.temp_dir:
                self.temp_dir = tempfile.mkdtemp()
            output_path = os.path.join(self.temp_dir, 'combined_frames.jpg')
            encode_params = [cv2.IMWRITE_JPEG_QUALITY, 95]
            cv2.imwrite(output_path, combined, encode_params)
            return output_path
        except Exception as e:
            logging.error(f'Ошибка при объединении кадров: {str(e)}0')

    def image_to_base64(self, image_path):
        """Преобразование изображения в base64."""  # inserted
        try:
            with open(image_path, 'rb') as img_file:
                pass  # postinserted
        except Exception as e:
                return base64.b64encode(img_file.read()).decode('utf-8')
                logging.error(f'Ошибка при конвертации изображения в base64: {e}0')

    def analyze_video_content(self, combined_image_path, video_path):
        """Анализ видео контента с повторными попытками."""  # inserted
        for attempt in range(self.max_retries):
            try:
                rules = self.read_rules()
                if not rules:
                    raise Exception('Не удалось прочитать правила для анализа')
                video_info = self.get_video_info(video_path)
                enhanced_rules = f'{rules}\nVideo Info: {video_info}'
                base64_image = self.image_to_base64(combined_image_path)
                if not base64_image:
                    raise Exception('Ошибка преобразования изображения')
                messages = [{'role': 'user', 'content': [{'type': 'text', 'text': enhanced_rules}, {'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{base64_image}', 'detail': 'low'}}]}]
                response = self.client.chat.completions.create(model='gpt-4o-2024-08-06', messages=messages, max_tokens=500)
                return response
            except Exception as e:
                pass  # postinserted
            if attempt < self.max_retries - 1:
                logging.warning(f'Попытка {attempt + 1} не удалась: {str(e)}')
                time.sleep(self.retry_delay)
            else:  # inserted
                logging.error(f'Все попытки анализа не удались: {str(e)}0')
                return

    def get_video_info(self, video_path):
        cap = None
        try:
            cap = cv2.VideoCapture(video_path)
            fps = cap.get(cv2.CAP_PROP_FPS)
            frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            duration = frame_count / fps if fps > 0 else 0
            return f"Resolution: {width}x{height}, Duration: {duration:.2f}s, FPS: {fps}"
        except Exception as e:
            logging.error(f'Ошибка получения информации о видео: {str(e)}0')
            return 'Information not available'

    def process_response(self, response):
        """Обработка ответа API."""  # inserted
        if response is None:
            return
        try:
            content = response.choices[0].message.content
            logging.info(f'API response content: {content}')
            content = content.replace('**', '')
            title_match = re.search('Title:\\s*\"?(.*?)\"?\\s*(?:$|\\n)', content)
            keywords_match = re.search('Keywords:\\s*\"?(.*?)\"?\\s*(?:$|\\n)', content)
            title = title_match.group(1).strip() if title_match else ''
            if title:
                title = title.lower().capitalize()
                for symbol in ['-', '--', '—']:
                    title = title.replace(symbol, '').strip()
            keywords = keywords_match.group(1).strip() if keywords_match else ''
            if keywords:
                keywords = keywords.replace('.', '')
                keywords_list = [kw.strip() for kw in keywords.split(',')]
        except Exception as e:
                    keywords = ', '.join(keywords_list)
            return {'title': title, 'keywords': keywords}
                logging.error(f'Ошибка при обработке ответа API: {e}0')

    def save_results_to_csv(self, filename, results):
        """Сохранение результатов в CSV файл."""  # inserted
        try:
            with open(self.output_csv_path, mode='a', newline='', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                writer = csv.writer(file)
                writer.writerow([filename, results.get('title', ''), results.get('keywords', '')])
                logging.error(f'Ошибка при сохранении результатов: {e}')
                return False

    def process_keywords(self, keywords: str) -> List[str]:
        """\n        Обработка ключевых слов с удалением дубликатов и кавычек\n        """  # inserted
        if not keywords:
            return []
        unique_keywords = set()
        for keyword in keywords.split(','):
            cleaned = keyword.strip().strip('\"').strip('\'').strip().lower()
            if cleaned:
                unique_keywords.add(cleaned)
        return sorted(list(unique_keywords))

    def setup_logging(self):
        """Настройка логирования с отдельным файлом для видео."""  # inserted
        self.logger.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setFormatter(formatter)
        self.logger.addHandler(console_handler)
        log_path = os.path.join(self.script_dir, 'video_metadata.log')
        file_handler = logging.FileHandler(log_path, encoding='utf-8')
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

    def check_exiftool(self) -> bool:
        """Проверка наличия и работоспособности ExifTool"""  # inserted
        try:
            if not os.path.exists(self.exiftool_path):
                logging.error(f'ExifTool не найден по пути: {self.exiftool_path}0')
                return False
            result = subprocess.run([self.exiftool_path, '-ver'], capture_output=True, text=True, encoding='utf-8')
            if result.returncode == 0:
                self.logger.info(f'ExifTool версия: {result.stdout.strip()}')
                return True
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            self.logger.error(f'Ошибка ExifTool: {result.stderr}0')
            return False
                self.logger.error(f'Ошибка при проверке ExifTool: {str(e)}0')
                return False

    def process_folder_metadata(self, folder_path: str, csv_path: str):
        """Обработка метаданных для всей папки."""  # inserted
        try:
            if not os.path.exists(csv_path):
                self.update_status('CSV файл не найден', is_error=True)
                return
            metadata_dict = {}
            with open(csv_path, 'r', encoding='utf-8') as csvfile:
                pass  # postinserted
        except Exception as e:
                reader = csv.DictReader(csvfile)
                for row in reader:
                    metadata_dict[row['Filename']] = row
                    if not metadata_dict:
                        self.update_status('CSV файл пуст', is_error=True)
                        return
                    video_extensions = {'.mkv', '.mp4', '.avi', '.mov'}
                    processed = 0
                    failed = 0
                    total_files = len([f for f in os.listdir(folder_path) if os.path.splitext(f)[1].lower() in video_extensions])
                        for file_path in Path(folder_path).glob('*'):
                            if file_path.suffix.lower() in video_extensions:
                                filename = file_path.name
                                if filename in metadata_dict:
                                    metadata = metadata_dict[filename]
                                    success = self.embed_metadata(str(file_path), metadata.get('Title', ''), metadata.get('Description', ''), metadata.get('Keywords', ''))
                                    if success:
                                        processed += 1
                                        self.update_status(f'Обработано файлов: {processed}/{total_files}', processed / total_files, f'Файл: {filename}0')
                                    else:  # inserted
                                        failed += 1
                                        self.logger.error(f'Не удалось обработать файл: {filename}0')
                                else:  # inserted
                                    failed += 1
                                    self.logger.error(f'Не найдены метаданные для файла: {filename}0')
                        final_message = f'Обработка завершена:\nУспешно: {processed}\nС ошибками: {failed}'
                        self.update_status(final_message, 1)
                error_msg = f'Ошибка при обработке папки: {str(e)}'
                self.logger.error(error_msg)
                self.logger.error(f'Трассировка: {traceback.format_exc()}0')
                self.update_status(error_msg, is_error=True)
                return None

    def embed_metadata(self, video_path: str, title: str, description: str, keywords: str) -> bool:
        """Встраивает метаданные в видео файл"""  # inserted
        try:
            self.logger.info(f'Обработка файла: {video_path}0')
            if not os.path.exists(video_path):
                self.logger.error(f'Файл не найден: {video_path}0')
                return False
            if not self.check_exiftool():
                self.logger.error('ExifTool не доступен')
                return False
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            keyword_list = self.process_keywords(keywords)
            self.logger.debug(f'Обработанные ключевые слова: {keyword_list}0')
            clean_command = [self.exiftool_path, '-m', '-overwrite_original', '-all=', '-P', video_path]
            subprocess.run(clean_command, capture_output=True, text=True)
            command = [self.exiftool_path, '-m', '-charset', 'utf8', '-overwrite_original', '-P', f'-XMP-dc:Title={title}0', f'-XMP-dc:Description={description}0']
            for keyword in keyword_list:
                command.append(f'-XMP-dc:Subject+={keyword}')
            command.extend([f'-QuickTime:Title={title}0', f'-QuickTime:Description={description}0', f"-QuickTime:Keywords={', '.join(keyword_list)}", video_path])
            result = subprocess.run(command, capture_output=True, text=True, encoding='utf-8')
            if result.returncode == 0:
                self.logger.info(f'Метаданные успешно добавлены в {video_path}0')
                self.verify_metadata(video_path)
                return True
        else:  # inserted
            self.logger.error(f'Ошибка ExifTool: {result.stderr}0')
            return False
                self.logger.error(f'Ошибка при обработке видео: {str(e)}0')
                self.logger.error(f'Трассировка: {traceback.format_exc()}0')
                return False

    def verify_metadata(self, video_path: str):
        try:
            result = subprocess.run([self.exiftool_path, '-s', '-G', video_path], capture_output=True, text=True, encoding='utf-8')
            self.logger.debug(f'Метаданные после обработки:\n{result.stdout}0')
        except Exception as e:
            self.logger.error(f'Ошибка при проверке метаданных: {str(e)}0')

    def process_folder(self, folder_path: str, csv_path: str):
        try:
            with open(csv_path, 'r', encoding='utf-8') as csvfile:
                pass  # postinserted
        except Exception as e:
                reader = csv.DictReader(csvfile)
                metadata = next(reader)
                    video_extensions = {'.mkv', '.mp4', '.avi', '.mov'}
                    processed = 0
                    failed = 0
                    for file_path in Path(folder_path).glob('*'):
                        if file_path.suffix.lower() in video_extensions:
                            success = self.embed_metadata(str(file_path), metadata.get('Title', ''), metadata.get('Description', ''), metadata.get('Keywords', ''))
                            if success:
                                processed += 1
                            else:  # inserted
                                failed += 1
                    self.logger.info('\nОбработка завершена:')
                    self.logger.info(f'Успешно: {processed}0')
                    self.logger.info(f'С ошибками: {failed}0')
                self.logger.error(f'Ошибка при обработке папки: {str(e)}0')
                self.logger.error(f'Трассировка: {traceback.format_exc()}0')

    def process_videos(self):
        """Основной метод обработки видео."""  # inserted
        try:
            if not self.verify_requirements():
                return
            video_files = self.get_video_files()
            if not video_files:
                self.update_status('В указанной папке нет видеофайлов', is_error=True)
                return
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            total_files = len(video_files)
            self.create_output_csv()
            for i, filename in enumerate(video_files, 1):
                pass  # postinserted
            else:  # inserted
                try:
                    video_info = self.process_single_video(filename, i, total_files)
                    if video_info and all((key in video_info for key in ['title', 'keywords'])):
                        self.embed_metadata(os.path.join(self.images_path, filename), video_info['title'], '', video_info['keywords'])
                except Exception as e:
                    pass  # postinserted
                self.cleanup()
                self.update_status('Обработка завершена', 1)
                logging.error(f'Ошибка обработки видео {filename}: {str(e)}')
                if not self.should_continue_processing():
                    break
                self.handle_processing_error(e)

    def verify_requirements(self):
        """Проверка необходимых требований перед обработкой."""  # inserted
        if not os.path.exists(self.license_path):
            self.update_status('Лицензионный файл не найден', is_error=True)
            return False
        if not self.client:
            self.update_status('Клиент OpenAI не инициализирован', is_error=True)
            return False
        return True

    def get_video_files(self):
        """Получение списка видео файлов."""  # inserted
        return [f if f.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')) else f for f in os.listdir(self.images_path)]

    def create_output_csv(self):
        """Создание CSV файла для результатов."""  # inserted
        with open(self.output_csv_path, mode='w', newline='', encoding='utf-8') as file:
            writer = csv.writer(file)
            writer.writerow(['Filename', 'Title', 'Keywords'])

    def process_single_video(self, filename, current, total):
        """Обработка отдельного видео файла."""  # inserted
        try:
            self.update_status(f'Обработка {current}/{total}', current / total, f'Файл: {filename}0')
            video_path = os.path.join(self.images_path, filename)
            frames = self.extract_frames(video_path)
            if not frames:
                raise Exception(f'Не удалось извлечь кадры из {filename}')
            combined_path = self.combine_frames(frames)
            if not combined_path:
                raise Exception(f'Не удалось объединить кадры для {filename}')
            response = self.analyze_video_content(combined_path, video_path)
            if not response:
                raise Exception(f'Не удалось проанализировать {filename}')
            results = self.process_response(response)
            if results:
                self.save_results_to_csv(filename, results)
            else:  # inserted
                raise Exception(f'Не удалось обработать результаты анализа для {filename}')
        except Exception as e:
            logging.error(f'Ошибка при обработке видео {filename}: {str(e)}')
            raise
        else:  # inserted
            pass

    def cleanup(self):
        """Очистка временных файлов."""  # inserted
        try:
            if self.temp_dir and os.path.exists(self.temp_dir):
                shutil.rmtree(self.temp_dir)
                logging.info(f'Временная папка удалена: {self.temp_dir}0')
            if os.path.exists(self.images_path_file):
                os.remove(self.images_path_file)
                logging.info('Файл пути к изображениям удален')
        except Exception as e:
            logging.error(f'Ошибка при очистке временных файлов: {str(e)}0')

    def should_continue_processing(self):
        """Запрос продолжения обработки при ошибке."""  # inserted
        return messagebox.askyesno('Ошибка', 'Произошла ошибка при обработке файла. Продолжить обработку остальных файлов?')

    def handle_processing_error(self, error):
        """Обработка ошибок процесса."""  # inserted
        error_message = f'Произошла ошибка при обработке видео: {str(error)}'
        self.update_status(error_message, is_error=True)
        logging.error(error_message)
        logging.error(traceback.format_exc())

class ExifToolManager:
    REQUIRED_PERL_FILES = ['Config.pm', 'strict.pm', 'warnings.pm', 'vars.pm', 'constant.pm', 'Exporter.pm']
    EXIFTOOL_URLS = {'win32': 'https://exiftool.org/exiftool-12.99_64.zip', 'darwin': 'https://exiftool.org/ExifTool-12.99.pkg'}

    def __init__(self, script_dir):
        self.script_dir = script_dir
        self.platform = sys.platform
        self.temp_dir = None
        self.status_callback = None
        if self.platform == 'darwin':
            self.system_exiftool = '/usr/local/bin/exiftool'
            self.exiftool_dir = os.path.expanduser('~/Library/Application Support/ExifTool')
        else:  # inserted
            self.system_exiftool = None
            self.exiftool_dir = os.path.join(self.script_dir, 'exiftool_files')
        self.perl_lib_dir = os.path.join(self.exiftool_dir, 'lib')
        self.exiftool_path = self._get_default_path()
        os.makedirs(self.exiftool_dir, exist_ok=True)
        os.makedirs(self.perl_lib_dir, exist_ok=True)
        self._setup_environment()
        self.required_files = self._get_required_files()
        self.exiftool_path = self._get_default_path()

    def _setup_environment(self):
        """Настройка переменных окружения в зависимости от платформы"""  # inserted
        os.environ['PATH'] = f"{self.exiftool_dir}0{os.pathsep}{os.environ.get('PATH', '')}"
        if self.platform == 'darwin':
            os.environ['PATH'] = f"/usr/local/bin:{self.exiftool_dir}:{os.environ.get('PATH', '')}"
            os.environ['PERL5LIB'] = f'{self.perl_lib_dir}:/usr/local/lib/perl5'
        else:  # inserted
            if self.platform == 'win32':
                os.environ['PERL5LIB'] = self.perl_lib_dir
                os.environ['PERLLIB'] = self.perl_lib_dir
                os.environ['PERL_DLL'] = os.path.join(self.exiftool_dir, 'perl532.dll')

    def get_path(self):
        """Возвращает путь к исполняемому файлу ExifTool"""  # inserted
        if self.platform == 'darwin' and os.path.exists(self.system_exiftool):
            return self.system_exiftool
        return self.exiftool_path

    def _get_default_path(self):
        """Получаем путь к ExifTool для текущей платформы"""  # inserted
        if self.platform == 'darwin':
            if os.path.exists(self.system_exiftool):
                return self.system_exiftool
            return os.path.join(self.exiftool_dir, 'exiftool')
        return os.path.join(self.exiftool_dir, 'exiftool.exe')

    def _get_required_files(self):
        """Определяет необходимые файлы для работы ExifTool"""  # inserted
        if self.platform == 'darwin':
            return {'exiftool': self.get_path()}
        return {'exiftool.exe': os.path.join(self.exiftool_dir, 'exiftool.exe'), 'perl532.dll': os.path.join(self.exiftool_dir, 'perl532.dll'), 'libgcc_s_seh-1.dll': os.path.join(self.exiftool_dir, 'libgcc_s_seh-1.dll'), 'libwinpthread-1.dll': os.path.join(self.exiftool_dir, 'libwinpthread-1.dll'), 'libstdc++-6.dll': os.path.join(self.exiftool_dir, 'libstdc++-6.dll')}

    def _install_platform_specific(self, download_path):
        """Установка ExifTool в зависимости от платформы"""  # inserted
        if self.platform == 'darwin':
            return self._install_macos(download_path)
        return self._install_windows(download_path)

    def _install_macos(self, pkg_path):
        """Установка ExifTool на macOS"""  # inserted
        try:
            print('\nНачало установки ExifTool для macOS')
            print(f'Путь к пакету: {pkg_path}')
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            try:
                print('Проверка прав sudo...')
                test_cmd = ['sudo', '-n', 'true']
                subprocess.run(test_cmd, check=True)
                print('Права sudo доступны')
            except subprocess.CalledProcessError:
                pass  # postinserted
            else:  # inserted
                install_cmd = ['sudo', 'installer', '-pkg', pkg_path, '-target', '/', '-verbose']
                print(f"Выполнение команды: {' '.join(install_cmd)}0")
                result = subprocess.run(install_cmd, capture_output=True, text=True)
                print('\nРезультат установки:')
                if result.stdout:
                    print('Вывод:')
                    print(result.stdout)
                if result.stderr:
                    print('Ошибки:')
                    print(result.stderr)
                if result.returncode!= 0:
                    print(f'Ошибка: установка завершилась с кодом {result.returncode}')
                    return False
                print('\nПроверка установки в системе...')
                if not os.path.exists(self.system_exiftool):
                    print(f'Ошибка: ExifTool не найден в {self.system_exiftool}')
                    return False
            else:  # inserted
                print('Создание символической ссылки...')
                our_exiftool = os.path.join(self.exiftool_dir, 'exiftool')
                if os.path.exists(our_exiftool):
                    os.remove(our_exiftool)
                os.symlink(self.system_exiftool, our_exiftool)
                os.chmod(our_exiftool, 493)
                print('Установка на macOS успешно завершена')
                return True
                print('Требуются права sudo для установки')
                print('Запуск установщика может запросить пароль администратора')
                print(f'\nОшибка при установке на macOS: {str(e)}')
                print('Трассировка:')
                traceback.print_exc()
                return False

    def _install_windows(self, zip_path):
        """Установка ExifTool на Windows"""  # inserted
        try:
            logging.info('Начало распаковки архива')
            with zipfile.ZipFile(str(zip_path), 'r') as zip_ref:
                pass  # postinserted
        except Exception as e:
                zip_ref.extractall(str(self.temp_dir))
                logging.info('Архив успешно распакован')
                    extracted_dir = next((d for d in os.listdir(str(self.temp_dir)) if d.startswith('exiftool-') and os.path.isdir(os.path.join(str(self.temp_dir), d))))
                    logging.info(f'Найдена распакованная директория: {extracted_dir}')
                    source_dir = str(os.path.join(self.temp_dir, extracted_dir))
                    target_dir = str(self.exiftool_dir)
                    logging.info(f'Копирование файлов из {source_dir} в {target_dir}')
                    files_copied = 0
                    for item in os.listdir(source_dir):
                        s = str(os.path.join(source_dir, item))
                        d = str(os.path.join(target_dir, item))
                        if os.path.exists(d):
                            if os.path.isdir(d):
                                logging.info(f'Удаление существующей директории: {d}')
                                shutil.rmtree(d)
                            else:  # inserted
                                logging.info(f'Удаление существующего файла: {d}')
                                os.remove(d)
                        if os.path.isdir(s):
                            logging.info(f'Копирование директории: {item}')
                            shutil.copytree(s, d)
                        else:  # inserted
                            logging.info(f'Копирование файла: {item}')
                            shutil.copy2(s, d)
                        files_copied += 1
                    logging.info(f'Скопировано файлов: {files_copied}')
                    old_exe = str(os.path.join(target_dir, 'exiftool(-k).exe'))
                    new_exe = str(os.path.join(target_dir, 'exiftool.exe'))
                    if os.path.exists(old_exe):
                        logging.info('Переименование exiftool(-k).exe в exiftool.exe')
                        if os.path.exists(new_exe):
                            os.remove(new_exe)
                        os.rename(old_exe, new_exe)
                    logging.info('Установка Windows успешно завершена')
                    return True
                logging.error(f'Ошибка установки на Windows: {str(e)}0')
                logging.error(f'Трассировка: {traceback.format_exc()}0')
                return False

    def _download_and_install(self):
        """Скачивание и установка ExifTool"""  # inserted
        try:
            if self.platform not in self.EXIFTOOL_URLS:
                print(f'Ошибка: неподдерживаемая платформа {self.platform}')
        except requests.exceptions.RequestException as e:
            else:  # inserted
                break
                if self.temp_dir:
                    if os.path.exists(self.temp_dir):
                        try:
                            shutil.rmtree(self.temp_dir)
                            print('\nВременные файлы удалены')
                            return False
                        except Exception as e:
                            pass  # postinserted
                    else:  # inserted
                        return False
                else:  # inserted
                    return False
            else:  # inserted
                print('\n=== Начало установки ExifTool ===')
                print(f'Платформа: {self.platform}')
                self.temp_dir = tempfile.mkdtemp(prefix='exiftool_temp_')
                print(f'Создана временная директория: {self.temp_dir}')
                url = self.EXIFTOOL_URLS[self.platform]
                download_path = os.path.join(self.temp_dir, os.path.basename(url))
                print('\nЗагрузка ExifTool:')
                print(f'URL: {url}')
                print('Начало загрузки...')
                response = requests.get(url, stream=True, timeout=30)
                response.raise_for_status()
                total_size = int(response.headers.get('content-length', 0))
                print(f'Размер файла: {total_size / 1024 / 1024:.2f} MB')
                downloaded = 0
                with open(download_path, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
                        downloaded += len(chunk)
                        if total_size:
                            percent = downloaded / total_size * 100
                            print(f"Загружено: {downloaded / 1024 / 1024:.2f} MB ({percent:.1f}%)", end='\r')
                        print('\nЗагрузка завершена')
                        print(f'Файл сохранен: {download_path}')
                        print('\n=== Установка ===')
                        if self.platform == 'darwin':
                            print('Запуск установки для macOS...')
                            success = self._install_macos(download_path)
                        else:  # inserted
                            print('Запуск установки для Windows...')
                            success = self._install_windows(download_path)
                        if success:
                            print('\nУстановка успешно завершена')
                            if self._verify_installation():
                                print('Проверка установки успешна')
                            else:  # inserted
                                break
                                if self.temp_dir:
                                    if os.path.exists(self.temp_dir):
                                        try:
                                            shutil.rmtree(self.temp_dir)
                                            print('\nВременные файлы удалены')
                                            return True
                                        except Exception as e:
                                            pass  # postinserted
                            else:  # inserted
                                print('Ошибка: проверка установки не удалась')
                            else:  # inserted
                                if self.temp_dir:
                                    if os.path.exists(self.temp_dir):
                                        try:
                                            shutil.rmtree(self.temp_dir)
                                            print('\nВременные файлы удалены')
                                            return False
                            except Exception as e:
                                    else:  # inserted
                                        return False
                                else:  # inserted
                                    return False
                        else:  # inserted
                            print('\nОшибка: установка не удалась')
                        else:  # inserted
                            if self.temp_dir:
                                if os.path.exists(self.temp_dir):
                                    try:
                                        shutil.rmtree(self.temp_dir)
                                        print('\nВременные файлы удалены')
                                        return False
                        except Exception as e:
                                else:  # inserted
                                    return False
                            else:  # inserted
                                return False
                print(f'Предупреждение: ошибка при удалении временных файлов: {str(e)}')
                return False
                print(f'Предупреждение: ошибка при удалении временных файлов: {str(e)}')
                return True
                print(f'Предупреждение: ошибка при удалении временных файлов: {str(e)}')
                return False
                print(f'Предупреждение: ошибка при удалении временных файлов: {str(e)}')
                return False
                print(f'\nОшибка при загрузке файла: {str(e)}')
                return False
                except Exception as e:
                    print(f'Предупреждение: ошибка при удалении временных файлов: {str(e)}')
                    return False
                return False
                else:  # inserted
                    return False
            except Exception as e:
                print(f'\nОшибка при установке ExifTool: {str(e)}')
                if hasattr(e, '__traceback__'):
                    traceback.print_exc()
                break
                    print('\nВременные файлы удалены')
                    print(f'Предупреждение: ошибка при удалении временных файлов: {str(e)}0')

    def _verify_installation(self):
        """Проверка установки ExifTool"""  # inserted
        try:
            print('\n=== Проверка установки ExifTool ===')
            exiftool_path = self.get_path()
            print(f'Путь к ExifTool: {exiftool_path}')
            if not os.path.exists(exiftool_path):
                print(f'Ошибка: файл не найден: {exiftool_path}')
                return False
            print('Проверка версии ExifTool...')
            cmd = [exiftool_path, '-ver']
            print(f"Выполнение команды: {' '.join(cmd)}0")
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=5, env=os.environ.copy())
            if result.returncode == 0:
                version = result.stdout.strip()
                print(f'ExifTool версия {version} успешно проверена')
                return True
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            print(f'Ошибка: команда вернула код {result.returncode}')
            if result.stderr:
                print(f'Ошибка: {result.stderr}')
            return False
                print(f'\nОшибка при проверке установки: {str(e)}')
                print('Трассировка:')
                traceback.print_exc()
                return False

    def ensure_exiftool(self):
        """Проверяет наличие и работоспособность ExifTool"""  # inserted
        try:
            if self._verify_installation():
                return True
            return self._download_and_install()
        except Exception as e:
            logging.error(f'Ошибка при проверке ExifTool: {str(e)}0')
            return False
        else:  # inserted
            pass

class ImprovedMisheninApp(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.THEME_COLOR = '#3A7CA5'
        if getattr(sys, 'frozen', False):
            self.script_dir = os.path.dirname(sys.executable)
        else:  # inserted
            self.script_dir = os.path.dirname(os.path.abspath(__file__))
        self.title('Mishenin App')
        self.geometry('1100x700')
        self.configure(fg_color='#1E1E1E')
        self.overrideredirect(True)
        self._drag_start_x = 0
        self._drag_start_y = 0
        self.logger = logging.getLogger(__name__)
        self.setup_logging()
        self.license_manager = LicenseManager()
        try:
            self.exiftool_manager = ExifToolManager(self.script_dir)
            if not self.exiftool_manager.ensure_exiftool():
                self.logger.error('Не удалось инициализировать ExifTool')
            else:  # inserted
                self.exiftool_path = self.exiftool_manager.get_path()
                self.logger.info(f'ExifTool успешно инициализирован: {self.exiftool_path}0')
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            self.selected_script = None
            self.images_path = None
            self.output_folder = None
            self.progress = None
            self.grid_columnconfigure(0, weight=1)
            self.grid_rowconfigure(1, weight=1)
            self.create_titlebar()
            self.create_main_content()
            self.after(100, self.check_initial_setup)
            self.logger.error(f'Ошибка при инициализации ExifTool: {str(e)}0')
            self.exiftool_path = None

    def check_initial_setup(self):
        """Проверка начальной настройки без повторной инициализации интерфейса"""  # inserted
        self.update_license_status()
        if not self.exiftool_path:
            self.after(500, self.show_exiftool_error)

    def create_license_window(self):
        key_entry = ctk.CTkToplevel(self)
        key_entry.title('License Activation')
        key_entry.geometry('500x400')
        key_entry.configure(fg_color='#1E1E1E')
        id_frame = ctk.CTkFrame(key_entry, fg_color='#252526')
        id_frame.pack(padx=20, pady=10, fill='x')
        self = self.license_manager.get_hardware_id()
        ctk.CTkLabel(id_frame, text='Device ID:', font=('Segoe UI', 12), text_color='#E0E0E0').pack(pady=5)
        id_label = ctk.CTkLabel(id_frame, text=self, font=('Courier', 14), text_color='#FFFFFF')
        id_label.pack(pady=5)
        id_buttons_frame = ctk.CTkFrame(id_frame, fg_color='#252526')
        id_buttons_frame.pack(pady=10)
        copy_btn = ctk.CTkButton(id_buttons_frame, text='Copy ID', command=lambda: pyperclip.copy(device_id), width=100, fg_color='#3A7CA5', hover_color='#2C5F7F', text_color='#FFFFFF')
        copy_btn.pack(side=tk.LEFT, padx=5)
        key_frame = ctk.CTkFrame(key_entry, fg_color='#252526')
        key_frame.pack(padx=20, pady=10, fill='x')
        ctk.CTkLabel(key_frame, text='License Key:', font=('Segoe UI', 12), text_color='#E0E0E0').pack(pady=5)
        device_id = ctk.CTkEntry(key_frame, width=400, fg_color='#2D2D2D', text_color='#FFFFFF')
        device_id.pack(pady=5)
        key_buttons_frame = ctk.CTkFrame(key_frame, fg_color='#252526')
        key_buttons_frame.pack(pady=10)

        def paste_key():
            try:
                clipboard_text = license_window.clipboard_get()
                key_entry.delete(0, tk.END)
                key_entry.insert(0, clipboard_text)
            except:
                return None
        paste_btn = ctk.CTkButton(key_buttons_frame, text='Paste Key', command=license_window, width=100, fg_color='#3D3D3D', hover_color='#4A4A4A', text_color='#E0E0E0')
        paste_btn.pack(side=tk.LEFT, padx=5)

        def activate():
            key = key_entry.get().strip()
            if not key:
                status_label.configure(text='Please enter a license key', text_color='#FF6B6B')
                return
            success, message = self.license_manager.activate_license(key)
            if success:
                status_label.configure(text='License activated successfully!', text_color='#4CAF50')
                self.update_license_status()
                if hasattr(self, 'control_frame'):
                    for widget in self.control_frame.winfo_children():
                        if isinstance(widget, ctk.CTkButton) and widget.cget('text') == 'Start Processing':
                            widget.configure(fg_color=self.THEME_COLOR, hover_color='#2C5F7F')
                license_window.after(1500, license_window.destroy)
            else:  # inserted
                status_label.configure(text=f'Activation failed: {message}0', text_color='#FF6B6B')
        activate_btn = ctk.CTkButton(key_buttons_frame, text='Activate License', command=activate, width=150, fg_color='#3A7CA5', hover_color='#2C5F7F', text_color='#FFFFFF')
        activate_btn.pack(side=tk.LEFT, padx=10)
        status_frame = ctk.CTkFrame(key_entry, fg_color='#252526')
        status_frame.pack(padx=20, pady=10, fill='x', expand=True)
        paste_key = ctk.CTkLabel(status_frame, text='', font=('Segoe UI', 12), text_color='#E0E0E0', wraplength=350)
        paste_key.pack(pady=10)
        key_entry.update_idletasks()
        x = self.winfo_x() + self.winfo_width() // 2 - key_entry.winfo_width() // 2
        y = self.winfo_y() + self.winfo_height() // 2 - key_entry.winfo_height() // 2
        key_entry.geometry(f'+{x}+{y}')
        key_entry.transient(self)
        key_entry.grab_set()

        def handle_key_event(event):
            if event.state & 4:
                if event.keysym.lower() == 'v':
                    paste_key()
                else:  # inserted
                    if event.keysym.lower() == 'c':
                        pyperclip.copy(device_id)
        key_entry.bind('<Key>', handle_key_event)

    def update_license_status(self):
        """Обновляет отображение статуса лицензии в интерфейсе"""  # inserted
        try:
            is_valid, message = self.license_manager.check_license_status()
            license_info = self.license_manager.get_license_info()
            if license_info and 'expiry_date' in license_info:
                expiry_date = datetime.strptime(license_info['expiry_date'], '%Y-%m-%d')
                days_left = (expiry_date - datetime.now()).days
                if days_left <= 0:
                    expiry_text = f"License expired on {license_info['expiry_date']}"
                    text_color = '#FF8888'
                else:  # inserted
                    expiry_text = f"License valid until: {license_info['expiry_date']} ({days_left} days left)"
                    text_color = '#88FF88'
            else:  # inserted
                expiry_text = 'License status: ' + message
                text_color = '#888888'
            if hasattr(self, 'license_status_label'):
                self.license_status_label.configure(text=expiry_text, text_color=text_color)
        except Exception as e:
            self.logger.error(f'Error updating license status: {e}0')
            if hasattr(self, 'license_status_label'):
                self.license_status_label.configure(text='Error checking license status', text_color='#FF8888')

    def setup_logging(self):
        """Настройка логирования."""  # inserted
        self.logger.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setFormatter(formatter)
        self.logger.addHandler(console_handler)
        log_path = os.path.join(self.script_dir, 'metadata.log')
        file_handler = logging.FileHandler(log_path, encoding='utf-8')
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)
        self.logger.info('Приложение запущено')
        self.logger.info(f'Рабочая директория: {self.script_dir}0')

    def show_initialization_message(self):
        """Показ сообщения об инициализации без блокировки интерфейса"""  # inserted

        def show_message():
            messagebox.showinfo('Инициализация', 'Начинается проверка и установка необходимых компонентов.\nЭто может занять некоторое время в зависимости от скорости интернета.\n\nПожалуйста, подождите...')
        self.after(200, show_message)

    def create_titlebar(self):
        titlebar = ctk.CTkFrame(self, fg_color='#252526', height=40, corner_radius=0)
        titlebar.grid(row=0, column=0, sticky='ew')
        titlebar.grid_columnconfigure(1, weight=1)
        logo_frame = ctk.CTkFrame(titlebar, fg_color='#252526', width=200, height=40)
        logo_frame.grid(row=0, column=0, padx=(20, 0), pady=0, sticky='w')
        logo_frame.grid_propagate(False)
        logo = ctk.CTkLabel(logo_frame, text='M', font=('Segoe UI', 28, 'bold'), text_color=self.THEME_COLOR)
        logo.place(x=18, y=2)
        app_name = ctk.CTkLabel(logo_frame, text='ishenin App', font=('Segoe UI', 12, 'bold'), text_color='#E8E8E8')
        app_name.place(x=44, y=15)
        version_label = ctk.CTkLabel(titlebar, text='v1.0.0.1', font=('Segoe UI', 12), text_color='#888888')
        version_label.grid(row=0, column=2, padx=10, pady=5, sticky='e')
        control_frame = ctk.CTkFrame(titlebar, fg_color='#252526')
        control_frame.grid(row=0, column=3, padx=(0, 5), pady=5, sticky='e')
        for symbol, command in [('─', self.minimize_window), ('□', self.maximize_window), ('×', self.close_window)]:
            btn = self.create_titlebar_button(control_frame, symbol, command)
            btn.pack(side='left', padx=(0, 5))
        titlebar.bind('<ButtonPress-1>', self.start_window_move)
        titlebar.bind('<B1-Motion>', self.on_window_move)

    def show_exiftool_error(self):
        """Показывает сообщение об ошибке ExifTool с вариантами действий."""  # inserted
        response = messagebox.askretrycancel('Ошибка ExifTool', 'Не удалось инициализировать ExifTool.\n\nПриложение может работать в ограниченном режиме без обработки метаданных.\n\nХотите попробовать переустановить ExifTool?', icon='warning')
        if response:
            self.reinstall_exiftool()

    def check_exiftool_availability(self):
        """Проверяет доступность ExifTool перед операциями с метаданными."""  # inserted
        if not self.exiftool_path or not os.path.exists(self.exiftool_path):
            messagebox.showwarning('Предупреждение', 'ExifTool не доступен. Функции обработки метаданных будут ограничены.')
            return False
        return True

    def create_main_content(self):
        main_frame = ctk.CTkFrame(self, fg_color='#1E1E1E', corner_radius=0)
        main_frame.grid(row=1, column=0, sticky='nsew')
        main_frame.grid_columnconfigure(0, weight=1)
        main_frame.grid_rowconfigure(1, weight=1)
        self.control_frame = ctk.CTkFrame(main_frame, fg_color='#2D2D2D', corner_radius=10)
        self.control_frame.grid(row=0, column=0, padx=20, pady=15, sticky='ew')
        self.control_frame.grid_columnconfigure(4, weight=1)
        self.script_var = ctk.StringVar(value='classic')
        classic_btn = ctk.CTkRadioButton(self.control_frame, text='Classic', variable=self.script_var, value='classic', font=('Segoe UI', 14), fg_color=self.THEME_COLOR, border_color='#B3B3B3', text_color='#E0E0E0', hover_color='#2C5F7F')
        classic_btn.grid(row=0, column=0, padx=(20, 10), pady=10, sticky='w')
        concept_btn = ctk.CTkRadioButton(self.control_frame, text='Concept', variable=self.script_var, value='concept', font=('Segoe UI', 14), fg_color=self.THEME_COLOR, border_color='#B3B3B3', text_color='#E0E0E0', hover_color='#2C5F7F')
        concept_btn.grid(row=0, column=1, padx=10, pady=10, sticky='w')
        video_btn = ctk.CTkRadioButton(self.control_frame, text='Video', variable=self.script_var, value='video', font=('Segoe UI', 14), fg_color=self.THEME_COLOR, border_color='#B3B3B3', text_color='#E0E0E0', hover_color='#2C5F7F')
        video_btn.grid(row=0, column=2, padx=10, pady=10, sticky='w')
        start_btn = ctk.CTkButton(self.control_frame, text='Start Processing', font=('Segoe UI', 14, 'bold'), fg_color=self.THEME_COLOR, hover_color='#2C5F7F', corner_radius=5, height=36, command=self.start_script)
        start_btn.grid(row=0, column=3, padx=10, pady=10)
        import_btn = self.create_button(self.control_frame, 'Import CSV', 4, self.embed_exif_metadata)
        api_btn = self.create_button(self.control_frame, 'API Key', 5, self.set_api_key)
        license_btn = self.create_button(self.control_frame, 'License', 6, self.create_license_window)
        api_btn.grid(row=0, column=5, padx=(0, 20), pady=10, sticky='e')
        license_btn.grid(row=0, column=6, padx=(0, 20), pady=10, sticky='e')
        content_frame = ctk.CTkFrame(main_frame, fg_color='#252526', corner_radius=10)
        content_frame.grid(row=1, column=0, padx=20, pady=(10, 20), sticky='nsew')
        content_frame.grid_columnconfigure(0, weight=1)
        content_frame.grid_rowconfigure(1, weight=1)
        status_frame = ctk.CTkFrame(content_frame, fg_color='#2D2D2D', corner_radius=5)
        status_frame.grid(row=0, column=0, padx=15, pady=15, sticky='ew')
        status_frame.grid_columnconfigure(1, weight=1)
        self.status_label = ctk.CTkLabel(status_frame, text='Ready to process', font=('Segoe UI', 13), text_color='#B3B3B3')
        self.status_label.grid(row=0, column=0, padx=15, sticky='w')
        self.progress_bar = ctk.CTkProgressBar(status_frame, height=6, corner_radius=3, progress_color=self.THEME_COLOR)
        self.progress_bar.grid(row=0, column=1, padx=15, sticky='ew')
        self.progress_bar.set(0)
        self.log_area = ctk.CTkTextbox(content_frame, font=('Segoe UI', 13), text_color='#E0E0E0', fg_color='#1E1E1E', border_width=1, border_color='#3E3E3E', corner_radius=5)
        self.log_area.grid(row=1, column=0, padx=15, pady=(0, 15), sticky='nsew')
        self.show_instructions()
        footer_frame = ctk.CTkFrame(main_frame, fg_color='#252526', corner_radius=0, height=40)
        footer_frame.grid(row=2, column=0, sticky='ew')
        footer_frame.grid_columnconfigure(0, weight=1)
        self.footer_frame = ctk.CTkFrame(main_frame, fg_color='#252526', corner_radius=0, height=40)
        self.footer_frame.grid(row=2, column=0, sticky='ew')
        self.footer_frame.grid_columnconfigure(0, weight=1)
        expiry_text = 'License status: checking...'
        self.license_status_label = ctk.CTkLabel(self.footer_frame, text=expiry_text, font=('Segoe UI', 11), text_color='#888888')
        self.license_status_label.grid(row=0, column=0, padx=20, pady=5, sticky='w')
        expiry_text = 'License not found'
        if os.path.exists(resource_path('license.json')):
            try:
                with open(resource_path('license.json'), 'r') as f:
                    pass  # postinserted
            except Exception as e:
                    data = json.load(f)
                    expiry_text = f"License valid until: {data['expiry_date']}"
        else:  # inserted
            pass  # postinserted
        license_label = ctk.CTkLabel(footer_frame, text=expiry_text, font=('Segoe UI', 11), text_color='#888888')
        license_label.grid(row=0, column=0, padx=20, pady=5, sticky='w')
                    logging.error(f'Error reading license info: {e}')
                    expiry_text = 'Error reading license info'

    def embed_exif_metadata(self):
        try:
            self.images_path = filedialog.askdirectory(title='Выберите папку с файлами')
            if not self.images_path:
                self.update_status('Не выбрана папка с файлами', 0, 1)
            else:  # inserted
                self.csv_file = filedialog.askopenfilename(title='Выберите CSV файл', filetypes=[('CSV файлы', '*.csv')])
                if not self.csv_file:
                    self.update_status('Не выбран CSV файл', 0, 1)
        except Exception as e:
                else:  # inserted
                    if self.script_var.get() == 'video':
                        processor = StockVideoProcessor(self.output_folder or self.images_path, self.progress_bar, self.status_label, self)
                        processor.process_folder_metadata(self.images_path, self.csv_file)
                        return
                else:  # inserted
                    self.update_status('Подготовка к обработке', 0, 1)
                    data = self.read_csv(self.csv_file)
                    if not data:
                        self.update_status('Ошибка чтения CSV файла или файл пуст', 0, 1)
                        return
                else:  # inserted
                    total_files = len(data)
                    self.update_status(f'Найдено файлов для обработки: {total_files}0', 0, 1)
                    with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as temp_file:
                        processed = 0
                        skipped = 0
                        for record in data:
                            try:
                                image_filename = record.get('Filename', '').strip()
                                if not image_filename:
                                    logging.warning('Пропущена запись с пустым именем файла')
                                    skipped += 1
                                    continue
                                image_path = os.path.join(self.images_path, image_filename)
                                if not os.path.exists(image_path):
                                    logging.warning(f'Файл не найден: {image_path}')
                                    skipped += 1
                                    continue
                            except Exception as e:
                                pass  # postinserted
                            else:  # inserted
                                title = record.get('Title', '').strip()
                                description = self.get_description(title, record.get('Description', '').strip())
                                if description and (not description.endswith('.')):
                                    description += '.'
                                raw_keywords = record.get('Keywords', '').split(',')
                                keywords = set()
                                for keyword in raw_keywords:
                                    keyword = ' '.join(keyword.replace('-', ' ').split())
                                    if keyword:
                                        pass  # postinserted
                        else:  # inserted
                            keywords.add(keyword)
                        temp_file.write(f'\n            {image_path}\n            -charset=utf8\n            -overwrite_original\n            -preserve\n            -P\n            -codedcharacterset=utf8\n            -all=\n            ')
                        if title:
                            temp_file.write(f'\n            -XMP-dc:Title={title}\n            -IPTC:ObjectName={title}\n            -EXIF:ImageDescription={title}\n            ')
                        if description:
                            temp_file.write(f'\n            -XMP-dc:Description={description}\n            -IPTC:Caption-Abstract={description}\n            -EXIF:UserComment={description}\n            ')
                        for keyword in keywords:
                            temp_file.write(f'-XMP-dc:Subject+={keyword}\n')
                            temp_file.write(f'-IPTC:Keywords+={keyword}\n')
                        if keywords:
                            windows_keywords = '; '.join(keywords)
                            temp_file.write(f'\n            -XPTitle={title}\n            -XPComment={description}\n            -XPKeywords={windows_keywords}\n            ')
                        temp_file.write('-execute\n\n')
                        processed += 1
                        self.update_status(f'Подготовка файлов: {processed}/{total_files}0', processed, total_files)
                            self.update_status('Применение метаданных...', processed, total_files)
                        else:  # inserted
                            try:
                                cmd = [self.exiftool_path, '-charset', 'utf8', '-@', temp_file.name]
                                result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
                                with open(temp_file.name, 'r', encoding='utf-8') as f:
                                    pass  # postinserted
                            except Exception as e:
                                    logging.debug(f'Содержимое командного файла:\n{f.read()}0')
                                        if result.returncode == 0:
                                            success_message = f'Обработка завершена\nУспешно обработано: {processed}\nПропущено: {skipped}'
                                            self.update_status(success_message, total_files, total_files)
                                            logging.info(success_message)
                                            messagebox.showinfo('Успех', success_message)
                                        else:  # inserted
                                            error_message = f'Ошибка при обработке: {result.stderr}'
                                            logging.error(error_message)
                                            self.update_status('Ошибка обработки', 0, total_files)
                                            messagebox.showerror('Ошибка', error_message)
                                    else:  # inserted
                                        try:
                                            os.unlink(temp_file.name)
                                            logging.info('Временный файл команд удален')
                            logging.error(f'Ошибка при подготовке файла {image_filename}: {str(e)}')
                            skipped += 1
                            error_message = f'Ошибка при выполнении ExifTool: {str(e)}'
                            logging.error(error_message)
                            logging.error(traceback.format_exc())
                            self.update_status('Ошибка обработки', 0, total_files)
                            messagebox.showerror('Ошибка', error_message)
                            logging.warning(f'Не удалось удалить временный файл: {str(e)}0')
                        error_message = f'Общая ошибка: {str(e)}'
                        logging.error(error_message)
                        logging.error(traceback.format_exc())
                        self.update_status(error_message, 0, 1)
                        messagebox.showerror('Ошибка', error_message)

    def create_titlebar_button(self, parent, text, command):
        return ctk.CTkButton(parent, text=text, width=35, height=35, corner_radius=0, fg_color='transparent', hover_color='#3E3E3E', text_color='#E8E8E8', font=('Segoe UI', 14, 'bold'), command=command)

    def create_radio_button(self, parent, text, value, column):
        btn = ctk.CTkRadioButton(parent, text=text, variable=self.script_var, value=value, font=('Segoe UI', 14), fg_color=self.THEME_COLOR, border_color='#B3B3B3', text_color='#E0E0E0', hover_color='#2C5F7F')
        btn.grid(row=0, column=column, padx=(20, 10), pady=10, sticky='w')
        return btn

    def create_button(self, parent, text, column, command):
        btn = ctk.CTkButton(parent, text=text, font=('Segoe UI', 14), fg_color='#3D3D3D', hover_color='#4A4A4A', text_color='#E0E0E0', corner_radius=5, height=36, command=command)
        btn.grid(row=0, column=column, padx=10, pady=10)
        return btn

    def show_instructions(self):
        instructions = 'Instructions:\n\n1. Для первого запуска вставьте свой Api ключ\n2. Включите VPN на компьютере (не в браузере) если он вам необходим\n3. Выберите режим работы Classic, Concept или Video и нажмите старт\n4. Выберите папку где находятся файлы и куда сохранить итоговый csv\n5. Для сохранения информации в файл нажмите Import CSV (функция находится в beta тестировании, делайте бэкапы данных перед использованием)'
        self.log_area.configure(state='normal')
        self.log_area.delete('1.0', END)
        self.log_area.insert('1.0', instructions)
        self.log_area.configure(state='disabled')

    def reinstall_exiftool(self):
        """Пытается переустановить ExifTool."""  # inserted
        try:
            if os.path.exists(self.exiftool_manager.exiftool_path):
                os.remove(self.exiftool_manager.exiftool_path)
            exiftool_dir = os.path.join(self.script_dir, 'exiftool_files')
            if os.path.exists(exiftool_dir):
                shutil.rmtree(exiftool_dir)
                logging.info('Удалена старая установка')
            self.exiftool_manager = ExifToolManager(self.script_dir)
            if self.exiftool_manager.ensure_exiftool():
                self.exiftool_path = self.exiftool_manager.get_path()
                messagebox.showinfo('Успех', 'ExifTool успешно переустановлен.')
                logging.info('ExifTool успешно переустановлен')
                return True
            raise Exception('Не удалось установить ExifTool')
        except Exception as e:
            error_msg = f'Ошибка при переустановке ExifTool: {str(e)}'
            logging.error(error_msg)
            messagebox.showerror('Ошибка', f'Не удалось переустановить ExifTool.\n{str(e)}0')
            return False

    def start_window_move(self, event):
        self._drag_start_x = event.x
        self._drag_start_y = event.y

    def on_window_move(self, event):
        x = self.winfo_x() + (event.x - self._drag_start_x)
        y = self.winfo_y() + (event.y - self._drag_start_y)
        self.geometry(f'+{x}+{y}')

    def minimize_window(self):
        self.iconify()

    def maximize_window(self):
        if self.wm_state() == 'zoomed':
            self.wm_state('normal')
        else:  # inserted
            self.wm_state('zoomed')

    def close_window(self):
        self.destroy()

    def start_script(self):
        """Запуск обработки с обязательной проверкой лицензии"""  # inserted
        is_valid, message = self.license_manager.check_license_status()
        if not is_valid:
            messagebox.showerror('License Required', f'Valid license is required to process files.\n\nStatus: {message}0')
            self.create_license_window()
            return False
        self.selected_script = self.script_var.get()
        if self.selected_script:
            self.update_status('Starting', 0, 1, f'Starting {self.selected_script} script')
            thread = threading.Thread(target=self.execute_script)
            thread.start()
            return True
        self.update_status('Error', 0, 1, 'Please select a script first.')
        return False
    pass
    pass
    def update_status(self, message: str, processed: int | float | None=None, total: int=1, additional_info: str='', is_error: bool=False):
        """\n        Обновляет статус обработки файлов.\n        Args:\n            message (str): Текст сообщения\n            processed (int | float | None): Текущий прогресс или количество обработанных файлов\n            total (int): Общее количество для вычисления прогресса\n            additional_info (str): Дополнительная информация\n            is_error (bool): Флаг ошибки\n        """  # inserted
        try:
            status_text = message
            if additional_info:
                status_text += f' | {additional_info}'
            self.status_label.configure(text=status_text)
            if processed is not None:
                if isinstance(processed, float) and 0 <= processed <= 1:
                    self.progress_bar.set(processed)
                else:  # inserted
                    if total > 0:
                        self.progress_bar.set(processed / total)
                    else:  # inserted
                        self.progress_bar.set(0)
            self.update_idletasks()
            if is_error:
                logging.error(message)
                messagebox.showerror('Ошибка', message)
            else:  # inserted
                logging.info(message)
        except Exception as e:
            logging.error(f'Ошибка в update_status: {str(e)}0')

    def log_error(self, message):
        self.update_status('Ошибка', 0, 1, message)
        logging.error(message)
        messagebox.showerror('Ошибка', message)

    def set_api_key(self):
        api_key_window = ctk.CTkToplevel(self)
        api_key_window.title('API Key')
        api_key_window.geometry('400x280')
        api_key_window.configure(fg_color='#1E1E1E')
        api_key_window.resizable(False, False)
        api_key_window.overrideredirect(True)

        def close_window():
            api_key_window.destroy()

        def start_move(event):
            api_key_window.start_x = event.x
            api_key_window.start_y = event.y

        def stop_move(event):
            api_key_window.x = None
            api_key_window.y = None

        def do_move(event):
            if hasattr(api_key_window, 'start_x') and hasattr(api_key_window, 'start_y'):
                deltax = event.x - api_key_window.start_x
                deltay = event.y - api_key_window.start_y
                x = api_key_window.winfo_x() + deltax
                y = api_key_window.winfo_y() + deltay
                api_key_window.geometry(f'+{x}+{y}')
        title_bar = ctk.CTkFrame(api_key_window, fg_color='#252526', height=40, corner_radius=0)
        title_bar.pack(fill='x')
        title_bar.bind('<ButtonPress-1>', start_move)
        title_bar.bind('<ButtonRelease-1>', stop_move)
        title_bar.bind('<B1-Motion>', do_move)
        title_label = ctk.CTkLabel(title_bar, text='API Key', font=('Segoe UI', 12, 'bold'), text_color='#E8E8E8')
        title_label.pack(side='left', padx=10)
        close_button = ctk.CTkButton(title_bar, text='×', width=40, height=40, fg_color='transparent', hover_color='#3E3E3E', text_color='#E8E8E8', font=('Segoe UI', 14, 'bold'), command=close_window)
        close_button.pack(side='right')
        content_frame = ctk.CTkFrame(api_key_window, fg_color='#252526', corner_radius=10)
        content_frame.pack(padx=20, pady=20, fill='both', expand=True)
        label = ctk.CTkLabel(content_frame, text='Введите ваш API ключ:', font=('Segoe UI', 14, 'bold'), text_color='#E8E8E8')
        label.pack(pady=(15, 10))
        api_entry = ctk.CTkEntry(content_frame, width=300, height=35, font=('Segoe UI', 12), fg_color='#2D2D2D', text_color='#E0E0E0', border_color='#3A7CA5', border_width=2)
        api_entry.pack(pady=10)

        def save_api_key():
            api_key = api_entry.get().strip()
            if api_key:
                api_key_path = resource_path('api_key.txt')
                try:
                    with open(api_key_path, 'w', encoding='utf-8') as file:
                        pass  # postinserted
                except Exception as e:
                        file.write(api_key)
                            messagebox.showinfo('Успех', 'API ключ сохранен успешно!')
                            logging.info('API ключ сохранен успешно!')
                            api_key_window.destroy()
            else:  # inserted
                messagebox.showwarning('Предупреждение', 'API ключ не может быть пустым')
                messagebox.showerror('Ошибка', f'Ошибка сохранения API ключа: {e}0')
                logging.error(f'Ошибка сохранения API ключа: {e}0')

        def handle_paste(event=None):
            try:
                clipboard_text = api_key_window.clipboard_get().strip()
                api_entry.delete(0, ctk.END)
                api_entry.insert(0, clipboard_text)
                return 'break'
            except TclError:
                return None
            else:  # inserted
                pass
        api_entry.bind('<Control-v>', handle_paste)
        button_frame = ctk.CTkFrame(content_frame, fg_color='#252526')
        button_frame.pack(pady=15)
        save_button = ctk.CTkButton(button_frame, text='Сохранить', command=save_api_key, width=120, height=35, corner_radius=5, fg_color='#3A7CA5', hover_color='#2C5F7F', text_color='#E8E8E8', font=('Segoe UI', 12, 'bold'))
        save_button.pack(side=ctk.LEFT, padx=10)
        paste_button = ctk.CTkButton(button_frame, text='Вставить', command=handle_paste, width=120, height=35, corner_radius=5, fg_color='#3D3D3D', hover_color='#4A4A4A', text_color='#E0E0E0', font=('Segoe UI', 12))
        paste_button.pack(side=ctk.LEFT, padx=10)
        api_key_window.update_idletasks()
        width = api_key_window.winfo_width()
        height = api_key_window.winfo_height()
        x = self.winfo_x() + self.winfo_width() // 2 - width // 2
        y = self.winfo_y() + self.winfo_height() // 2 - height // 2
        api_key_window.geometry(f'+{x}+{y}')
        api_key_window.grab_set()
        api_key_window.focus_set()
        api_key_window.wait_window()

    def get(self, key, default=None):
        """\n        Безопасно получает значение по ключу.\n        Если ключ не найден, возвращает значение по умолчанию.\n        """  # inserted
        try:
            return self.__getattribute__(key)
        except AttributeError:
            return default
        else:  # inserted
            pass

    def setup_logging(self):
        """Настройка логирования."""  # inserted
        if sys.platform == 'win32':
            log_dir = os.path.join(os.getenv('APPDATA'), 'MisheninApp', 'logs')
        else:  # inserted
            if sys.platform == 'darwin':
                log_dir = os.path.join(os.path.expanduser('~/Library/Logs'), 'MisheninApp')
            else:  # inserted
                log_dir = os.path.join(os.path.expanduser('~'), '.misheninapp', 'logs')
        os.makedirs(log_dir, exist_ok=True)
        log_path = os.path.join(log_dir, 'app.log')
        self.logger.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setFormatter(formatter)
        self.logger.addHandler(console_handler)
        log_path = os.path.join(self.script_dir, 'metadata.log')
        file_handler = logging.FileHandler(log_path, encoding='utf-8')
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

    def check_exiftool(self) -> bool:
        """Проверка наличия и работоспособности ExifTool."""  # inserted
        try:
            result = subprocess.run([self.exiftool_path, '-ver'], capture_output=True, text=True, encoding='utf-8')
            if result.returncode == 0:
                self.logger.info(f'ExifTool версия: {result.stdout.strip()}')
                return True
            self.logger.error(f'Ошибка ExifTool: {result.stderr}0')
            return False
        except Exception as e:
            self.logger.error(f'Ошибка при проверке ExifTool: {str(e)}0')
            return False

    def read_csv(self, filepath):
        """Читает CSV файл и возвращает список записей."""  # inserted
        records = []
        try:
            with open(filepath, mode='r', encoding='utf-8') as file:
                pass  # postinserted
        except Exception as e:
                reader = csv.DictReader(file)
                for row in reader if 'Filename' in row and 'Title' in row and ('Description' in row) and ('Keywords' in row):
                    records.append(row)
                else:  # inserted
                    logging.warning('CSV файл не содержит необходимых заголовков.')
                else:  # inserted
                    return records
                logging.error(f'Ошибка чтения CSV файла: {e}0')

    def process_keywords(self, keywords):
        keyword_list = [kw.strip() for kw in keywords.split(',') if kw.strip()]
        unique_keywords = list(OrderedDict.fromkeys(keyword_list))
        return unique_keywords

    def get_description(self, title, description):
        if description:
            return description
        if title:
            return title + '.'
        return ''

    def normalize_path(self, path):
        return os.path.abspath(os.path.normpath(path))

    def encode_user_comment(self, text):
        return b'UNICODE\x00' + text.encode('utf-16le')

    def embed_metadata(self, image_path, title, description, keywords):
        try:
            command = [self.exiftool_path, '-m', '-charset', 'utf8' if sys.platform == 'win32' else 'UTF8', '-overwrite_original', '-P']
            image_path = self.normalize_path(image_path)
            logging.info(f'Нормализованный путь к файлу: {image_path}')
            if not os.path.exists(image_path):
                logging.error(f'Файл не найден: {image_path}')
                return
            if not self.check_exiftool():
                raise Exception('ExifTool не найден')
            unique_keywords = self.process_keywords(keywords)
            final_description = self.get_description(title, description)
            command = [self.exiftool_path, '-m', '-charset', 'utf8', '-overwrite_original', '-P', '-all=', f'-XMP-dc:Title={title}0', f'-XMP-dc:Description={final_description}0', f'-IPTC:ObjectName={title}0', f'-IPTC:Caption-Abstract={final_description}0', f'-EXIF:ImageDescription={title}0', f'-EXIF:UserComment={final_description}0', f'-XPTitle={title}0', f'-XPComment={final_description}0']
            for keyword in unique_keywords:
                command.append(f'-XMP-dc:Subject+={keyword}')
                command.append(f'-IPTC:Keywords+={keyword}')
                command.append(f'-XPKeywords+={keyword}')
            command.append(image_path)
            result = subprocess.run(command, capture_output=True, text=True, encoding='utf-8')
            if result.returncode == 0:
                logging.info(f'Метаданные успешно добавлены в файл: {image_path}')
                logging.info(f'Финальное описание: {final_description}')
                logging.info(f"Обработанные ключевые слова: {', '.join(unique_keywords)}0")
                return True
        except IOError as e:
            pass  # postinserted
        else:  # inserted
            logging.error(f'Ошибка ExifTool при обработке {image_path}: {result.stderr}')
            return False
                logging.error(f'Ошибка ввода/вывода при обработке изображения: {e}')
                logging.error(f'Проверьте права доступа и существование файла: {image_path}')
                return False
            except Exception as e:
                logging.error(f'Ошибка при обработке изображения: {e}')
                logging.error(f'Трассировка ошибки: {traceback.format_exc()}0')
                return False

    def remove_all_metadata(self, image_path):
        try:
            with open(image_path, 'rb') as f:
                pass  # postinserted
        except Exception as e:
                original_data = f.read()
                else:  # inserted
                    try:
                        cleaned_data = piexif.remove(original_data)
                    finally:  # inserted
                        with open(image_path, 'wb') as f:
                            f.write(cleaned_data)
                                iptc = IPTCInfo(image_path, force=True)
                                iptc.data = {}
                                iptc.save()
                                logging.info(f'Метаданные успешно удалены из файла: {image_path}0')
                logging.error(f'Ошибка при удалении метаданных из файла {image_path}: {e}')
                logging.error(f'Трассировка ошибки: {traceback.format_exc()}0')

    def cleanup_temp_files(self, directory):
        """Очистка временных файлов ExifTool из директории"""  # inserted
        try:
            for filename in os.listdir(directory):
                if filename.endswith('~') or filename.endswith('_original'):
                    file_path = os.path.join(directory, filename)
        except Exception as e:
                else:  # inserted
                    try:
                        os.remove(file_path)
                        logging.info(f'Удален временный файл: {file_path}')
                    except Exception as e:
                        pass  # postinserted
                logging.warning(f'Не удалось удалить временный файл {file_path}: {str(e)}')
                logging.error(f'Ошибка при очистке временных файлов: {str(e)}0')

    def select_script_1(self):
        self.selected_script = 'classic'
        self.update_status('Script selected', 0, 1, 'Script 1 (Classic) selected')
        logging.info('Script 1 (Classic) selected')

    def select_script_2(self):
        self.selected_script = 'concept'
        self.update_status('Script selected', 0, 1, 'Script 2 (Concept) selected')
        logging.info('Script 2 (Concept) selected')

    def execute_script(self):
        self.images_path = filedialog.askdirectory(title='Выберите папку с изображениями')
        if not self.images_path:
            self.update_status('Error', 0, 1, 'Не выбрана папка с изображениями.')
            return
        self.output_folder = filedialog.askdirectory(title='Выберите папку для сохранения CSV')
        if not self.output_folder:
            self.update_status('Error', 0, 1, 'Не выбрана папка для сохранения CSV.')
            return
        images_path_file = resource_path('images_path.txt')
        with open(images_path_file, 'w', encoding='utf-8') as file:
            file.write(self.images_path)
            logging.info(f'Images path saved to: {images_path_file}0')
        self.update_status('Processing', 0, 1, 'Начинается обработка...')
        logging.info(f'Paths selected - Images: {self.images_path}, Output: {self.output_folder}0')
        self.run_processing()

    def check_license_before_processing(self):
        """Проверка лицензии перед запуском обработки"""  # inserted
        is_valid, message = self.license_manager.check_license_status()
        if not is_valid:
            messagebox.showerror('License Error', message)
            self.create_license_window()
            return False
        return True

    def run_processing(self):
        """Запуск соответствующего процессора в зависимости от типа контента."""  # inserted
        try:
            logging.info(f'Запуск обработки, выбранный скрипт: {self.selected_script}0')
            if self.selected_script == 'video':
                logging.info('Инициализация StockVideoProcessor')
                processor = StockVideoProcessor(self.output_folder, self.progress_bar, self.status_label, self)
                logging.info('Вызов process_videos')
                processor.process_videos()
            else:  # inserted
                if self.selected_script == 'classic':
                    logging.info('Запуск ClassicImageProcessor')
                    processor = ClassicImageProcessor(self.output_folder, self.progress_bar, self.status_label, self)
                    processor.process_images()
        except Exception as e:
                else:  # inserted
                    if self.selected_script == 'concept':
                        logging.info('Запуск ConceptImageProcessor')
                        processor = ConceptImageProcessor(self.output_folder, self.progress_bar, self.status_label, self)
                        processor.process_images()
                    else:  # inserted
                        error_msg = f'Неизвестный тип скрипта: {self.selected_script}'
                        logging.error(error_msg)
                        raise ValueError(error_msg)
                        error_message = f'Произошла ошибка при обработке: {str(e)}'
                        logging.error(error_message)
                        logging.error(f'Traceback: {traceback.format_exc()}0')
                        self.update_status(error_message, 0, 1, 'Ошибка')
                        messagebox.showerror('Ошибка', error_message)
if __name__ == '__main__':
    app = ImprovedMisheninApp()
    app.mainloop()