diff --git a/README.md b/README.md index d155dce..84a930a 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,76 @@ # YouTube-Cloude + +Утилита для кодирования произвольных файлов в видео формата MP4 и их последующего восстановления. Опционально поддерживает шифрование данных. + --- -## Вам понадобится установить зависимости (pip install ...) ->
cv2 -
numpy as np -
os -
math -
subprocess -
tempfile -
shutil -
sys -
re -
hashlib + +## Установка зависимостей + +``` +pip install opencv-python numpy +``` + --- -# Для кодирования любого файла в cmd.exe: ->
python coder.py encode FILENAME.xxx FILENAME.mp4 -
Где .xxx - это расширение вашего файла, который находится в одной папке с coder.py + +## Использование + +### Кодирование файла в видео + +``` +python coder.py encode FILENAME.xxx [OUTPUT.mp4] +``` + +`OUTPUT.mp4` — необязательный аргумент. Если не указан, результат сохранится как `output.mp4`. + +### Декодирование видео обратно в файл + +``` +python coder.py decode FILENAME.mp4 [ПАПКА] +``` + +`ПАПКА` — необязательный аргумент. Если не указана, файл сохранится в текущую директорию. + --- -# Для декодирования видеофайла: ->
python coder.py decode FILENAME.mp4 + +## Шифрование + +Файл можно зашифровать при кодировании и расшифровать при декодировании. Ключ указывается одним из двух способов — они взаимоисключающие. + +### 1. Ключ напрямую в командной строке + +**Кодирование:** +``` +python coder.py encode FILENAME.xxx OUTPUT.mp4 --key SECRET +``` + +**Декодирование:** +``` +python coder.py decode FILENAME.mp4 --key SECRET +``` + +### 2. Путь к файлу с ключом + +**Кодирование:** +``` +python coder.py encode FILENAME.xxx OUTPUT.mp4 --key-file /path/to/key.txt +``` + +**Декодирование:** +``` +python coder.py decode FILENAME.mp4 --key-file /path/to/key.txt +``` + --- -## Ключ шифрования ->
Вы можете создать в папке с coder.py файл key.txt и написать внутри него ключ шифрования любой длины. После этого код будет учитывать присутствие key.txt при кодирование файлов и в дальнейшем такие видеофайлы обратно декодироваться правильно будуту только с правильным ключом, указанном в key.txt. + +## Справка + +Общая справка: +``` +python coder.py --help +``` + +Справка по конкретной команде: +``` +python coder.py encode --help +python coder.py decode --help +``` diff --git a/coder.py b/coder.py index a04efff..e4ddb10 100644 --- a/coder.py +++ b/coder.py @@ -1,4 +1,5 @@ # youtube_storage_fixed.py + import cv2 import numpy as np import os @@ -9,180 +10,172 @@ import sys import re import hashlib +import argparse from collections import Counter class YouTubeEncoder: def __init__(self, key=None): self.width = 1920 self.height = 1080 - self.fps = 6 # ИЗМЕНЕНО: теперь 6 кадров в секунду - + self.fps = 6 # ИЗМЕНЕНО: теперь 6 кадров в секунду + # Параметры self.block_height = 16 self.block_width = 24 self.spacing = 4 - + # Ключ шифрования self.key = key self.use_encryption = key is not None - + # 16 цветов self.colors = { - '0000': (255, 0, 0), # Ярко-синий - '0001': (0, 255, 0), # Ярко-зеленый - '0010': (0, 0, 255), # Ярко-красный - '0011': (255, 255, 0), # Желтый - '0100': (255, 0, 255), # Пурпурный - '0101': (0, 255, 255), # Голубой - '0110': (255, 128, 0), # Оранжевый - '0111': (128, 0, 255), # Фиолетовый - '1000': (0, 128, 128), # Бирюзовый - '1001': (128, 128, 0), # Оливковый - '1010': (128, 0, 128), # Темно-пурпурный - '1011': (0, 128, 0), # Темно-зеленый - '1100': (128, 0, 0), # Бордовый - '1101': (0, 0, 128), # Темно-синий - '1110': (192, 192, 192), # Светло-серый - '1111': (255, 255, 255) # Белый + '0000': (255, 0, 0), # Ярко-синий + '0001': (0, 255, 0), # Ярко-зеленый + '0010': (0, 0, 255), # Ярко-красный + '0011': (255, 255, 0), # Желтый + '0100': (255, 0, 255), # Пурпурный + '0101': (0, 255, 255), # Голубой + '0110': (255, 128, 0), # Оранжевый + '0111': (128, 0, 255), # Фиолетовый + '1000': (0, 128, 128), # Бирюзовый + '1001': (128, 128, 0), # Оливковый + '1010': (128, 0, 128), # Темно-пурпурный + '1011': (0, 128, 0), # Темно-зеленый + '1100': (128, 0, 0), # Бордовый + '1101': (0, 0, 128), # Темно-синий + '1110': (192, 192, 192),# Светло-серый + '1111': (255, 255, 255) # Белый } - + # Маркеры по углам self.marker_size = 80 - + # Расчет сетки self.blocks_x = (self.width - 2*self.marker_size) // (self.block_width + self.spacing) self.blocks_y = (self.height - 2*self.marker_size) // (self.block_height + self.spacing) self.blocks_per_region = self.blocks_x * self.blocks_y self.blocks_per_frame = self.blocks_per_region * 3 - + # Маркер конца self.eof_marker = "█" * 64 self.eof_bytes = self.eof_marker.encode('utf-8') - + print("="*60) print("🎬 КОДИРОВЩИК YouTube (6 FPS)") print("="*60) print(f"📊 Сетка: {self.blocks_x} x {self.blocks_y} блоков на регион") - print(f"🎞️ FPS: {self.fps}") + print(f"🎞️ FPS: {self.fps}") print(f"🔐 Шифрование: {'ВКЛ' if self.use_encryption else 'ВЫКЛ'}") - + def _encrypt_data(self, data): """XOR шифрование с ключом""" if not self.use_encryption: return data - key_bytes = self.key.encode() result = bytearray() - for i, byte in enumerate(data): key_byte = key_bytes[i % len(key_bytes)] result.append(byte ^ key_byte) - return result - + def _draw_markers(self, frame): """Рисует маркеры по углам""" cv2.rectangle(frame, (0, 0), (self.marker_size, self.marker_size), (255, 255, 255), -1) cv2.rectangle(frame, (self.width-self.marker_size, 0), (self.width, self.marker_size), (255, 255, 255), -1) cv2.rectangle(frame, (0, self.height-self.marker_size), (self.marker_size, self.height), (255, 255, 255), -1) cv2.rectangle(frame, (self.width-self.marker_size, self.height-self.marker_size), (self.width, self.height), (255, 255, 255), -1) - cv2.rectangle(frame, (0, 0), (self.marker_size, self.marker_size), (0, 0, 0), 2) cv2.rectangle(frame, (self.width-self.marker_size, 0), (self.width, self.marker_size), (0, 0, 0), 2) cv2.rectangle(frame, (0, self.height-self.marker_size), (self.marker_size, self.height), (0, 0, 0), 2) cv2.rectangle(frame, (self.width-self.marker_size, self.height-self.marker_size), (self.width, self.height), (0, 0, 0), 2) - return frame - + def _draw_block(self, frame, x, y, color): """Рисует один блок""" x1 = self.marker_size + x * (self.block_width + self.spacing) y1 = self.marker_size + y * (self.block_height + self.spacing) x2 = x1 + self.block_width y2 = y1 + self.block_height - if x2 > self.width - self.marker_size or y2 > self.height - self.marker_size: return False - cv2.rectangle(frame, (x1, y1), (x2, y2), color, -1) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 0), 1) return True - + def _bits_to_color(self, bits): """4 бита -> цвет""" while len(bits) < 4: bits = '0' + bits return self.colors.get(bits, (255, 0, 0)) - + def _data_to_blocks(self, data): """Конвертирует данные в 4-битные блоки""" all_bits = [] for byte in data: for i in range(7, -1, -1): all_bits.append(str((byte >> i) & 1)) - while len(all_bits) % 4 != 0: all_bits.append('0') - blocks = [''.join(all_bits[i:i+4]) for i in range(0, len(all_bits), 4)] return blocks - + def encode(self, input_file, output_file): """Кодирует файл в видео с опциональным шифрованием""" - print("\n📤 КОДИРОВАНИЕ ФАЙЛА") print("-" * 40) - + # Читаем файл with open(input_file, 'rb') as f: data = f.read() - + print(f"📄 Файл: {input_file}") print(f"📦 Размер: {len(data)} байт") - + # Шифруем данные если нужно if self.use_encryption: encrypted_data = self._encrypt_data(data) print(f"🔐 Данные зашифрованы") else: encrypted_data = data - + # Создаем заголовок header = f"FILE:{os.path.basename(input_file)}:SIZE:{len(data)}|" header_bytes = header.encode('latin-1') print(f"📋 Заголовок: {header}") - + # Конвертируем в блоки header_blocks = self._data_to_blocks(header_bytes) data_blocks = self._data_to_blocks(encrypted_data) eof_blocks = self._data_to_blocks(self.eof_bytes) all_blocks = header_blocks + data_blocks + eof_blocks - + print(f"🎨 Всего блоков: {len(all_blocks)}") print(f"🏁 Маркер конца: {len(eof_blocks)} блоков") - + # Рассчитываем количество кадров frames_needed = math.ceil(len(all_blocks) / self.blocks_per_region) + # Добавляем 5 защитных кадров frames_needed += 5 + print(f"🎬 Требуется кадров: {frames_needed}") - print(f"⏱️ Длительность видео: {frames_needed/self.fps:.1f} сек") - + print(f"⏱️ Длительность видео: {frames_needed/self.fps:.1f} сек") + # Создаем временную папку temp_dir = tempfile.mkdtemp() print(f"📁 Временная папка: {temp_dir}") - + # Создаем кадры for frame_num in range(frames_needed - 5): - print(f"\n🖼️ Кадр {frame_num + 1}/{frames_needed}") - + print(f"\n🖼️ Кадр {frame_num + 1}/{frames_needed}") frame = np.zeros((self.height, self.width, 3), dtype=np.uint8) frame = self._draw_markers(frame) - + start_idx = frame_num * self.blocks_per_region end_idx = min(start_idx + self.blocks_per_region, len(all_blocks)) frame_blocks = all_blocks[start_idx:end_idx] - + # Основные блоки for idx, bits in enumerate(frame_blocks): y = idx // self.blocks_x @@ -190,7 +183,7 @@ def encode(self, input_file, output_file): if y < self.blocks_y: color = self._bits_to_color(bits) self._draw_block(frame, x, y, color) - + # Резерв 1 for idx, bits in enumerate(frame_blocks): y = idx // self.blocks_x @@ -198,7 +191,7 @@ def encode(self, input_file, output_file): if x < self.blocks_x * 2 and y < self.blocks_y: color = self._bits_to_color(bits) self._draw_block(frame, x, y, color) - + # Резерв 2 for idx, bits in enumerate(frame_blocks): y = idx // self.blocks_x + self.blocks_y @@ -206,13 +199,13 @@ def encode(self, input_file, output_file): if x < self.blocks_x and y < self.blocks_y * 2: color = self._bits_to_color(bits) self._draw_block(frame, x, y, color) - + # Сохраняем кадр frame_file = os.path.join(temp_dir, f"frame_{frame_num:05d}.png") cv2.imwrite(frame_file, frame) - + # Создаем защитные кадры (синий фон) - print("\n🛡️ Создание защитных кадров...") + print("\n🛡️ Создание защитных кадров...") for i in range(5): frame_num = frames_needed - 5 + i frame = np.zeros((self.height, self.width, 3), dtype=np.uint8) @@ -223,13 +216,11 @@ def encode(self, input_file, output_file): frame_file = os.path.join(temp_dir, f"frame_{frame_num:05d}.png") cv2.imwrite(frame_file, frame) print(f" 🟦 Защитный кадр {i+1}/5") - + # Конвертируем в MP4 - print("\n🎞️ Конвертация в MP4...") - + print("\n🎞️ Конвертация в MP4...") try: subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True) - cmd = [ 'ffmpeg', '-framerate', str(self.fps), @@ -243,32 +234,29 @@ def encode(self, input_file, output_file): '-y', output_file ] - subprocess.run(cmd, check=True, capture_output=True) print("✅ FFmpeg конвертация успешна") - except Exception as e: print(f"⚠️ FFmpeg не доступен, использую OpenCV...") fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_file, fourcc, self.fps, (self.width, self.height)) - for frame_num in range(frames_needed): frame_file = os.path.join(temp_dir, f"frame_{frame_num:05d}.png") frame = cv2.imread(frame_file) if frame is not None: out.write(frame) out.release() - + # Удаляем временные файлы shutil.rmtree(temp_dir) print("🧹 Временные файлы удалены") - + if os.path.exists(output_file): size = os.path.getsize(output_file) print(f"\n✅ Видео сохранено: {output_file}") print(f"📊 Размер: {size} байт ({size/1024/1024:.2f} MB)") print(f"🎬 Кадров: {frames_needed}") - print(f"⏱️ Длительность: {frames_needed/self.fps:.1f} сек") + print(f"⏱️ Длительность: {frames_needed/self.fps:.1f} сек") return True return False @@ -281,10 +269,10 @@ def __init__(self, key=None): self.block_width = 24 self.spacing = 4 self.marker_size = 80 - + # Ключ шифрования self.key = key - + # 16 цветов self.colors = { '0000': (255, 0, 0), @@ -304,28 +292,28 @@ def __init__(self, key=None): '1110': (192, 192, 192), '1111': (255, 255, 255) } - + # Оптимизации self.color_values = np.array(list(self.colors.values()), dtype=np.int32) self.color_keys = list(self.colors.keys()) self.color_cache = {} self.cache_hits = 0 self.cache_misses = 0 - + # Расчет сетки self.blocks_x = (self.width - 2*self.marker_size) // (self.block_width + self.spacing) self.blocks_y = (self.height - 2*self.marker_size) // (self.block_height + self.spacing) self.blocks_per_region = self.blocks_x * self.blocks_y - + # Предвычисление координат self._precompute_coordinates() - + print("="*60) print("🎬 ДЕКОДЕР YouTube") print("="*60) print(f"📊 Сетка: {self.blocks_x} x {self.blocks_y} блоков") print(f"🔐 Ключ: {'ЕСТЬ' if self.key else 'НЕТ'}") - + def _precompute_coordinates(self): """Предвычисляет координаты блоков""" self.block_coords = [] @@ -336,55 +324,49 @@ def _precompute_coordinates(self): cx = self.marker_size + x * (self.block_width + self.spacing) + self.block_width // 2 cy = self.marker_size + y * (self.block_height + self.spacing) + self.block_height // 2 self.block_coords.append((cx, cy)) - + def _decrypt_data(self, data): """XOR дешифрование с ключом""" if not self.key: return data - key_bytes = self.key.encode() result = bytearray() - for i, byte in enumerate(data): key_byte = key_bytes[i % len(key_bytes)] result.append(byte ^ key_byte) - return result - + def _color_to_bits_fast(self, color): """Оптимизированный поиск цвета""" color_key = (color[0], color[1], color[2]) - if color_key in self.color_cache: self.cache_hits += 1 return self.color_cache[color_key] - + self.cache_misses += 1 - + # Быстрая проверка на синий фон if color[0] > 200 and color[1] < 50 and color[2] < 50: self.color_cache[color_key] = '0000' return '0000' - + # NumPy векторизация color_arr = np.array([color[0], color[1], color[2]], dtype=np.int32) distances = np.sum((self.color_values - color_arr) ** 2, axis=1) best_idx = np.argmin(distances) result = self.color_keys[best_idx] - + self.color_cache[color_key] = result return result - + def decode_frame_fast(self, frame): """Быстрое декодирование одного кадра с масштабированием""" # Принудительное масштабирование к оригинальному размеру if frame.shape[1] != self.width or frame.shape[0] != self.height: - frame = cv2.resize(frame, (self.width, self.height), - interpolation=cv2.INTER_NEAREST) - + frame = cv2.resize(frame, (self.width, self.height), + interpolation=cv2.INTER_NEAREST) blocks = [] h, w = frame.shape[:2] - for cx, cy in self.block_coords: if cx < w and cy < h: color = frame[cy, cx] @@ -392,14 +374,12 @@ def decode_frame_fast(self, frame): blocks.append(bits) else: blocks.append('0000') - return blocks - + def _blocks_to_bytes(self, blocks): """4-битные блоки -> байты""" all_bits = ''.join(blocks) bytes_data = bytearray() - for i in range(0, len(all_bits) - 7, 8): byte_str = all_bits[i:i+8] if len(byte_str) == 8: @@ -408,58 +388,55 @@ def _blocks_to_bytes(self, blocks): bytes_data.append(byte) except: bytes_data.append(0) - return bytes_data - + def _find_eof_marker(self, data): """Поиск маркера конца █████... в данных""" eof_bytes = b'\xe2\x96\x88' * 64 - for i in range(len(data) - len(eof_bytes)): if data[i:i+len(eof_bytes)] == eof_bytes: return i return -1 - + def decode(self, video_file, output_dir='.'): """Декодирует видео""" - print("\n📥 ДЕКОДИРОВАНИЕ ВИДЕО") print("-" * 40) - + if not os.path.exists(video_file): print(f"❌ Файл не найден: {video_file}") return False - + cap = cv2.VideoCapture(video_file) if not cap.isOpened(): print("❌ Не удалось открыть видео") return False - + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - + print(f"📹 Всего кадров: {total_frames}") print(f"📹 FPS: {fps}") print(f"📹 Разрешение: {width}x{height}") - + # Сброс статистики self.cache_hits = 0 self.cache_misses = 0 start_time = cv2.getTickCount() - + # Сбор блоков all_blocks = [] frames_processed = 0 - + for frame_num in range(total_frames): ret, frame = cap.read() if not ret: break - + frames_processed += 1 - + # Прогресс if frame_num % 100 == 0: elapsed = (cv2.getTickCount() - start_time) / cv2.getTickFrequency() @@ -468,23 +445,23 @@ def decode(self, video_file, output_dir='.'): print(f" Прогресс: {frame_num}/{total_frames} | " f"Скорость: {speed:.1f} кадр/сек | " f"Кэш: {cache_ratio:.1f}%") - + # Декодирование кадра с масштабированием frame_blocks = self.decode_frame_fast(frame) all_blocks.extend(frame_blocks) - + cap.release() - + # Статистика elapsed = (cv2.getTickCount() - start_time) / cv2.getTickFrequency() print(f"\n📊 Статистика: {len(all_blocks)} блоков за {elapsed:.1f} сек") print(f" 🎯 Кэш: попаданий {self.cache_hits}, промахов {self.cache_misses}") print(f" 🔄 Кадров обработано: {frames_processed}") - + # Конвертация в байты bytes_data = self._blocks_to_bytes(all_blocks) print(f"📦 Получено байт: {len(bytes_data)}") - + # Поиск маркера конца eof_pos = self._find_eof_marker(bytes_data) if eof_pos > 0: @@ -493,26 +470,25 @@ def decode(self, video_file, output_dir='.'): print(f"📦 Байт после обрезки: {len(bytes_data)}") else: print("⚠️ Маркер конца не найден") - + # Поиск заголовка data_str = bytes_data[:1000].decode('latin-1', errors='ignore') pattern = r'FILE:([^:]+):SIZE:(\d+)\|' match = re.search(pattern, data_str) - + if match: filename = match.group(1) filesize = int(match.group(2)) - print(f"\n✅ Найден заголовок: {filename}, размер: {filesize} байт") - + header_str = match.group(0) header_bytes = header_str.encode('latin-1') header_pos = bytes_data.find(header_bytes) - + if header_pos >= 0: # Извлекаем зашифрованные данные encrypted_data = bytes_data[header_pos + len(header_bytes):header_pos + len(header_bytes) + filesize] - + # Дешифруем если есть ключ if self.key: file_data = self._decrypt_data(encrypted_data) @@ -520,7 +496,7 @@ def decode(self, video_file, output_dir='.'): else: file_data = encrypted_data print(f"⚠️ Данные без расшифровки") - + # Сохраняем файл output_path = os.path.join(output_dir, filename) counter = 1 @@ -528,23 +504,23 @@ def decode(self, video_file, output_dir='.'): while os.path.exists(output_path): output_path = os.path.join(output_dir, f"{base}_{counter}{ext}") counter += 1 - + with open(output_path, 'wb') as f: f.write(file_data) - + print(f"\n✅ Файл восстановлен: {output_path}") print(f"📏 Размер: {len(file_data)} байт") - + # Проверка размера if len(file_data) == filesize: print("✅ Размер совпадает с оригиналом") else: print(f"⚠️ Размер не совпадает: {len(file_data)} != {filesize}") - + return True else: print("❌ Заголовок не найден") - + # Если не нашли заголовок output_path = os.path.join(output_dir, "decoded_data.bin") with open(output_path, 'wb') as f: @@ -553,59 +529,116 @@ def decode(self, video_file, output_dir='.'): return False -def read_key_from_file(key_file='key.txt'): - """Читает ключ из файла key.txt""" +def read_key_from_file(key_file): + """Читает ключ из файла""" try: if os.path.exists(key_file): with open(key_file, 'r', encoding='utf-8') as f: key = f.read().strip() - if key: - print(f"🔑 Ключ загружен из {key_file}") - return key - else: - print(f"⚠️ Файл {key_file} пуст") + if key: + print(f"🔑 Ключ загружен из {key_file}") + return key + else: + print(f"⚠️ Файл {key_file} пуст") else: print(f"ℹ️ Файл {key_file} не найден, шифрование не используется") except Exception as e: print(f"⚠️ Ошибка чтения ключа: {e}") - return None +def resolve_key(args): + """ + Определяет ключ шифрования по приоритету: + 1. --key TEXT — ключ прямо в командной строке + 2. --key-file PATH — путь к файлу с ключом + 3. key.txt рядом со скриптом (обратная совместимость) + """ + if args.key: + print("🔑 Используется ключ из аргумента --key") + return args.key + + if args.key_file: + key = read_key_from_file(args.key_file) + if key is None: + print(f"❌ Не удалось прочитать ключ из файла: {args.key_file}") + sys.exit(1) + return key + + # Фолбэк: ищем key.txt рядом со скриптом (старое поведение) + #return read_key_from_file() + + def main(): - if len(sys.argv) < 2: - print("\n" + "="*60) - print("🎥 YouTube File Storage (6 FPS)") - print("="*60) - print("\nИспользование:") - print(" encode <файл> [output.mp4] - закодировать файл") - print(" decode <видео> [папка] - декодировать видео") - print("\nХарактеристики:") - print(" • Частота кадров: 6 FPS") - print(" • Масштабирование к 1920x1080") - print(" • Маркер конца данных") - print(" • 5 защитных кадров") - print("\nШифрование:") - print(" • Для шифрования создайте key.txt с ключом") - return - - # Читаем ключ из файла - key = read_key_from_file() - - if sys.argv[1] == "encode": + parser = argparse.ArgumentParser( + prog='coder.py', + description='🎥 YouTube File Storage (6 FPS) — кодирование файлов в видео', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Примеры: + # Кодирование без шифрования + python coder.py encode file.zip output.mp4 + + # Кодирование с ключом прямо в командной строке + python coder.py encode file.zip output.mp4 --key "mysecretpassword" + + # Кодирование с ключом из файла + python coder.py encode file.zip output.mp4 --key-file /path/to/key.txt + + # Декодирование с ключом прямо в командной строке + python coder.py decode output.mp4 --key "mysecretpassword" + + # Декодирование с ключом из файла + python coder.py decode output.mp4 --key-file /path/to/key.txt + + # Если key.txt лежит рядом со скриптом — ключ подхватится автоматически + python coder.py decode output.mp4 + """ + ) + + subparsers = parser.add_subparsers(dest='command', metavar='КОМАНДА') + subparsers.required = True + + # --- encode --- + enc = subparsers.add_parser('encode', help='Закодировать файл в видео') + enc.add_argument('input_file', metavar='ФАЙЛ', help='Путь к файлу для кодирования') + enc.add_argument('output_file', metavar='ВИДЕО', nargs='?', default='output.mp4', + help='Имя выходного MP4-файла (по умолч.: output.mp4)') + _add_key_args(enc) + + # --- decode --- + dec = subparsers.add_parser('decode', help='Декодировать видео обратно в файл') + dec.add_argument('video_file', metavar='ВИДЕО', help='Путь к MP4-файлу для декодирования') + dec.add_argument('output_dir', metavar='ПАПКА', nargs='?', default='.', + help='Папка для сохранения результата (по умолч.: текущая)') + _add_key_args(dec) + + args = parser.parse_args() + key = resolve_key(args) + + if args.command == 'encode': encoder = YouTubeEncoder(key) - input_file = sys.argv[2] - output = sys.argv[3] if len(sys.argv) > 3 else "output.mp4" - encoder.encode(input_file, output) - - elif sys.argv[1] == "decode": + encoder.encode(args.input_file, args.output_file) + + elif args.command == 'decode': decoder = YouTubeDecoder(key) - video_file = sys.argv[2] - output_dir = sys.argv[3] if len(sys.argv) > 3 else "." - decoder.decode(video_file, output_dir) - else: - print(f"❌ Неизвестная команда: {sys.argv[1]}") + decoder.decode(args.video_file, args.output_dir) + + +def _add_key_args(subparser): + """Добавляет аргументы шифрования в подкоманду.""" + group = subparser.add_mutually_exclusive_group() + group.add_argument( + '--key', + metavar='ТЕКСТ', + help='Ключ шифрования в виде строки' + ) + group.add_argument( + '--key-file', + metavar='ПУТЬ', + help='Путь к файлу с ключом шифрования' + ) if __name__ == "__main__": - main() + main() \ No newline at end of file