encode.py (3467B)
1 import numpy as np 2 import math 3 import os 4 import sys 5 from PIL import Image 6 import random 7 import string 8 9 def get_rnd_str(length): 10 random_string = ''.join(random.choices(string.ascii_letters + string.digits, k=length)) 11 return(random_string) 12 13 # idea: 14 # encode text into XxX matrix for white/black bits 15 # use image magick or smt 16 # ffmpeg -> vid 17 18 19 def byte_to_bits(byte): 20 return [(byte >> i) & 1 for i in reversed(range(8))] 21 22 23 # assume 1920x1080 24 # how many bits? 25 # 2073600 bits! 26 # 259,200 bytes 27 28 # this should be a deterministic size for ease of encoding/decoding 29 30 # reserved header: 31 # file name - 100 bytes - 800 bits / pixels 32 # chunk number - 4 bytes - 32 pixels 33 # bytes encoded - 4 bytes - 32 pixels 34 35 # 108 * 8 = 864 36 37 def make_header(file_name, chunk_number, bytes_encoded): 38 file_name_bytes = file_name.encode("utf-8") 39 if len(file_name_bytes) > 100: 40 file_name_bytes = file_name_bytes[:100] 41 else: 42 file_name_bytes = file_name_bytes.ljust(100, b'\x00') 43 44 chunk_num = np.int32(chunk_number).tobytes() 45 num_bytes = np.int32(bytes_encoded).tobytes() 46 47 header = file_name_bytes + chunk_num + num_bytes 48 assert len(header) == 108 49 return header 50 51 # reserved header is 108 bytes 52 53 # 259,200 - 108 = 259,092 54 55 x = int(float(sys.argv[1])) 56 y = int(float(sys.argv[2])) 57 total_bits_available = x * y 58 bits_for_data = total_bits_available - 864 59 bytes_per_frame = bits_for_data // 8 60 61 print("Max bytes per frame: " + str(bytes_per_frame)) 62 63 filename = sys.argv[3] 64 destination = sys.argv[4] 65 destination.removesuffix("/") 66 67 if not os.path.exists(destination): 68 os.mkdir(destination) 69 70 print("x: " + str(x)) 71 print("y: " + str(y)) 72 print("filename: " + filename) 73 74 file_size_bytes = os.path.getsize(filename) 75 print("File size in bytes: " + str(file_size_bytes)) 76 77 78 chunks_required = math.ceil(file_size_bytes / bytes_per_frame) 79 print("Chunks required: " + str(chunks_required)) 80 81 if chunks_required > 1: 82 bytes_per_chunk = [bytes_per_frame] * (chunks_required - 1) 83 bytes_per_chunk.append(file_size_bytes % bytes_per_frame) 84 else: 85 bytes_per_chunk = [file_size_bytes] 86 87 assert sum(bytes_per_chunk) == file_size_bytes 88 89 print("Bytes per chunk: " + str(bytes_per_chunk)) 90 91 92 with open(filename, "rb") as file: 93 file_bytes = file.read() 94 95 # ENSURE HEADER IS 108 BYTES 96 # SIDE EFFECT FOR ARR 97 # BYTES TO WRITE IS NUM 98 # BYTES STR IS DATA 99 def create_image(arr, header, byte_str, start_idx, bytes_to_write, x): 100 101 written = 0 102 103 offset = 0 104 for byte in header: 105 for bit in byte_to_bits(byte): 106 x_idx = offset % x 107 y_idx = offset // x 108 arr[y_idx][x_idx] = 255 if bit else 0 109 offset += 1 110 written += 1 111 112 assert offset == 108 * 8 113 114 for i in range(bytes_to_write): 115 byte = byte_str[start_idx + i] 116 for bit in byte_to_bits(byte): 117 x_idx = offset % x 118 y_idx = offset // x 119 arr[y_idx][x_idx] = 255 if bit else 0 120 written += 1 121 offset += 1 122 return written 123 124 125 index = 0 126 chunk_num = 1 127 for chunk_size in bytes_per_chunk: 128 arr = np.zeros((y,x)) 129 written = create_image(arr, make_header(filename, chunk_num, chunk_size), file_bytes, index, chunk_size, x) 130 img = Image.fromarray(arr) 131 img = img.convert('L') 132 fname = destination + "/" + str(get_rnd_str(16)) + ".png" 133 print("Writing to: " + fname) 134 img.save(fname) 135 index += chunk_size 136 chunk_num += 1 137 138 assert index == file_size_bytes