Ожившие фото: Создаем приватный стеганографический аудиоплеер на Python и PyQt6

от автора

Привет, Хабр!

Вы когда-нибудь хотели, чтобы ваши фотографии могли рассказывать истории? Не в переносном смысле, а буквально. А что, если бы эти истории были предназначены только для вас? Представьте, что вы отправляете другу обычный с виду PNG-файл, но внутри него скрыто личное аудиопоздравление, которое не увидит ни один почтовый сервис или мессенджер. Или ведете цифровой фотодневник, где за каждым снимком скрывается голосовая заметка с вашими мыслями, надежно спрятанная от посторонних глаз.

Это не магия, а стеганография. Сегодня я расскажу о проекте ChameleonLab, а точнее — о его уникальной функции: стеганографическом имидж-плеере. Это десктопное приложение, которое позволяет не только прятать аудиофайлы внутри изображений, но и проигрывать их, как в обычном плеере, создавая новый способ для приватного и творческого обмена информацией. Проект уже имеет готовые сборки для Windows и macOS.

Программа "ChameleonLab". Стеганографический имидж-плеер

Программа «ChameleonLab». Стеганографический имидж-плеер

Зачем это нужно? Приватность и творчество

Идея прятать один файл в другом не нова. Но большинство утилит созданы для специалистов. Мы же сфокусировались на практическом применении для всех.

  • Самое главное — конфиденциальность. В цифровую эпоху сложно быть уверенным в приватности пересылаемых данных. Наш плеер предлагает решение: вы можете отправить фотографию через любой открытый канал (почту, соцсеть), и только тот, у кого есть приложение ChameleonLab, сможет узнать о существовании скрытого аудиосообщения и прослушать его. Это идеальный способ передать личную информацию, не вызывая подозрений.

  • «Живые» фотоальбомы: Сохраняйте короткие аудиозаметки или окружающие звуки прямо в ваших фотографиях. Фотография ребенка с его первым словом, снимок с концерта с фрагментом выступления, фото с дня рождения с поздравлениями — все это хранится в одном PNG-файле, скрытое от посторонних.

  • Образование и искусство: Представьте интерактивную выставку в музее или урок ИЗО. Ученики открывают на планшетах репродукции картин, и каждая картина «рассказывает» голосом экскурсовода о своей истории, авторе и технике.

Как это работает: Погружение в код

В основе всего лежит классический метод стеганографии LSB (Least Significant Bit). Если кратко, мы берем наименее значимые биты каждого цветового компонента (R, G, B) каждого пикселя и заменяем их битами нашего аудиофайла. Для человеческого глаза эти изменения абсолютно незаметны.

В качестве контейнера мы используем формат PNG, потому что он сжимает данные без потерь. Использование JPG для этих целей губительно, так как его алгоритмы сжатия с потерями разрушат и исказят спрятанную информацию.

В нашем приложении есть два ключевых компонента: Создатель и Плеер.

Шаг за шагом: Создаем наше первое аудио-фото

Мы встроили в плеер вкладку «Создатель», которая делает процесс максимально простым.

  1. Выбираем изображение-контейнер. Можно перетащить файл в левое окно. Поддерживаются PNG, JPG, BMP. Любой формат на выходе будет конвертирован в PNG.

  2. Выбираем аудиофайл. В правое окно перетаскиваем аудиофайл (.mp3 или .wav).

  3. Проверяем вместимость. Программа автоматически рассчитывает, поместится ли аудиофайл в картинку. Если нет, можно поставить галочку «Автоматически расширять…», и программа добавит к изображению снизу черные пиксели, чтобы увеличить его емкость, не искажая оригинал.

  4. Создаем! Нажимаем кнопку «Создать и сохранить», и получаем наш гибридный PNG-файл.

Программа "ChameleonLab". Создатель

Программа «ChameleonLab». Создатель

Под капотом «Создателя»

За этот процесс отвечает фоновый воркер PlayerCreateWorker. Главная работа происходит в функции steganography_core.hide(). Перед тем как спрятать аудио, мы формируем «полезную нагрузку» (payload) по простому формату:

[Имя файла в UTF-8] + [Символ-разделитель '|'] + [Байты аудиофайла]

Это позволяет нам при извлечении узнать оригинальное имя файла. Воркер в фоновом потоке выполняет всю тяжелую работу: расширяет изображение (если нужно), читает аудиофайл и вызывает stego.hide(), чтобы побитово вписать данные в пиксели.

Вот ключевая часть кода из workers.py:

