Pull to refresh

Под белым флагом пост, или Как я спас ваш видеокурс от появления на трекере

Reading time 4 min
Views 13K
captain_barbossa.jpg

Привет, Хабр! Хм, у меня такое чувство, будто мы уже встречались… Ах, да. Вот же тот пост, где мы лампово обсудили, приемлемо ли мониторить окружение, ограничивать пользователя в количестве устройств для просмотра, предоставлять исполняемые файлы вместо оплаченных видео и по-другому всячески некультурно себя вести при организации «защиты» видеокурсов от пиратства.

И все бы ничего, да вот только нельзя критиковать, не предлагая взамен своего решения. «Ты можешь лучше, что ли?!», — раздавались возгласы из комментариев. «Лучше бы поддержал соотечественника, помог сделать их продукт лучше!», — вкратце пересказываю я некоторые общие мысли. Справедливо. Так вот, я и правда могу лучше. По крайней мере, мое предложение не будет требовать от конечного пользователя запуска кривого софта вместо ожидаемых видеофайлов.

Решение всех проблем


А решение самое что ни на есть тривиальное, друзья: водяные знаки. Да, всего лишь водяные знаки. Вместо того, чтобы придумывать сложные механизмы привязки к конкретному устройству, «подпишите» видеоряд. Только и всего.

Какими свойствами должен обладать ватермарк для того, чтобы выполнять оборонительную функцию:

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

Если сделать водяной знак сильно прозрачным, его присутствие никак не помешает пользователю, но все равно об этом стоит упомянуть в описании курса перед оплатой.

Таким образом, для извлечения обличающей информации, потенциальному пирату потребовалось бы пойти по одному из нижеописанных сценариев:

  1. Вырезать водяной знак целиком (помним, что согласно 2-у свойству ватермарк должен занимать весь экран и продолжать выполнять свои защитные функции даже при частичном его стирании), тем самым обесценив видеоролик (по-моему, логично, что в случае, когда нет бо́льшей части ролика, нет и ценности ролика).
  2. Редактировать каждый кадр по-отдельности, чтобы избавиться от ватермарка, не нанеся при этом значительного ущерба видеоролику. Трудоемкость выполнения такого действа вручную превышает создание видео «с нуля», а согласно 3-у свойству у нарушителя нет возможности автоматизировать процесс.
  3. (?) Наверно, можно попросить умную нейросеть сделать это за вас. Хотя не уверен, не специалист, можно поправить меня в комментариях.

Proof-of-Concept


За полчаса был составлен тривиальный скрипт в 100 строк, демонстрирующий простоту и доступность реализации такой защиты. Подчеркну: не для того, чтобы показать, какой я умный, а даже совсем наоборот, чтобы отметить, что человек, весьма далекий от обработки изображений, смог за полчаса по кускам составить вполне работающий код (под спойлером), вот как это просто:

fckInfoprotectorV2.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Usage: python3 fckInfoprotectorV2.py

import os
from shutil import rmtree

import numpy as np
import cv2


class VideoSigner:

	def __init__(self, video, watermark):
		os.makedirs('original')
		os.makedirs('watermarked')

		self.vidin = cv2.VideoCapture(video)
		self.fps = self.vidin.get(cv2.CAP_PROP_FPS)
		self.frame_size = (
			int(self.vidin.get(cv2.CAP_PROP_FRAME_WIDTH)),
			int(self.vidin.get(cv2.CAP_PROP_FRAME_HEIGHT))
		)

		self.watermark = cv2.imread(watermark, cv2.IMREAD_UNCHANGED)
		self.wH, self.wW = self.watermark.shape[:2]

		B, G, R, A = cv2.split(self.watermark)
		B = cv2.bitwise_and(B, B, mask=A)
		G = cv2.bitwise_and(G, G, mask=A)
		R = cv2.bitwise_and(R, R, mask=A)
		self.watermark = cv2.merge([B, G, R, A])

	def __del__(self):
		rmtree('original')
		rmtree('watermarked')

	def _split(self):
		print('[*] Splitting video by frames... ', end='', flush=True)

		(success, image), count = self.vidin.read(), 0
		while success:
			path = os.path.join('original', f'{count}.jpg')
			cv2.imwrite(path, image)
			success, image = self.vidin.read()
			count += 1

		print('Done')

	def _watermark(self):
		print('[*] Signing each frame... ', end='', flush=True)

		for image_name in sorted(
			os.listdir('original'),
			key=lambda x: int(x.split('.')[0])
		):
			image_path = os.path.join('original', image_name)
			image = cv2.imread(image_path)
			h, w = image.shape[:2]

			image = np.dstack([
				image,
				np.ones((h, w), dtype='uint8') * 255
			])

			overlay = np.zeros((h, w, 4), dtype='uint8')
			half_h_diff = (h - self.wH) // 2
			half_w_diff = (w - self.wW) // 2
			overlay[half_h_diff:half_h_diff + self.wH, half_w_diff:half_w_diff + self.wW] = self.watermark

			output = image.copy()
			cv2.addWeighted(overlay, 0.25, output, 1.0, 0, output)

			path = os.path.join('watermarked', image_name)
			cv2.imwrite(path, output)

		print('Done')

	def _merge(self):
		print('[*] Merging signed frames... ', end='', flush=True)

		self.vidout = cv2.VideoWriter(
			'signed.avi',
			cv2.VideoWriter_fourcc(*'XVID'),
			fps=self.fps,
			frameSize=self.frame_size
		)

		for image_name in sorted(
			os.listdir('watermarked'),
			key=lambda x: int(x.split('.')[0])
		):
			image_path = os.path.join('watermarked', image_name)
			image = cv2.imread(image_path)
			self.vidout.write(image)

		print('Done')

	def sign(self):
		self._split()
		self._watermark()
		self._merge()


if __name__ == '__main__':
	signer = VideoSigner('SampleVideo_1280x720_1mb.mp4', 'watermark.png')
	signer.sign()


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

sample_original.gif

sample_signed.gif


Не хайпа ради, но только ради общего блага.

Честь имею.
Tags:
Hubs:
-11
Comments 128
Comments Comments 128

Articles