2020-10-05 12:33:16 +02:00
|
|
|
from bitstring import Bits, BitArray
|
|
|
|
from magic import Magic
|
|
|
|
import mimetypes
|
|
|
|
from PIL import Image
|
|
|
|
import glob
|
|
|
|
|
2020-10-09 20:13:06 +02:00
|
|
|
from operator import sub
|
2020-10-05 12:33:16 +02:00
|
|
|
import numpy as np
|
|
|
|
from tqdm import tqdm
|
|
|
|
import ffmpeg
|
|
|
|
|
|
|
|
import binascii
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
|
|
DELIMITER = bin(int.from_bytes("HELLO MY NAME IS ALFREDO".encode(), "big"))
|
|
|
|
FRAMES_DIR = "./fvid_frames/"
|
|
|
|
|
|
|
|
|
|
|
|
def get_bits_from_file(filepath):
|
|
|
|
bitarray = BitArray(filename=filepath)
|
|
|
|
|
|
|
|
# adding a delimiter to know when the file ends to avoid corrupted files
|
|
|
|
# when retrieving
|
|
|
|
bitarray.append(DELIMITER)
|
|
|
|
|
|
|
|
return bitarray.bin
|
|
|
|
|
2020-10-09 19:22:40 +02:00
|
|
|
def less(val1, val2):
|
|
|
|
return val1 < val2
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
def get_bits_from_image(image):
|
|
|
|
width, height = image.size
|
|
|
|
|
|
|
|
done = False
|
|
|
|
|
|
|
|
px = image.load()
|
|
|
|
bits = ""
|
|
|
|
|
|
|
|
delimiter_str = DELIMITER.replace("0b", "")
|
|
|
|
delimiter_length = len(delimiter_str)
|
|
|
|
|
|
|
|
pbar = tqdm(range(height), desc="Getting bits from frame")
|
|
|
|
|
2020-10-07 16:56:09 +02:00
|
|
|
white = (255, 255, 255)
|
|
|
|
black = (0, 0, 0)
|
|
|
|
|
2020-10-05 12:33:16 +02:00
|
|
|
for y in pbar:
|
|
|
|
for x in range(width):
|
|
|
|
|
|
|
|
# check if we have hit the delimiter
|
|
|
|
if bits[-delimiter_length:] == delimiter_str:
|
|
|
|
# remove delimiter from bit data to have an exact one to one
|
|
|
|
# copy when decoding
|
|
|
|
|
|
|
|
bits = bits[: len(bits) - delimiter_length]
|
|
|
|
|
|
|
|
pbar.close()
|
|
|
|
|
|
|
|
return (bits, True)
|
|
|
|
|
|
|
|
pixel = px[x, y]
|
|
|
|
|
2020-10-08 15:59:24 +02:00
|
|
|
pixel_bin_rep = "0"
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
# for exact matches
|
|
|
|
if pixel == white:
|
2020-10-08 15:59:24 +02:00
|
|
|
pixel_bin_rep = "1"
|
2020-10-05 12:33:16 +02:00
|
|
|
elif pixel == black:
|
2020-10-08 15:59:24 +02:00
|
|
|
pixel_bin_rep = "0"
|
2020-10-05 12:33:16 +02:00
|
|
|
else:
|
2020-10-09 18:45:14 +02:00
|
|
|
white_diff = tuple(map(abs, map(sub, white, pixel)))
|
2020-10-05 12:33:16 +02:00
|
|
|
# min_diff = white_diff
|
2020-10-09 18:45:14 +02:00
|
|
|
black_diff = tuple(map(abs, map(sub, black, pixel)))
|
|
|
|
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
# if the white difference is smaller, that means the pixel is closer
|
|
|
|
# to white, otherwise, the pixel must be black
|
2020-10-09 18:45:14 +02:00
|
|
|
if all(map(less, white_diff, black_diff)):
|
2020-10-08 15:59:24 +02:00
|
|
|
pixel_bin_rep = "1"
|
2020-10-05 12:33:16 +02:00
|
|
|
else:
|
2020-10-08 15:59:24 +02:00
|
|
|
pixel_bin_rep = "0"
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
# adding bits
|
2020-10-08 15:59:24 +02:00
|
|
|
bits += pixel_bin_rep
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
return (bits, done)
|
|
|
|
|
|
|
|
|
|
|
|
def get_bits_from_video(video_filepath):
|
|
|
|
# get image sequence from video
|
|
|
|
image_sequence = []
|
|
|
|
|
|
|
|
ffmpeg.input(video_filepath).output(
|
|
|
|
f"{FRAMES_DIR}decoded_frames%03d.png"
|
|
|
|
).run(quiet=True)
|
|
|
|
|
|
|
|
for filename in glob.glob(f"{FRAMES_DIR}decoded_frames*.png"):
|
|
|
|
image_sequence.append(Image.open(filename))
|
|
|
|
|
|
|
|
bits = ""
|
|
|
|
sequence_length = len(image_sequence)
|
|
|
|
|
|
|
|
for index in range(sequence_length):
|
|
|
|
b, done = get_bits_from_image(image_sequence[index])
|
|
|
|
|
|
|
|
bits += b
|
|
|
|
|
|
|
|
if done:
|
|
|
|
break
|
|
|
|
|
|
|
|
return bits
|
|
|
|
|
|
|
|
|
2020-10-08 01:47:50 +02:00
|
|
|
def save_bits_to_file(file_path, bits):
|
2020-10-05 12:33:16 +02:00
|
|
|
# get file extension
|
|
|
|
|
|
|
|
bitstring = Bits(bin=bits)
|
|
|
|
|
|
|
|
mime = Magic(mime=True)
|
|
|
|
mime_type = mime.from_buffer(bitstring.tobytes())
|
|
|
|
|
2020-10-07 16:19:45 +02:00
|
|
|
# If filepath not passed in use defualt
|
|
|
|
# otherwise used passed in filepath
|
|
|
|
if file_path == None:
|
|
|
|
filepath = f"file{mimetypes.guess_extension(type=mime_type)}"
|
|
|
|
else:
|
|
|
|
filepath = file_path
|
|
|
|
|
2020-10-05 12:33:16 +02:00
|
|
|
with open(
|
2020-10-07 16:19:45 +02:00
|
|
|
filepath, "wb"
|
2020-10-05 12:33:16 +02:00
|
|
|
) as f:
|
|
|
|
bitstring.tofile(f)
|
|
|
|
|
|
|
|
|
|
|
|
def make_image(bit_set, resolution=(1920, 1080)):
|
|
|
|
|
|
|
|
width, height = resolution
|
|
|
|
|
|
|
|
image = Image.new("1", (width, height))
|
|
|
|
image.putdata(bit_set)
|
|
|
|
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
def split_list_by_n(lst, n):
|
|
|
|
for i in range(0, len(lst), n):
|
|
|
|
yield lst[i : i + n]
|
|
|
|
|
|
|
|
|
|
|
|
def make_image_sequence(bitstring, resolution=(1920, 1080)):
|
|
|
|
width, height = resolution
|
|
|
|
|
|
|
|
# split bits into sets of width*height to make (1) image
|
|
|
|
set_size = width * height
|
|
|
|
|
|
|
|
# bit_sequence = []
|
|
|
|
bit_sequence = split_list_by_n(list(map(int, bitstring)), width * height)
|
|
|
|
image_bits = []
|
|
|
|
|
|
|
|
# using bit_sequence to make image sequence
|
|
|
|
|
|
|
|
image_sequence = []
|
|
|
|
|
|
|
|
for bit_set in bit_sequence:
|
|
|
|
image_sequence.append(make_image(bit_set))
|
|
|
|
|
|
|
|
return image_sequence
|
|
|
|
|
|
|
|
|
2020-10-07 18:05:21 +02:00
|
|
|
def make_video(output_filepath, image_sequence, framerate="1/5"):
|
2020-10-05 12:33:16 +02:00
|
|
|
|
2020-10-07 16:19:45 +02:00
|
|
|
if output_filepath == None:
|
|
|
|
outputfile = "file.mp4"
|
|
|
|
else:
|
|
|
|
outputfile = output_filepath
|
|
|
|
|
|
|
|
|
2020-10-05 12:33:16 +02:00
|
|
|
frames = glob.glob(f"{FRAMES_DIR}encoded_frames*.png")
|
|
|
|
|
|
|
|
# for one frame
|
|
|
|
if len(frames) == 1:
|
|
|
|
ffmpeg.input(frames[0], loop=1, t=1).output(
|
2020-10-07 16:19:45 +02:00
|
|
|
outputfile, vcodec="libx264rgb"
|
2020-10-05 12:33:16 +02:00
|
|
|
).run(quiet=True)
|
|
|
|
|
|
|
|
else:
|
|
|
|
ffmpeg.input(
|
|
|
|
f"{FRAMES_DIR}encoded_frames*.png",
|
|
|
|
pattern_type="glob",
|
2020-10-07 18:05:21 +02:00
|
|
|
framerate=framerate,
|
2020-10-07 16:19:45 +02:00
|
|
|
).output(outputfile, vcodec="libx264rgb").run(quiet=True)
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup():
|
|
|
|
# remove frames
|
|
|
|
import shutil
|
|
|
|
|
|
|
|
shutil.rmtree(FRAMES_DIR)
|
|
|
|
|
|
|
|
|
|
|
|
def setup():
|
|
|
|
import os
|
|
|
|
|
|
|
|
if not os.path.exists(FRAMES_DIR):
|
|
|
|
os.makedirs(FRAMES_DIR)
|
|
|
|
|
|
|
|
|
2020-10-07 16:14:32 +02:00
|
|
|
def main():
|
2020-10-05 12:33:16 +02:00
|
|
|
parser = argparse.ArgumentParser(description="save files as videos")
|
|
|
|
parser.add_argument(
|
|
|
|
"-e", "--encode", help="encode file as video", action="store_true"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-d", "--decode", help="decode file from video", action="store_true"
|
|
|
|
)
|
|
|
|
|
|
|
|
parser.add_argument("-i", "--input", help="input file", required=True)
|
|
|
|
parser.add_argument("-o", "--output", help="output path")
|
2020-10-08 15:38:31 +02:00
|
|
|
parser.add_argument("-f", "--framerate", help="set framerate for encoding (as a fraction)", default="1/5", type=str)
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
setup()
|
|
|
|
|
|
|
|
if args.decode:
|
|
|
|
bits = get_bits_from_video(args.input)
|
|
|
|
|
2020-10-07 16:19:45 +02:00
|
|
|
file_path = None
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
if args.output:
|
|
|
|
file_path = args.output
|
|
|
|
|
2020-10-07 16:19:45 +02:00
|
|
|
save_bits_to_file(file_path, bits)
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
elif args.encode:
|
2020-10-07 20:19:58 +02:00
|
|
|
# isdigit has the benefit of being True and raising an error if the user passes a negative string
|
|
|
|
# all() lets us check if both the negative sign and forward slash are in the string, to prevent negative fractions
|
|
|
|
if (not args.framerate.isdigit() and "/" not in args.framerate) or all(x in args.framerate for x in ("-", "/")):
|
2020-10-07 20:13:52 +02:00
|
|
|
raise NotImplementedError("The framerate must be a positive fraction or an integer for now, like 3, '1/3', or '1/5'!")
|
2020-10-05 12:33:16 +02:00
|
|
|
# get bits from file
|
|
|
|
bits = get_bits_from_file(args.input)
|
|
|
|
|
|
|
|
# create image sequence
|
|
|
|
image_sequence = make_image_sequence(bits)
|
|
|
|
|
|
|
|
# save images
|
|
|
|
for index in range(len(image_sequence)):
|
|
|
|
image_sequence[index].save(
|
|
|
|
f"{FRAMES_DIR}encoded_frames_{index}.png"
|
|
|
|
)
|
|
|
|
|
2020-10-07 16:19:45 +02:00
|
|
|
video_file_path = None
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
if args.output:
|
|
|
|
video_file_path = args.output
|
|
|
|
|
2020-10-07 18:05:21 +02:00
|
|
|
make_video(video_file_path, image_sequence, args.framerate)
|
2020-10-05 12:33:16 +02:00
|
|
|
|
|
|
|
cleanup()
|