# Из файла ui/workers.py class PlayerCreateWorker(QtCore.QObject):     # ... сигналы ...     def __init__(self, carrier_data, audio_path, n_bits, should_pad):         # ...          def run(self):         try:             # ... расчет необходимого размера ...              if required_size > capacity_bytes:                 if not self.should_pad:                     raise ValueError(t("embed_log_conclusion_fail"))                                  # Логика расширения холста изображения                 h, w, c = self.carrier_data.shape                 # ... расчет новых размеров ...                 new_h = math.ceil(required_pixels / w)                 padded_image = np.zeros((new_h, w, c), dtype=np.uint8)                 padded_image[0:h, :, :] = self.carrier_data                 self.carrier_data = padded_image                          self.progress.emit(40)                          with open(self.audio_path, 'rb') as f:                 audio_bytes = f.read()                          secret_filename = Path(self.audio_path).name             packaged_data = secret_filename.encode('utf-8') + b'|' + audio_bytes                          self.progress.emit(60)             output_payload = stego.hide(self.carrier_data, packaged_data, self.n_bits, is_encrypted=False)             self.progress.emit(95)             self.finished.emit(output_payload)          except Exception as e:             self.error.emit(str(e)) 

Сердце плеера: Как извлечь и проиграть звук

Процесс воспроизведения обратный и тоже выполняется в фоновом потоке, чтобы интерфейс не зависал.

  1. Мгновенный предпросмотр: Как только пользователь выбирает трек, мы сразу загружаем и отображаем картинку.

  2. Извлечение в фоне: Одновременно запускается PlayerRevealWorker. Он открывает PNG, считывает LSB-биты пикселей и восстанавливает из них спрятанный пакет данных ([имя файла]|[аудио]).

  3. Воспроизведение: Когда воркер завершает работу, он передает извлеченные аудиобайты основному потоку. Мы сохраняем эти байты во временный файл на диске и передаем его стандартному QMediaPlayer для воспроизведения.

  4. Очистка: Временный файл автоматически удаляется после проигрывания или при закрытии программы.

Вот как выглядит воркер для извлечения:

# Из файла ui/workers.py class PlayerRevealWorker(QtCore.QObject):     finished = QtCore.pyqtSignal(bytes) # audio_bytes     error = QtCore.pyqtSignal(str)     progress = QtCore.pyqtSignal(int)      def __init__(self, image_path):         super().__init__()         self.image_path = image_path      def run(self):         try:             self.progress.emit(20)             carrier_data, _, _ = file_handlers.read_file(self.image_path)             self.progress.emit(50)             packaged_data, _, found = stego.reveal(carrier_data)             self.progress.emit(90)              if not found:                 raise ValueError(t("player_error_no_audio"))              try:                 _, audio_bytes = packaged_data.split(b'|', 1)             except ValueError:                 audio_bytes = packaged_data                          self.finished.emit(audio_bytes)         except Exception as e:             self.error.emit(str(e)) 

Трудности и решения

В процессе разработки мы столкнулись с несколькими классическими проблемами:

  • Сбои потоков: QThread: Destroyed while thread is still running — эта головная боль всех, кто работает с многопоточностью в PyQt. Решилась установкой родительского виджета для QThread (QtCore.QThread(self)), что создает жесткую связь и не дает сборщику мусора удалить объект потока раньше времени.

  • Зависание интерфейса: Изначально все операции выполнялись в основном потоке, что приводило к зависанию приложения на несколько секунд. Перенос всей тяжелой логики в классы-воркеры (QObject) и запуск их через QThread полностью решил эту проблему, сделав интерфейс отзывчивым.

Заключение

Проект ChameleonLab и его имидж-плеер — это пример того, как можно взять известную технологию и найти для нее новое, творческое и, что самое важное, приватное применение. Мы получили не просто утилиту, а интуитивно понятный инструмент для создания нового типа контента, где у каждого изображения есть второй, скрытый аудио-слой.

Это не только инструмент для творчества, позволяющий создавать «живые» фотографии, но и способ защитить личные аудиовоспоминания и сообщения в нашем излишне открытом цифровом мире.

Проект ChameleonLab уже доступен в виде готовых сборок для Windows и macOS, позволяя каждому желающему попробовать создать свои собственные «живые» и секретные фотографии уже сегодня.

Мы продолжим прислушиваться к вам и развивать ChameleonLab. Огромное спасибо за ваше участие и помощь!

Скачать:


ссылка на оригинал статьи https://habr.com/ru/articles/942270/