diff --git a/TextPixel/README.md b/TextPixel/README.md new file mode 100644 index 0000000..70202bd --- /dev/null +++ b/TextPixel/README.md @@ -0,0 +1,24 @@ +TextPixel +============== + +Encode / Decode between strings and PNG images. Can be imported or used on the commandline. Since it uses one channel to store each character, this program is only compatible with characters between 0 and 255 in unicode. + +One channel is used to store the character, and the other two are randomized, so the output looks different every time. + +python example: + + encoded_string = textpixel.encode_string('Wow, look!') + textpixel.write_pixels(encoded_string, 'wowlook.png') + + +commandline example: + + > textpixel encode bears.txt bears + Done. + + > textpixel decode bears + Once upon a time there + was a book. + + It was about bears. + So many bears. \ No newline at end of file diff --git a/TextPixel/bears.png b/TextPixel/bears.png new file mode 100644 index 0000000..3ce4e96 Binary files /dev/null and b/TextPixel/bears.png differ diff --git a/TextPixel/bears.txt b/TextPixel/bears.txt new file mode 100644 index 0000000..f56af96 --- /dev/null +++ b/TextPixel/bears.txt @@ -0,0 +1,5 @@ +Once upon a time there +was a book. + +It was about bears. +So many bears. \ No newline at end of file diff --git a/TextPixel/example.png b/TextPixel/example.png new file mode 100644 index 0000000..11ca5c9 Binary files /dev/null and b/TextPixel/example.png differ diff --git a/TextPixel/textpixel.py b/TextPixel/textpixel.py new file mode 100644 index 0000000..0ea18b4 --- /dev/null +++ b/TextPixel/textpixel.py @@ -0,0 +1,169 @@ +import sys +from PIL import Image as PILImage +import random +import string + +ARGS_ENCODE = ['encode', 'save', 'write'] +ARGS_DECODE = ['decode', 'read'] +# Alternate operation names for commandline use. + +def png_filename(filename): + if filename[-4:].lower() != '.png': + filename += '.png' + return filename + +def encode_character(text, densitymin=0, densitymax=300): + ''' + Given a character, take its ord() value and return an + RGB pixel representation. + + The RGB channels will become the ord value, a number + lower than the ord value, and a number higher than the + ord value, though the order will be randomized. + + `densitymin` and `densitymax` control how much lower and higher + the additional values will be relative to the ord value, in %. + + Returns a tuple (R,G,B) + ''' + + densitymin = abs(densitymin) + densitymax = abs(densitymax) + + asciivalue = ord(text) + percentmin = int((asciivalue * densitymin) / 100) + percentmax = int((asciivalue * densitymax) / 100) + + lowerfill = random.randint(asciivalue - percentmax, asciivalue - percentmin) + upperfill = random.randint(asciivalue + percentmin, asciivalue + percentmax) + + lowerfill = max(0, lowerfill) + upperfill = min(255, upperfill) + + rgb = [lowerfill, asciivalue, upperfill] + random.shuffle(rgb) + return tuple(rgb) + +def encode_string(text, densitymin=0, densitymax=300): + ''' + Given a string `text`, map each character to an RGB + pixel, and return these pixels so they can be written to a file. + + `densitymin` and `densitymax` are passed to encode_character + + Returns a list where + [0] is (width,height), + [1] is a dict of pixels: {(x,y) : (R,G,B)} + ''' + text = text.strip() + + lines = text.split('\n') + encoded_height = len(lines) + encoded_width = max([len(line) for line in lines]) + encoded_pixels = {} + + for y in range(encoded_height): + line = lines[y] + for x in range(len(line)): + character = line[x] + pixel = encode_character(character, densitymin, densitymax) + encoded_pixels[(x,y)] = pixel + + dim = (encoded_width, encoded_height) + out = [dim, encoded_pixels] + return out + +def write_pixels(dimout, filename): + ''' + Given pixel data of the form returned by `encode_string()`, + write it to a PNG file named `filename`. + ''' + + filename = png_filename(filename) + + dim = dimout[0] + encoded_pixels = dimout[1] + + image = PILImage.new('RGBA', dim) + + for pixel in encoded_pixels: + image.putpixel(pixel, encoded_pixels[pixel]) + + image.save(filename) + +############################################################################## + +def decode_pixel(pixel): + ''' + Given a tuple (R,G,B), return the decoded text character. + + The character is the str() for the middle value of the tuple. + ("middle" referring to numeric value, not index) + ''' + + pixel = sorted(list(pixel)) + character = chr(pixel[1]) + return character + +def decode_image(image): + ''' + Given an image, return the decoded string + + `image` may be a string representing the filename + or a PIL Image object + ''' + + if isinstance(image, str): + if image[-4:].lower() != '.png': + image += '.png' + image = png_filename(image) + image = PILImage.open(image) + + width = image.size[0] + height = image.size[1] + + decoded_string = '' + + for y in range(height): + for x in range(width): + pixel = image.getpixel((x,y)) + if pixel[3] == 0: + break + decoded_string += decode_pixel(pixel) + decoded_string += '\n' + decoded_string = decoded_string.strip() + return decoded_string + +def argsfail(): + print('\ninvalid parameters.') + print('> textpixel.py encode text filename') + print('> textpixel.py decode filename') + quit() + +if __name__ == '__main__': + if len(sys.argv) < 3: + argsfail() + + op = sys.argv[1].lower() + + if op in ARGS_ENCODE and len(sys.argv) == 4: + text = sys.argv[2] + if text[-4:].lower() == '.txt': + try: + tfile = open(text, 'r') + text = tfile.read() + tfile.close() + except FileNotFoundError: + pass + + filename = sys.argv[3] + pixels = encode_string(text) + write_pixels(pixels, filename) + print('Done.') + + elif op in ARGS_DECODE and len(sys.argv) == 3: + filename = sys.argv[2] + print(decode_image(filename)) + + else: + argsfail() diff --git a/TotalDL/totaldl.py b/TotalDL/totaldl.py index 516cb1e..23181de 100644 --- a/TotalDL/totaldl.py +++ b/TotalDL/totaldl.py @@ -252,13 +252,15 @@ def handle_twitter(url, customname=None): try: link = pagedata.split('data-url="')[1] link = link.split('"')[0] - handle_master(link, customname=customname) + if link != url: + handle_master(link, customname=customname) return except IndexError: try: link = pagedata.split('data-expanded-url="')[1] link = link.split('"')[0] - handle_master(link, customname=customname) + if link != url: + handle_master(link, customname=customname) return except IndexError: pass @@ -366,6 +368,9 @@ def test_twitter(): # Twitter plain text handle_master('https://twitter.com/cp_orange_x3/status/599700702382817280') + # Twitter plain text + handle_master('https://twitter.com/SyriacMFS/status/556513635913437184') + def test_generic(): # Some link that might work handle_master('https://raw.githubusercontent.com/voussoir/reddit/master/SubredditBirthdays/show/statistics.txt')