Внёс изменения в графический интерфейс

теперь есть возможность использовать метод DCT
This commit is contained in:
Likaon
2026-05-23 21:33:25 +03:00
parent 1f04fb3c2c
commit f053e2becc
2 changed files with 238 additions and 104 deletions

View File

@@ -1,6 +1,7 @@
"""
Модуль DCT стеганографии с квантованием (JPEG-style)
Оптимизирован для длинных сообщений
Поддержка цветных изображений (RGB)
Оптимизирован для GUI
"""
import numpy as np
@@ -10,16 +11,16 @@ from .utils import text_to_bits, bits_to_text
BLOCK_SIZE = 8
# Более мягкая таблица квантования (качество ~70-80%)
# Таблица квантования JPEG (качество 50%)
QUANT_TABLE = np.array([
[8, 6, 5, 8, 12, 20, 26, 31],
[6, 6, 7, 10, 13, 29, 30, 28],
[7, 7, 8, 12, 20, 29, 35, 28],
[7, 9, 11, 15, 26, 44, 40, 31],
[9, 11, 19, 28, 34, 55, 52, 39],
[12, 18, 28, 32, 41, 52, 57, 46],
[25, 32, 39, 44, 52, 61, 60, 51],
[36, 46, 48, 49, 56, 50, 52, 50]
[16, 11, 10, 16, 24, 40, 51, 61],
[12, 12, 14, 19, 26, 58, 60, 55],
[14, 13, 16, 24, 40, 57, 69, 56],
[14, 17, 22, 29, 51, 87, 80, 62],
[18, 22, 37, 56, 68, 109, 103, 77],
[24, 35, 55, 64, 81, 104, 113, 92],
[49, 64, 78, 87, 103, 121, 120, 101],
[72, 92, 95, 98, 112, 100, 103, 99]
])
@@ -41,8 +42,8 @@ def _get_zigzag_order() -> list:
ZIGZAG_ORDER = _get_zigzag_order()
# Используем больше коэффициентов (1-50)
MID_FREQ_INDICES = ZIGZAG_ORDER[1:50]
# Используем среднечастотные коэффициенты
MID_FREQ_INDICES = ZIGZAG_ORDER[10:40]
def _dct_quantize(block: np.ndarray) -> np.ndarray:
@@ -98,7 +99,13 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool:
img = Image.open(image_path).convert('RGB')
pixels = np.array(img, dtype=np.float64)
height, width, channels = pixels.shape
# Проверка размеров
if height % BLOCK_SIZE != 0 or width % BLOCK_SIZE != 0:
print(f"Ошибка: размеры {width}x{height} не кратны {BLOCK_SIZE}")
return False
# Формат: [длина: 32 бита] + [сообщение]
msg_bytes = message.encode('utf-8')
msg_length = len(msg_bytes)
length_bits = format(msg_length, '032b')
@@ -106,19 +113,20 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool:
all_bits = length_bits + message_bits
bit_list = [int(b) for b in all_bits]
total_bits = len(bit_list)
# Проверка вместимости
blocks_per_row = width // BLOCK_SIZE
blocks_per_col = height // BLOCK_SIZE
max_bits = blocks_per_row * blocks_per_col * len(MID_FREQ_INDICES) * 3
if total_bits > max_bits:
print(f"Ошибка: сообщение слишком большое.")
print(f"Доступно: {max_bits}, требуется: {total_bits}")
return False
modified_pixels = pixels.copy()
bit_index = 0
for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE):
for j in range(0, width - BLOCK_SIZE + 1, BLOCK_SIZE):
if bit_index >= total_bits:
@@ -127,7 +135,7 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool:
for c in range(3):
if bit_index >= total_bits:
break
block = pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE, c]
quant_block = _dct_quantize(block)
quant_block, bit_index = _embed_bits_in_block(quant_block, bit_list, bit_index)
@@ -136,7 +144,7 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool:
if bit_index >= total_bits:
break
result_img = Image.fromarray(modified_pixels.astype(np.uint8), mode='RGB')
result_img.save(output_path)
print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)")
@@ -148,7 +156,11 @@ def decode_dct(image_path: str) -> str:
img = Image.open(image_path).convert('RGB')
pixels = np.array(img, dtype=np.float64)
height, width, channels = pixels.shape
if height % BLOCK_SIZE != 0 or width % BLOCK_SIZE != 0:
return ""
# Извлекаем все биты
all_bits = []
for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE):
@@ -158,7 +170,8 @@ def decode_dct(image_path: str) -> str:
quant_block = _dct_quantize(channel_block)
bits_from_block = _extract_bits_from_block(quant_block, len(MID_FREQ_INDICES))
all_bits.extend(bits_from_block)
# Читаем длину сообщения (первые 32 бита)
if len(all_bits) < 32:
return ""
@@ -166,15 +179,13 @@ def decode_dct(image_path: str) -> str:
length_str = ''.join(str(b) for b in length_bits)
msg_length = int(length_str, 2)
if msg_length > (len(all_bits) - 32) // 8:
# Проверка
if msg_length <= 0 or msg_length > (len(all_bits) - 32) // 8:
return ""
# Читаем сообщение
message_bits = all_bits[32:32 + msg_length * 8]
remainder = len(message_bits) % 8
if remainder != 0:
message_bits.extend([0] * (8 - remainder))
if len(message_bits) == 0:
return ""
@@ -182,5 +193,5 @@ def decode_dct(image_path: str) -> str:
try:
return bits_to_text(bits_string)
except Exception as e:
print(f"Ошибка при декодировании: {e}")
print(f"Ошибка декодирования: {e}")
return ""

