Составь пожалуйста requirements.txt (его полное содержание включая все необходимое) для этого кода. Не спеши с ответом, перепровь чтобы не упустить:
import sys
import os
import asyncio
import logging
from typing import Optional
from PySide6.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, QLineEdit,
                               QFileDialog, QVBoxLayout, QHBoxLayout, QCheckBox, QMessageBox, QProgressBar)
from PySide6.QtGui import QPixmap, QImage
from PySide6.QtCore import Qt
import ffmpeg
import aiohttp
import tempfile

# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(name)

class VideoToGifConverter:
    def init(self):
        self.input_path: Optional[str] = None
        self.temp_files = []

    async def download_video(self, url: str) -> Optional[str]:
        """Асинхронно скачивает видео по прямой ссылке."""
        temp_dir = tempfile.mkdtemp()
        local_filename = os.path.join(temp_dir, os.path.basename(url))
        try:
            logger.info(f"Начинается загрузка видео по URL: {url}")
            async with aiohttp.ClientSession() as session:
                async with session.get(url) as resp:
                    if resp.status != 200:
                        logger.error(f"Не удалось скачать видео: статус {resp.status}")
                        return None
                    with open(local_filename, 'wb') as f:
                        while True:
                            chunk = await resp.content.read(1024)
                            if not chunk:
                                break
                            f.write(chunk)
            logger.info(f"Видео успешно скачано: {local_filename}")
            self.temp_files.append(local_filename)
            return local_filename
        except Exception as e:
            logger.error(f"Ошибка при скачивании видео: {e}")
            return None

    async def convert_to_gif(self, input_path: str, output_path: str,
                             start_time: Optional[float] = None, duration: Optional[float] = None,
                             scale: Optional[int] = None, fps: int = 15,
                             text_overlay: Optional[str] = None,
                             progress_callback=None):
        """Конвертирует видео в GIF с указанными параметрами."""
        try:
            probe = ffmpeg.probe(input_path)
            video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
            if video_stream is None:
                logger.error("Не удалось найти видеопоток.")
                return False

            input_args = {}
            if start_time is not None:
                input_args['ss'] = start_time

            if duration is not None:
                input_args['t'] = duration

            output_args = {
                'format': 'gif',
                'r': fps
            }

            if scale is not None:
                output_args['vf'] = f'scale={scale}:-1'

            if text_overlay:
                # Добавляем текст с помощью фильтров
                output_args['vf'] = output_args.get('vf', '') + f",drawtext=text='{text_overlay}':fontcolor=white:fontsize=24:x=(w-text_w)/2:y=(h-text_h)-10"

            # Настройка прогресса
            def progress_handler(curr_frame, total_frames):
                if progress_callback:
                    progress = int((curr_frame / total_frames) * 100)
                    progress_callback(progress)

            loop = asyncio.get_event_loop()
            await loop.run_in_executor(None, self.run_ffmpeg, input_path, output_path, input_args, output_args, progress_handler)

            logger.info(f"GIF-файл успешно сохранен: {output_path}")
            return True
        except Exception as e:
            logger.error(f"Ошибка при конвертации видео: {e}")
            return False

    def run_ffmpeg(self, input_path, output_path, input_args, output_args, progress_handler):
        """Запускает команду ffmpeg с указанными аргументами."""
        process = (
            ffmpeg
            .input(input_path, **input_args)
            .output(output_path, *output_args)
            .overwrite_output()
            .global_args('-progress', 'pipe:1', '-loglevel', 'error')
        )
        process.run(capture_stdout=True)

    def clean_up(self):
        """Удаляет временные файлы."""
        for file in self.temp_files:
            try:
                os.remove(file)
                logger.info(f"Удален временный файл: {file}")
            except Exception as e:
                logger.warning(f"Не удалось удалить временный файл {file}: {e}")
        self.temp_files.clear()