View File

@@ -4,7 +4,6 @@ GUI интерфейс для стеганографического прило
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
import threading
import os
import sys
@@ -22,15 +21,16 @@ class StegoGUI:
def __init__(self, root):
self.root = root
self.root.title("Stego App - Стеганография")
self.root.geometry("750x650")
self.root.title("Stego App - Стеганографическое приложение")
self.root.geometry("800x700")
self.root.resizable(True, True)
# Переменные
self.image_path = tk.StringVar()
self.output_path = tk.StringVar()
self.method = tk.StringVar(value="lsb")
self.mode = tk.StringVar(value="encode")
self.encode_image_path = tk.StringVar()
self.encode_output_path = tk.StringVar()
self.decode_image_path = tk.StringVar()
self.method_encode = tk.StringVar(value="lsb")
self.method_decode = tk.StringVar(value="lsb")
self.setup_ui()
@@ -60,27 +60,45 @@ class StegoGUI:
row = 0
# Метод
ttk.Label(self.encode_frame, text="Метод:").grid(row=row, column=0, sticky='w', padx=5, pady=5)
ttk.Radiobutton(self.encode_frame, text="LSB", variable=self.method, value="lsb").grid(row=row, column=1, padx=5)
ttk.Radiobutton(self.encode_frame, text="DCT", variable=self.method, value="dct").grid(row=row, column=2, padx=5)
ttk.Label(self.encode_frame, text="Метод стеганографии:").grid(row=row, column=0, sticky='w', padx=5, pady=5)
ttk.Radiobutton(self.encode_frame, text="LSB (рекомендуется)", variable=self.method_encode, value="lsb").grid(row=row, column=1, padx=5, sticky='w')
ttk.Radiobutton(self.encode_frame, text="DCT (для гладких/одноцветных изображений)", variable=self.method_encode, value="dct").grid(row=row, column=2, padx=5, sticky='w')
row += 1
# Разделитель
ttk.Separator(self.encode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=5)
row += 1
# Исходное изображение
ttk.Label(self.encode_frame, text="Исходное изображение:").grid(row=row, column=0, sticky='w', padx=5, pady=5)
ttk.Entry(self.encode_frame, textvariable=self.image_path, width=50).grid(row=row, column=1, columnspan=2, padx=5)
ttk.Button(self.encode_frame, text="Обзор...", command=self.select_image).grid(row=row, column=3, padx=5)
self.encode_image_entry = ttk.Entry(self.encode_frame, textvariable=self.encode_image_path, width=55)
self.encode_image_entry.grid(row=row, column=1, columnspan=2, padx=5, sticky='ew')
ttk.Button(self.encode_frame, text="Обзор...", command=self.select_encode_image).grid(row=row, column=3, padx=5)
row += 1
# Выходное изображение
ttk.Label(self.encode_frame, text="Сохранить как:").grid(row=row, column=0, sticky='w', padx=5, pady=5)
ttk.Entry(self.encode_frame, textvariable=self.output_path, width=50).grid(row=row, column=1, columnspan=2, padx=5)
ttk.Button(self.encode_frame, text="Обзор...", command=self.select_output).grid(row=row, column=3, padx=5)
ttk.Label(self.encode_frame, text="Сохранить как (PNG):").grid(row=row, column=0, sticky='w', padx=5, pady=5)
self.encode_output_entry = ttk.Entry(self.encode_frame, textvariable=self.encode_output_path, width=55)
self.encode_output_entry.grid(row=row, column=1, columnspan=2, padx=5, sticky='ew')
ttk.Button(self.encode_frame, text="Обзор...", command=self.select_encode_output).grid(row=row, column=3, padx=5)
row += 1
# Разделитель
ttk.Separator(self.encode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=5)
row += 1
# Сообщение
ttk.Label(self.encode_frame, text="Сообщение:").grid(row=row, column=0, sticky='nw', padx=5, pady=5)
self.message_text = tk.Text(self.encode_frame, height=10, width=60)
self.message_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5)
ttk.Label(self.encode_frame, text="Сообщение для сокрытия:").grid(row=row, column=0, sticky='nw', padx=5, pady=5)
self.message_text = tk.Text(self.encode_frame, height=8, width=65, wrap=tk.WORD)
self.message_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5, sticky='ew')
row += 1
# Кнопка очистки сообщения
ttk.Button(self.encode_frame, text="Очистить сообщение", command=self.clear_message).grid(row=row, column=1, sticky='w', padx=5, pady=5)
row += 1
# Разделитель
ttk.Separator(self.encode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=5)
row += 1
# Вместимость
@@ -88,20 +106,20 @@ class StegoGUI:
self.capacity_label.grid(row=row, column=1, columnspan=2, sticky='w', padx=5)
row += 1
# Прогресс
self.encode_progress = ttk.Progressbar(self.encode_frame, mode='indeterminate', length=400)
# Кнопка
self.encode_btn = ttk.Button(self.encode_frame, text="Скрыть сообщение", command=self.do_encode)
self.encode_btn.grid(row=row, column=1, columnspan=2, pady=10)
self.encode_btn.grid(row=row, column=1, columnspan=2, pady=15)
row += 1
# Статус
self.status_label = ttk.Label(self.encode_frame, text="", foreground="blue")
self.status_label.grid(row=row, column=0, columnspan=4, pady=5)
# Прогресс
self.progress = ttk.Progressbar(self.encode_frame, mode='indeterminate')
# Привязка события для обновления вместимости
self.image_path.trace('w', lambda *args: self.update_capacity())
# Привязка событий
self.encode_image_path.trace('w', lambda *args: self.update_capacity())
def setup_decode_tab(self):
"""Вкладка декодирования"""
@@ -109,30 +127,47 @@ class StegoGUI:
# Метод
ttk.Label(self.decode_frame, text="Метод:").grid(row=row, column=0, sticky='w', padx=5, pady=5)
ttk.Radiobutton(self.decode_frame, text="LSB", variable=self.method, value="lsb").grid(row=row, column=1, padx=5)
ttk.Radiobutton(self.decode_frame, text="DCT", variable=self.method, value="dct").grid(row=row, column=2, padx=5)
ttk.Radiobutton(self.decode_frame, text="LSB (рекомендуется)", variable=self.method_decode, value="lsb").grid(row=row, column=1, padx=5, sticky='w')
ttk.Radiobutton(self.decode_frame, text="DCT (для гладких/одноцветных изображений)", variable=self.method_decode, value="dct").grid(row=row, column=2, padx=5, sticky='w')
row += 1
# Разделитель
ttk.Separator(self.decode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=5)
row += 1
# Изображение
ttk.Label(self.decode_frame, text="Изображение со скрытым сообщением:").grid(row=row, column=0, sticky='w', padx=5, pady=5)
ttk.Entry(self.decode_frame, textvariable=self.image_path, width=50).grid(row=row, column=1, columnspan=2, padx=5)
ttk.Button(self.decode_frame, text="Обзор...", command=self.select_image).grid(row=row, column=3, padx=5)
self.decode_image_entry = ttk.Entry(self.decode_frame, textvariable=self.decode_image_path, width=55)
self.decode_image_entry.grid(row=row, column=1, columnspan=2, padx=5, sticky='ew')
ttk.Button(self.decode_frame, text="Обзор...", command=self.select_decode_image).grid(row=row, column=3, padx=5)
row += 1
# Разделитель
ttk.Separator(self.decode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=5)
row += 1
# Прогресс
self.decode_progress = ttk.Progressbar(self.decode_frame, mode='indeterminate', length=400)
# Кнопка
self.decode_btn = ttk.Button(self.decode_frame, text="Извлечь сообщение", command=self.do_decode)
self.decode_btn.grid(row=row, column=1, columnspan=2, pady=10)
self.decode_btn.grid(row=row, column=1, columnspan=2, pady=15)
row += 1
# Результат
ttk.Label(self.decode_frame, text="Извлеченное сообщение:").grid(row=row, column=0, sticky='nw', padx=5, pady=5)
self.result_text = tk.Text(self.decode_frame, height=10, width=60)
self.result_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5)
self.result_text = tk.Text(self.decode_frame, height=8, width=65, wrap=tk.WORD)
self.result_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5, sticky='ew')
row += 1
# Кнопка сохранения
self.save_btn = ttk.Button(self.decode_frame, text="Сохранить в файл", command=self.save_result, state='disabled')
self.save_btn.grid(row=row, column=1, columnspan=2, pady=5)
# Кнопки
button_frame = ttk.Frame(self.decode_frame)
button_frame.grid(row=row, column=1, columnspan=2, pady=5)
self.save_btn = ttk.Button(button_frame, text="Сохранить в файл", command=self.save_result, state='disabled')
self.save_btn.pack(side='left', padx=5)
ttk.Button(button_frame, text="Очистить результат", command=self.clear_result).pack(side='left', padx=5)
row += 1
# Статус
@@ -143,55 +178,121 @@ class StegoGUI:
def setup_about_tab(self, frame):
"""Вкладка 'О программе'"""
ttk.Label(frame, text="Stego App", font=("Arial", 16)).pack(pady=10)
ttk.Label(frame, text="Стеганографическое приложение").pack()
ttk.Label(frame, text="").pack()
ttk.Label(frame, text="Методы:").pack()
ttk.Label(frame, text=" • LSB (Least Significant Bit) - сокрытие в последних битах пикселей").pack()
ttk.Label(frame, text=" • DCT (Discrete Cosine Transform) - сокрытие в частотной области").pack()
ttk.Label(frame, text="").pack()
ttk.Label(frame, text="Поддерживаемые форматы: PNG, BMP, JPG").pack()
ttk.Label(frame, text="").pack()
ttk.Label(frame, text=" Курсовая работа по стеганографии").pack()
ttk.Label(frame, text="Петров Игорь ИУ10-24").pack()
# Основная информация
info_frame = ttk.Frame(frame)
info_frame.pack(pady=20)
ttk.Label(info_frame, text="Stego App", font=("Arial", 16, "bold")).pack(pady=5)
ttk.Label(info_frame, text="Стеганографическое приложение", font=("Arial", 10)).pack(pady=2)
ttk.Label(info_frame, text="").pack(pady=5)
ttk.Label(info_frame, text="Петров Игорь ИУ10-24").pack()
ttk.Label(info_frame, text="").pack(pady=5)
# Год
ttk.Label(info_frame, text="2026", font=("Arial", 10, "italic")).pack(pady=5)
# Разделитель
ttk.Separator(frame, orient='horizontal').pack(fill='x', padx=20, pady=10)
# Техническая информация
tech_frame = ttk.Frame(frame)
tech_frame.pack(pady=10)
ttk.Label(tech_frame, text="Методы стеганографии:", font=("Arial", 10, "bold")).pack(anchor='w')
ttk.Label(tech_frame, text=" • LSB (Least Significant Bit) - сокрытие в последних битах пикселей").pack(anchor='w')
ttk.Label(tech_frame, text=" Работает с любыми изображениями, высокая вместимость").pack(anchor='w', padx=20)
ttk.Label(tech_frame, text="").pack()
ttk.Label(tech_frame, text=" • DCT (Discrete Cosine Transform) - сокрытие в частотной области").pack(anchor='w')
ttk.Label(tech_frame, text=" Рекомендуется для гладких/однотонных изображений").pack(anchor='w', padx=20)
ttk.Label(tech_frame, text="").pack()
ttk.Label(tech_frame, text="Поддерживаемые форматы:", font=("Arial", 10, "bold")).pack(anchor='w')
ttk.Label(tech_frame, text=" • PNG, BMP, JPG, JPEG").pack(anchor='w')
ttk.Label(tech_frame, text="").pack()
ttk.Label(tech_frame, text="Рекомендации:", font=("Arial", 10, "bold")).pack(anchor='w')
ttk.Label(tech_frame, text=" • Для сохранения результата используйте PNG").pack(anchor='w')
ttk.Label(tech_frame, text=" • LSB - универсальный метод, рекомендуется по умолчанию").pack(anchor='w')
def select_image(self):
"""Выбор изображения"""
def select_encode_image(self):
"""Выбор изображения для кодирования"""
filename = filedialog.askopenfilename(
title="Выберите изображение",
title="Выберите исходное изображение",
filetypes=[("Изображения", "*.png *.bmp *.jpg *.jpeg"), ("Все файлы", "*.*")]
)
if filename:
self.image_path.set(filename)
self.encode_image_path.set(filename)
def select_output(self):
def select_encode_output(self):
"""Выбор выходного файла"""
filename = filedialog.asksaveasfilename(
title="Сохранить как",
title="Сохранить результат как",
defaultextension=".png",
filetypes=[("PNG", "*.png"), ("BMP", "*.bmp"), ("Все файлы", "*.*")]
)
if filename:
self.output_path.set(filename)
self.encode_output_path.set(filename)
def select_decode_image(self):
"""Выбор изображения для декодирования"""
filename = filedialog.askopenfilename(
title="Выберите изображение со скрытым сообщением",
filetypes=[("Изображения", "*.png *.bmp *.jpg *.jpeg"), ("Все файлы", "*.*")]
)
if filename:
self.decode_image_path.set(filename)
def clear_message(self):
"""Очистка поля сообщения"""
self.message_text.delete("1.0", tk.END)
def clear_result(self):
"""Очистка поля результата"""
self.result_text.delete("1.0", tk.END)
self.decoded_message = ""
self.save_btn.config(state='disabled')
def update_capacity(self):
"""Обновление информации о вместимости"""
path = self.image_path.get()
path = self.encode_image_path.get()
if path and os.path.exists(path):
try:
capacity = calculate_capacity(path)
self.capacity_label.config(text=f"Максимальная вместимость: {capacity} байт (~{capacity/1024:.1f} КБ)")
kb = capacity / 1024
self.capacity_label.config(text=f"📊 Максимальная вместимость: {capacity} байт (~{kb:.1f} КБ)")
except Exception as e:
self.capacity_label.config(text=f"Ошибка: {e}")
self.capacity_label.config(text=f"⚠️ Ошибка: {e}")
else:
self.capacity_label.config(text="")
def check_image_for_dct(self, image_path):
"""Проверка изображения для DCT метода"""
try:
from PIL import Image, ImageStat
img = Image.open(image_path)
w, h = img.size
# Проверка размеров
if w % 8 != 0 or h % 8 != 0:
return False, f"Размеры изображения ({w}x{h}) должны быть кратны 8 для DCT метода"
# Проверка "гладкости" изображения
stat = ImageStat.Stat(img)
# Если стандартное отклонение цветов большое (>50) — изображение пёстрое
if max(stat.stddev) > 50:
return False, "Изображение содержит много деталей/цветов.\nDCT рекомендуется только для гладких/однотонных изображений.\nИспользуйте LSB метод."
return True, ""
except Exception as e:
return False, str(e)
def do_encode(self):
"""Выполнение кодирования"""
image = self.image_path.get()
output = self.output_path.get()
image = self.encode_image_path.get()
output = self.encode_output_path.get()
message = self.message_text.get("1.0", tk.END).strip()
method = self.method.get()
method = self.method_encode.get()
if not image:
messagebox.showerror("Ошибка", "Выберите исходное изображение")
@@ -203,11 +304,18 @@ class StegoGUI:
messagebox.showerror("Ошибка", "Введите сообщение для сокрытия")
return
# Проверка для DCT
if method == 'dct':
is_ok, error_msg = self.check_image_for_dct(image)
if not is_ok:
messagebox.showerror("Ошибка", error_msg)
return
# Запускаем в отдельном потоке
self.encode_btn.config(state='disabled')
self.progress.grid(row=5, column=1, columnspan=2, sticky='we', padx=5, pady=5)
self.progress.start()
self.status_label.config(text="Кодирование...")
self.encode_progress.grid(row=11, column=1, columnspan=2, sticky='ew', padx=5, pady=5)
self.encode_progress.start()
self.status_label.config(text="Кодирование... Пожалуйста, подождите", foreground="blue")
thread = threading.Thread(target=self._do_encode, args=(image, output, message, method))
thread.daemon = True
@@ -221,34 +329,38 @@ class StegoGUI:
else:
success = encode_dct(image, message, output)
self.root.after(0, self._encode_done, success)
self.root.after(0, self._encode_done, success, None)
except Exception as e:
self.root.after(0, self._encode_done, False, str(e))
def _encode_done(self, success, error=None):
"""Завершение кодирования"""
self.progress.stop()
self.progress.grid_forget()
self.encode_progress.stop()
self.encode_progress.grid_forget()
self.encode_btn.config(state='normal')
if success:
self.status_label.config(text="✅ Кодирование успешно завершено!", foreground="green")
messagebox.showinfo("Успех", "Сообщение успешно скрыто в изображении!")
messagebox.showinfo("Успех", f"Сообщение успешно скрыто в изображении!\n\nСохранено: {self.encode_output_path.get()}")
else:
self.status_label.config(text=f"❌ Ошибка: {error or 'Кодирование не удалось'}", foreground="red")
messagebox.showerror("Ошибка", error or "Не удалось скрыть сообщение")
error_msg = error or "Неизвестная ошибка"
self.status_label.config(text=f"Ошибка кодирования", foreground="red")
messagebox.showerror("Ошибка", f"Не удалось скрыть сообщение:\n{error_msg}")
def do_decode(self):
"""Выполнение декодирования"""
image = self.image_path.get()
method = self.method.get()
image = self.decode_image_path.get()
method = self.method_decode.get()
if not image:
messagebox.showerror("Ошибка", "Выберите изображение")
return
# Запускаем в отдельном потоке
self.decode_btn.config(state='disabled')
self.decode_status.config(text="Декодирование...")
self.decode_progress.grid(row=2, column=1, columnspan=2, sticky='ew', padx=5, pady=5)
self.decode_progress.start()
self.decode_status.config(text="⏳ Декодирование... Пожалуйста, подождите", foreground="blue")
thread = threading.Thread(target=self._do_decode, args=(image, method))
thread.daemon = True
@@ -262,35 +374,46 @@ class StegoGUI:
else:
message = decode_dct(image)
self.root.after(0, self._decode_done, message)
self.root.after(0, self._decode_done, message, None)
except Exception as e:
self.root.after(0, self._decode_done, None, str(e))
def _decode_done(self, message, error=None):
"""Завершение декодирования"""
self.decode_progress.stop()
self.decode_progress.grid_forget()
self.decode_btn.config(state='normal')
if message and not error:
if message and not error and message != "":
self.decoded_message = message
self.result_text.delete("1.0", tk.END)
self.result_text.insert("1.0", message)
self.save_btn.config(state='normal')
self.decode_status.config(text="✅ Декодирование успешно завершено!", foreground="green")
messagebox.showinfo("Успех", f"✅ Сообщение успешно извлечено!\n\nДлина: {len(message)} символов")
else:
self.decode_status.config(text=f"❌ Ошибка: {error or 'Не удалось извлечь сообщение'}", foreground="red")
error_msg = error or "Сообщение не найдено или повреждено"
self.decode_status.config(text=f"❌ Ошибка декодирования", foreground="red")
self.save_btn.config(state='disabled')
messagebox.showerror("Ошибка", f"Не удалось извлечь сообщение:\n{error_msg}")
def save_result(self):
"""Сохранение извлеченного сообщения в файл"""
if not self.decoded_message:
return
filename = filedialog.asksaveasfilename(
title="Сохранить сообщение",
defaultextension=".txt",
filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")]
)
if filename and self.decoded_message:
with open(filename, 'w', encoding='utf-8') as f:
f.write(self.decoded_message)
messagebox.showinfo("Успех", f"Сообщение сохранено в {filename}")
if filename:
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(self.decoded_message)
messagebox.showinfo("Успех", f"✅ Сообщение сохранено в:\n{filename}")
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось сохранить файл:\n{e}")
def main():