class MainWindow(QWidget):
    def init(self):
        super().init()
        self.setWindowTitle('Конвертер Видео в GIF')
        self.converter = VideoToGifConverter()
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Выбор источника
        source_layout = QHBoxLayout()
        self.source_label = QLabel('Источник видео:')
        self.source_input = QLineEdit()
        self.browse_button = QPushButton('Обзор')
        self.browse_button.clicked.connect(self.browse_file)
        source_layout.addWidget(self.source_label)
        source_layout.addWidget(self.source_input)
        source_layout.addWidget(self.browse_button)
        layout.addLayout(source_layout)

        # Параметры конвертации
        params_layout = QVBoxLayout()

        self.start_label = QLabel('Время начала (сек):')
        self.start_input = QLineEdit()
        params_layout.addWidget(self.start_label)
        params_layout.addWidget(self.start_input)

        self.duration_label = QLabel('Продолжительность (сек):')
        self.duration_input = QLineEdit()
        params_layout.addWidget(self.duration_label)
        params_layout.addWidget(self.duration_input)

        self.scale_label = QLabel('Ширина (px):')
        self.scale_input = QLineEdit()
        params_layout.addWidget(self.scale_label)
        params_layout.addWidget(self.scale_input)

        self.fps_label = QLabel('FPS:')
        self.fps_input = QLineEdit('15')
        params_layout.addWidget(self.fps_label)
        params_layout.addWidget(self.fps_input)

        self.text_label = QLabel('Текст:')
        self.text_input = QLineEdit()
        params_layout.addWidget(self.text_label)
        params_layout.addWidget(self.text_input)

        layout.addLayout(params_layout)

        # Прогресс-бар
        self.progress_bar = QProgressBar()
        layout.addWidget(self.progress_bar)

        # Кнопка конвертации
        self.convert_button = QPushButton('Конвертировать')
        self.convert_button.clicked.connect(self.start_conversion)
        layout.addWidget(self.convert_button)

        self.setLayout(layout)

    def browse_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, 'Выберите видеофайл', '', 'Видео файлы (.mp4 *.avi *.mov .mkv)')
        if file_path:
            self.source_input.setText(file_path)

    def update_progress(self, value):
        self.progress_bar.setValue(value)

    def start_conversion(self):
        source = self.source_input.text().strip()
        if not source:
            QMessageBox.warning(self, 'Ошибка', 'Пожалуйста, укажите путь к видео.')
            return

        if source.startswith('http://') or source.startswith('https://'):
            # Асинхронная загрузка видео
            asyncio.create_task(self.handle_url_conversion(source))
        else:
            if not os.path.isfile(source):
                QMessageBox.warning(self, 'Ошибка', 'Видео файл не найден.')
                return
            asyncio.create_task(self.handle_file_conversion(source))

    async def handle_url_conversion(self, url):
        self.convert_button.setEnabled(False)
        local_file = await self.converter.download_video(url)
        if local_file:
            await self.convert_video(local_file)
            self.converter.clean_up()
        else:
            QMessageBox.warning(self, 'Ошибка', 'Не удалось скачать видео.')
        self.convert_button.setEnabled(True)

    async def handle_file_conversion(self, file_path):
        self.convert_button.setEnabled(False)
        await self.convert_video(file_path)
        self.convert_button.setEnabled(True)

    async def convert_video(self, input_path):
        output_path, _ = QFileDialog.getSaveFileName(self, 'Сохранить GIF как', '', 'GIF файлы (.gif)')
        if not output_path:
            return

        try:
            start_time = float(self.start_input.text()) if self.start_input.text() else None
            duration = float(self.duration_input.text()) if self.duration_input.text() else None
            scale = int(self.scale_input.text()) if self.scale_input.text() else None
            fps = int(self.fps_input.text()) if self.fps_input.text() else 15
            text_overlay = self.text_input.text() if self.text_input.text() else None
        except ValueError:
            QMessageBox.warning(self, 'Ошибка', 'Пожалуйста, введите правильные числовые значения.')
            return

        self.progress_bar.setValue(0)

        success = await self.converter.convert_to_gif(
            input_path=input_path,
            output_path=output_path,
            start_time=start_time,
            duration=duration,
            scale=scale,
            fps=fps,
            text_overlay=text_overlay,
            progress_callback=self.update_progress
        )

        if success:
            QMessageBox.information(self, 'Успех', 'Конвертация завершена!')
        else:
            QMessageBox.warning(self, 'Ошибка', 'Произошла ошибка при конвертации.')

def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

if name == 'main':
    # Запуск асинхронного цикла событий
    asyncio.run(main())