else
BIN
ImageFilters/ear.jpg
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
ImageFilters/ear.png
Normal file
After Width: | Height: | Size: 343 KiB |
97
ImageFilters/imagefilters.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
import PIL.Image
|
||||
|
||||
KERNEL_GAUSSIAN_BLUR = [
|
||||
[1, 2, 1],
|
||||
[2, 3, 2],
|
||||
[1, 2, 1],
|
||||
]
|
||||
KERNEL_EDGE_DETECTION_H = [
|
||||
[-2, 0, 2],
|
||||
[-2, 0, 2],
|
||||
[-2, 0, 2],
|
||||
]
|
||||
def index_to_xy(index, width):
|
||||
(y, x) = divmod(index, width)
|
||||
return (x, y)
|
||||
|
||||
def xy_to_index(x, y, width):
|
||||
return (y * width) + x
|
||||
|
||||
def apply_filter(old_image, kernel):
|
||||
kernel_height = len(kernel)
|
||||
kernel_width = len(kernel[0])
|
||||
if (kernel_height % 2 != 1) or (kernel_width % 2 != 1):
|
||||
raise ValueError('Kernel is not of odd size')
|
||||
|
||||
if any(len(segment) != kernel_width for segment in kernel):
|
||||
raise ValueError('Kernel is of inconsistent size')
|
||||
|
||||
kernel_center = (kernel_width // 2, kernel_height // 2)
|
||||
flat_kernel = list(flatten_list(kernel))
|
||||
lower = min(flat_kernel)
|
||||
lower = min(0, lower * 255)
|
||||
upper = max(flat_kernel)
|
||||
upper = max(255, upper * 255)
|
||||
print(lower, upper)
|
||||
|
||||
(image_width, image_height) = old_image.size
|
||||
old_pixels = old_image.getdata()
|
||||
new_pixels = list(old_image.getdata())
|
||||
|
||||
for (index, old_pixel) in enumerate(old_pixels):
|
||||
operation_sum = 0
|
||||
operation_denominator = 0
|
||||
(x, y) = index_to_xy(index, image_width)
|
||||
#print(x, y, index)
|
||||
for (kernel_y, kernel_row) in enumerate(kernel):
|
||||
#print(kernel_row)
|
||||
subject_y = y - (kernel_center[1] - kernel_y)
|
||||
if subject_y < 0 or subject_y >= image_height:
|
||||
continue
|
||||
for (kernel_x, kernel_entry) in enumerate(kernel_row):
|
||||
subject_x = x - (kernel_center[0] - kernel_x)
|
||||
if subject_x < 0 or subject_x >= image_width:
|
||||
continue
|
||||
subject = old_pixels[xy_to_index(subject_x, subject_y, image_width)]
|
||||
#print(x, y, subject_x, subject_y, kernel_entry, subject)
|
||||
operation_sum += kernel_entry * subject
|
||||
operation_denominator += kernel_entry
|
||||
|
||||
operation_denominator = max(1, operation_denominator)
|
||||
operation_avg = abs(operation_sum / operation_denominator)
|
||||
#n_operation_avg = int(map_range(operation_avg, lower, upper, 0, 255))
|
||||
if index % 4096 == 0:
|
||||
print(x, y, operation_sum, operation_denominator, operation_avg)
|
||||
#print(y, '/', image_height)
|
||||
new_pixels[index] = operation_avg
|
||||
|
||||
#print(new_pixels)
|
||||
new_image = PIL.Image.new('L', (old_image.size))
|
||||
new_image.putdata(new_pixels, 1, 0)
|
||||
#print(new_pixels)
|
||||
#print(list(new_image.getdata()))
|
||||
return new_image
|
||||
|
||||
def flatten_list(li):
|
||||
for element in li:
|
||||
if hasattr(element, '__iter__'):
|
||||
yield from flatten_list(element)
|
||||
else:
|
||||
yield element
|
||||
|
||||
def map_range(x, old_low, old_high, new_low, new_high):
|
||||
'''
|
||||
Given a number x in range [old_low, old_high], return corresponding
|
||||
number in range [new_low, new_high].
|
||||
'''
|
||||
if x > old_high or x < old_low:
|
||||
raise ValueError('%d not in range [%d..%d]' % (x, old_low, old_high))
|
||||
percentage = (x - old_low) / (old_high - old_low)
|
||||
y = (percentage * (new_high - new_low)) + new_low
|
||||
return y
|
||||
|
||||
if __name__ == '__main__':
|
||||
i = PIL.Image.open('ear.jpg')
|
||||
i = i.convert('L')
|
||||
i = apply_filter(apply_filter(i, KERNEL_GAUSSIAN_BLUR), KERNEL_EDGE_DETECTION_H)
|
||||
i.save('ear.png')
|
21
Minecraft3DVector/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
Minecraft 3D Vector
|
||||
===================
|
||||
|
||||
Given one, two, or three square images, produce a 3-dimensional rendering of them as a block in glorious SVG.
|
||||
|
||||
The svg has two layers. They contain identical shapes, but the lower one is slightly blurred. Although the shapes are aligned pretty well, some rendering engines create a 1px gap between shapes due to rounding. The blurred object helps fill these in. A mask is used to make sure the blur does not cause a halo around the edges. The seams still aren't perfect but they're pretty good, man.
|
||||
|
||||
Because each pixel of each face produces two shapes, you should probably stick to small source images.
|
||||
|
||||
##Usage:
|
||||
|
||||
> mc3dvector.py dirt.png
|
||||
Creates a block with dirt on all sides
|
||||
|
||||
> mc3dvector.py tree_top.png tree_side.png
|
||||
Creates a block with tree_top on the top, and tree_side on both sides.
|
||||
|
||||
> mc3dvector.py dirt.png gravel.png obsidian.png
|
||||
Creates a block with dirt on top, gravel on the left, and obsidian on the right.
|
||||
|
||||
The images do not need to be the same size as each other, but they each must be square.
|
BIN
Minecraft3DVector/examples/rendering_furnace_2048.png
Normal file
After Width: | Height: | Size: 388 KiB |
BIN
Minecraft3DVector/examples/rendering_hellsand_1024.png
Normal file
After Width: | Height: | Size: 215 KiB |
BIN
Minecraft3DVector/examples/rendering_minecraft_2048.png
Normal file
After Width: | Height: | Size: 426 KiB |
BIN
Minecraft3DVector/examples/rendering_obsidian_1024.png
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
Minecraft3DVector/examples/rendering_tree_1024.png
Normal file
After Width: | Height: | Size: 191 KiB |
BIN
Minecraft3DVector/examples/source_furnace_left.png
Normal file
After Width: | Height: | Size: 661 B |
BIN
Minecraft3DVector/examples/source_furnace_right.png
Normal file
After Width: | Height: | Size: 564 B |
BIN
Minecraft3DVector/examples/source_furnace_top.png
Normal file
After Width: | Height: | Size: 550 B |
BIN
Minecraft3DVector/examples/source_hellsand.png
Normal file
After Width: | Height: | Size: 633 B |
BIN
Minecraft3DVector/examples/source_minecraft_left.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Minecraft3DVector/examples/source_minecraft_right.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Minecraft3DVector/examples/source_minecraft_top.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Minecraft3DVector/examples/source_obsidian.png
Normal file
After Width: | Height: | Size: 489 B |
BIN
Minecraft3DVector/examples/source_tree_side.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
Minecraft3DVector/examples/source_tree_top.png
Normal file
After Width: | Height: | Size: 478 B |
9319
Minecraft3DVector/examples/svg_furnace.svg
Normal file
After Width: | Height: | Size: 295 KiB |
9319
Minecraft3DVector/examples/svg_hellsand.svg
Normal file
After Width: | Height: | Size: 295 KiB |
9319
Minecraft3DVector/examples/svg_minecraft.svg
Normal file
After Width: | Height: | Size: 295 KiB |
9319
Minecraft3DVector/examples/svg_obsidian.svg
Normal file
After Width: | Height: | Size: 295 KiB |
9319
Minecraft3DVector/examples/svg_tree.svg
Normal file
After Width: | Height: | Size: 295 KiB |
200
Minecraft3DVector/mc3dvector.py
Normal file
|
@ -0,0 +1,200 @@
|
|||
TEMPLATE_PRIMARY = '''
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
viewBox="0 0 744.09448819 1052.3622047"
|
||||
height="297mm"
|
||||
width="210mm" >
|
||||
<defs id="defs4" >
|
||||
<filter
|
||||
id="filter20779"
|
||||
style="color-interpolation-filters:sRGB" />
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter26457"
|
||||
x="-0.012382473"
|
||||
width="1.0247649"
|
||||
y="-0.011640447"
|
||||
height="1.0232809" >
|
||||
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur26459"
|
||||
stdDeviation="3.2073868" />
|
||||
</filter>
|
||||
<mask
|
||||
id="mask26461"
|
||||
maskUnits="userSpaceOnUse" >
|
||||
<g
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
transform="matrix(1.5199904,0,0,1.5199673,-1612.5515,-1151.5565)"
|
||||
id="g26463" >
|
||||
<path
|
||||
id="path26465"
|
||||
transform="matrix(0.65789889,0,0,0.65790889,1060.8942,757.62709)"
|
||||
d="
|
||||
M 682.8789,715.34763
|
||||
682.857,337.12518
|
||||
372.03711,195.5332
|
||||
61.221001,337.25218
|
||||
61.220697,715.32812
|
||||
372.041,856.80518 Z"
|
||||
style="
|
||||
opacity:1;
|
||||
fill:#ffffff;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:3.34395361;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-dasharray:none;
|
||||
stroke-dashoffset:0;
|
||||
stroke-opacity:1" />
|
||||
</g>
|
||||
</mask>
|
||||
</defs>
|
||||
<g
|
||||
id="layer1">
|
||||
<g
|
||||
mask="url(#mask26461)"
|
||||
style="filter:url(#filter26457)" id="g24913" >
|
||||
<g
|
||||
transform="matrix(1.0361037,-0.47199221,1.0361037,0.47199221,-801.62397,128.09956)"
|
||||
style="fill:#000000;fill-opacity:1" >
|
||||
{elements_top}
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-1.0361028,0.47161497,0,1.2603136,884.86634,-558.55115)"
|
||||
style="fill:#000000;fill-opacity:1" >
|
||||
{elements_left}
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(1.0361028,0.47161497,0,1.2603136,-140.77133,-558.55239)"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" >
|
||||
{elements_right}
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
mask="url(#mask26461)"
|
||||
>
|
||||
<g
|
||||
transform="matrix(1.0361037,-0.47199221,1.0361037,0.47199221,-801.62397,128.09956)"
|
||||
style="fill:#000000;fill-opacity:1" >
|
||||
{elements_top}
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-1.0361028,0.47161497,0,1.2603136,884.86634,-558.55115)"
|
||||
style="fill:#000000;fill-opacity:1" >
|
||||
{elements_left}
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(1.0361028,0.47161497,0,1.2603136,-140.77133,-558.55239)"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" >
|
||||
{elements_right}
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
'''
|
||||
TEMPLATE_PIXEL = '''
|
||||
<path
|
||||
d="m {x},{y} {pixsize},0 0,{pixsize} -{pixsize},0 z"
|
||||
id="{id}"
|
||||
style="opacity:{opacity};fill:#{color}"
|
||||
/>
|
||||
'''
|
||||
import os
|
||||
import PIL.Image
|
||||
import random
|
||||
import sys
|
||||
|
||||
# These numbers are MAGIC, and only work because of how the template was made.
|
||||
SQUARE_WIDTH = 300
|
||||
START_X = 194.94924
|
||||
START_Y = 637.82417
|
||||
|
||||
def hexadecimal(i):
|
||||
i = hex(i)[2:]
|
||||
width = 2 - (len(i) % 2)
|
||||
if width == 2: width = 0
|
||||
i = ('0'*width) + i
|
||||
return i
|
||||
|
||||
def mirror(image, direction):
|
||||
new_image = image.copy()
|
||||
for y in range(image.size[1]):
|
||||
for x in range(image.size[0]):
|
||||
pixel = image.getpixel((x, y))
|
||||
if direction == 'horizontal':
|
||||
x = (image.size[0] - 1) - x
|
||||
elif direction == 'vertical':
|
||||
y = (image.size[1] - 1) - y
|
||||
new_image.putpixel((x, y), pixel)
|
||||
return new_image
|
||||
|
||||
|
||||
def vectorize(filenames):
|
||||
images = [PIL.Image.open(f) for f in filenames]
|
||||
if len(images) == 1:
|
||||
images = [images[0], images[0], images[0]]
|
||||
elif len(images) == 2:
|
||||
images = [images[0], images[1], images[1]]
|
||||
elif len(images) == 3:
|
||||
pass
|
||||
else:
|
||||
raise ValueError('Invalid number of images supplied')
|
||||
|
||||
images[0] = images[0].rotate(270)
|
||||
images[2] = mirror(images[2], 'horizontal')
|
||||
elements_total = []
|
||||
for (image_index, image) in enumerate(images):
|
||||
elements_local = []
|
||||
width = image.size[0]
|
||||
step = SQUARE_WIDTH / width
|
||||
pixsize = SQUARE_WIDTH / width
|
||||
for y in range(width):
|
||||
y_point = START_Y + (y * step)
|
||||
for x in range(width):
|
||||
x_point = START_X + (x * step)
|
||||
color = image.getpixel((x, y))
|
||||
|
||||
opacity = 1
|
||||
if isinstance(color, int):
|
||||
color = hexadecimal(color) * 3
|
||||
elif isinstance(color, tuple):
|
||||
if len(color) == 4:
|
||||
opacity = color[3] / 255
|
||||
if len(color) >= 3:
|
||||
color = ''.join(hexadecimal(channel) for channel in color[:3])
|
||||
|
||||
element = TEMPLATE_PIXEL.format(
|
||||
x=x_point,
|
||||
y=y_point,
|
||||
opacity=opacity,
|
||||
pixsize=pixsize,
|
||||
color=color,
|
||||
id='face_%d_%d' % (image_index, x + (y*width)))
|
||||
elements_local.append(element)
|
||||
elements_total.append(elements_local)
|
||||
|
||||
elements_total = [''.join(elements) for elements in elements_total]
|
||||
image = TEMPLATE_PRIMARY.format(elements_top=elements_total[0], elements_right=elements_total[1], elements_left=elements_total[2])
|
||||
image = image.strip()
|
||||
|
||||
basenames = [os.path.splitext(f)[0] for f in filenames]
|
||||
outputname = '+'.join(basenames) + '.svg'
|
||||
print(outputname)
|
||||
f = open(outputname, 'w')
|
||||
f.write(image)
|
||||
f.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
filenames = sys.argv[1:]
|
||||
vectorize(filenames)
|
|
@ -1,87 +1,107 @@
|
|||
'''
|
||||
DOCSTRING='''
|
||||
OpenDirDL
|
||||
downloads open directories
|
||||
|
||||
Usage:
|
||||
The basics:
|
||||
1. Create a database of the directory's files with
|
||||
> opendirdl digest http://website.com/directory/
|
||||
2. Enable and disable the files you are interested in with
|
||||
> opendirdl remove_pattern ".*"
|
||||
> opendirdl keep_pattern "Daft%20Punk"
|
||||
> opendirdl remove_pattern "folder\.jpg"
|
||||
Note the percent-encoded string.
|
||||
3. Download the enabled files with
|
||||
> opendirdl download database.db
|
||||
|
||||
DIGEST:
|
||||
Specifics:
|
||||
|
||||
digest:
|
||||
Recursively fetch directories and build a database of file URLs.
|
||||
|
||||
> opendirdl digest !clipboard <flags>
|
||||
> opendirdl digest http://website.com/directory/ <flags>
|
||||
> opendirdl digest !clipboard <flags>
|
||||
|
||||
flags:
|
||||
-f | --fullscan:
|
||||
When included, perform HEAD requests on all files, to know the size of the entire directory.
|
||||
When included, perform HEAD requests on all files, to know the size of
|
||||
the entire directory.
|
||||
|
||||
-db "x.db" | --databasename "x.db":
|
||||
Use a custom database filename. By default, databases are named after the web domain.
|
||||
Use a custom database filename. By default, databases are named after
|
||||
the web domain.
|
||||
|
||||
DOWNLOAD:
|
||||
Download the files whose URLs are enabled in the database.
|
||||
download:
|
||||
Download the files whose URLs are Enabled in the database.
|
||||
|
||||
> opendirdl download website.com.db <flags>
|
||||
|
||||
flags:
|
||||
-o "x" | --outputdir "x":
|
||||
Save the files to a custom directory, "x". By default, files are saved to a folder named
|
||||
after the web domain.
|
||||
Save the files to a custom directory, "x". By default, files are saved
|
||||
to a folder named after the web domain.
|
||||
|
||||
-ow | --overwrite:
|
||||
When included, download and overwrite files even if they already exist in the output directory.
|
||||
When included, download and overwrite files even if they already exist
|
||||
in the output directory.
|
||||
|
||||
-bps 100 | --bytespersecond 100:
|
||||
Ratelimit yourself to downloading at 100 BYTES per second. The webmaster will appreciate this.
|
||||
Ratelimit yourself to downloading at 100 BYTES per second.
|
||||
The webmaster will appreciate this.
|
||||
|
||||
KEEP_PATTERN:
|
||||
Enable URLs which match a regex pattern. Matches are based on the percent-encoded strings!
|
||||
keep_pattern:
|
||||
Enable URLs which match a regex pattern. Matches are based on the percent-
|
||||
encoded strings!
|
||||
|
||||
> opendirdl keep_pattern website.com.db ".*"
|
||||
> opendirdl keep_pattern database.db ".*"
|
||||
|
||||
REMOVE_PATTERN:
|
||||
Disable URLs which match a regex pattern. Matches are based on the percent-encoded strings!
|
||||
remove_pattern:
|
||||
Disable URLs which match a regex pattern. Matches are based on the percent-
|
||||
encoded strings!
|
||||
|
||||
> opendirdl remove_pattern website.com.db ".*"
|
||||
> opendirdl remove_pattern database.db ".*"
|
||||
|
||||
LIST_BASENAMES:
|
||||
List enabled URLs in order of their base filename. This makes it easier to find titles of
|
||||
interest in a directory that is very scattered or poorly organized.
|
||||
list_basenames:
|
||||
List enabled URLs in order of their base filename. This makes it easier to
|
||||
find titles of interest in a directory that is very scattered or poorly
|
||||
organized.
|
||||
|
||||
> opendirdl list_basenames website.com.db <flags>
|
||||
|
||||
flags:
|
||||
-o "x.txt" | --outputfile "x.txt":
|
||||
Output the results to a file instead of stdout. This is useful if the filenames contain
|
||||
special characters that crash Python, or are so long that the console becomes unreadable.
|
||||
Output the results to a file instead of stdout. This is useful if the
|
||||
filenames contain special characters that crash Python, or are so long
|
||||
that the console becomes unreadable.
|
||||
|
||||
MEASURE:
|
||||
Sum up the filesizes of all enabled URLs.
|
||||
measure:
|
||||
Sum up the filesizes of all Enabled URLs.
|
||||
|
||||
> opendirdl measure website.com.db <flags>
|
||||
> opendirdl measure database.db <flags>
|
||||
|
||||
flags:
|
||||
-f | --fullscan:
|
||||
When included, perform HEAD requests on any URL whose size is not known. If this flag is
|
||||
not included, and some file's size is unkown, you will receive a printed note.
|
||||
When included, perform HEAD requests when a file's size is not known.
|
||||
If this flag is not included, and some file's size is unkown, you will
|
||||
receive a printed note.
|
||||
'''
|
||||
|
||||
# Module names preceeded by two hashes indicate modules that are imported during
|
||||
|
||||
# Module names preceeded by `## ~` indicate modules that are imported during
|
||||
# a function, because they are not used anywhere else and we don't need to waste
|
||||
# time importing them usually.
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.append('C:\\git\\else\\ratelimiter'); import ratelimiter
|
||||
|
||||
import argparse
|
||||
## import bs4
|
||||
## import hashlib
|
||||
## ~import bs4
|
||||
## ~import hashlib
|
||||
import os
|
||||
## import re
|
||||
## ~import re
|
||||
import requests
|
||||
import shutil
|
||||
import sqlite3
|
||||
## tkinter
|
||||
## ~tkinter
|
||||
import urllib.parse
|
||||
|
||||
FILENAME_BADCHARS = '/\\:*?"<>|'
|
||||
|
@ -249,6 +269,7 @@ class Walker:
|
|||
databasename = databasename.replace(':', '')
|
||||
self.databasename = databasename
|
||||
|
||||
safeprint('Opening %s' % self.databasename)
|
||||
self.sql = sqlite3.connect(self.databasename)
|
||||
self.cur = self.sql.cursor()
|
||||
db_init(self.sql, self.cur)
|
||||
|
@ -269,7 +290,7 @@ class Walker:
|
|||
External links, index sort links, and desktop.ini are discarded.
|
||||
'''
|
||||
import bs4
|
||||
soup = bs4.BeautifulSoup(response.text)
|
||||
soup = bs4.BeautifulSoup(response.text, 'html.parser')
|
||||
elements = soup.findAll(tag)
|
||||
for element in elements:
|
||||
try:
|
||||
|
@ -615,10 +636,10 @@ def filter_pattern(databasename, regex, action='keep', *trash):
|
|||
|
||||
should_keep = (keep and contains)
|
||||
if keep and contains and not current_do_dl:
|
||||
safeprint('Keeping "%s"' % url)
|
||||
safeprint('Enabling "%s"' % url)
|
||||
cur.execute('UPDATE urls SET do_download = 1 WHERE url == ?', [url])
|
||||
if remove and contains and current_do_dl:
|
||||
safeprint('Removing "%s"' % url)
|
||||
safeprint('Disabling "%s"' % url)
|
||||
cur.execute('UPDATE urls SET do_download = 0 WHERE url == ?', [url])
|
||||
sql.commit()
|
||||
|
||||
|
@ -735,6 +756,9 @@ def remove_pattern(args):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if listget(sys.argv, 1, '').lower() in ('help', '-h', '--help'):
|
||||
print(DOCSTRING)
|
||||
quit()
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
|
@ -758,7 +782,7 @@ if __name__ == '__main__':
|
|||
|
||||
p_list_basenames = subparsers.add_parser('list_basenames')
|
||||
p_list_basenames.add_argument('databasename')
|
||||
p_list_basenames.add_argument('-o', '--outputfile', dest='outputfile', default=None)
|
||||
p_list_basenames.add_argument('outputfile', nargs='?', default=None)
|
||||
p_list_basenames.set_defaults(func=list_basenames)
|
||||
|
||||
p_measure = subparsers.add_parser('measure')
|
||||
|
|
4
RateMeter/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
RateMeter
|
||||
=========
|
||||
|
||||
Provides a `RateMeter` class to measure the speed of something. Create an instance with the appropriate `span`, and call `meter.digest(x)` where `x` is the number of units processed. Later, call `meter.report()` to receive the current speed information.
|
|
@ -8,14 +8,10 @@ class RateMeter:
|
|||
This class is used to calculate a rolling average of
|
||||
units per second over `span` seconds.
|
||||
|
||||
Minimum span is 1 second.
|
||||
|
||||
Set `span` to None to calculate unit/s over the lifetime of the object
|
||||
after the first digest, rather than over a span.
|
||||
This saves the effort of tracking timestamps. Don't just use a large number!
|
||||
'''
|
||||
if span is not None and span < 1:
|
||||
raise ValueError('Span must be >= 1')
|
||||
self.sum = 0
|
||||
self.span = span
|
||||
|
||||
|
@ -23,10 +19,9 @@ class RateMeter:
|
|||
self.first_digest = None
|
||||
|
||||
def digest(self, value):
|
||||
now = math.ceil(time.time())
|
||||
now = time.time()
|
||||
self.sum += value
|
||||
|
||||
|
||||
if self.span is None:
|
||||
if self.first_digest is None:
|
||||
self.first_digest = now
|
||||
|
|
|
@ -4,8 +4,8 @@ import ratemeter
|
|||
import requests
|
||||
import time
|
||||
|
||||
URL = 'http://cdn.speedof.me/sample32768k.bin?r=0.8817502672426312'
|
||||
METER = ratemeter.RateMeter(span=10)
|
||||
URL = 'http://cdn.speedof.me/sample32768k.bin?r=0.881750426312'
|
||||
METER = ratemeter.RateMeter(span=5)
|
||||
METER_2 = ratemeter.RateMeter(span=None)
|
||||
class G:
|
||||
pass
|
||||
|
@ -13,7 +13,7 @@ class G:
|
|||
g = G()
|
||||
g.total = 0
|
||||
g.start = None
|
||||
g.last = int(time.time())
|
||||
g.last = time.time()
|
||||
|
||||
def callback_progress(bytes_downloaded, bytes_total):
|
||||
if g.start is None:
|
||||
|
@ -25,7 +25,7 @@ def callback_progress(bytes_downloaded, bytes_total):
|
|||
METER.digest(chunk)
|
||||
METER_2.digest(chunk)
|
||||
now = round(time.time(), 1)
|
||||
if now > g.last:
|
||||
if now > g.last or (bytes_downloaded >= bytes_total):
|
||||
g.last = now
|
||||
percent = percent.rjust(9, ' ')
|
||||
rate = bytestring.bytestring(METER.report()[2]).rjust(15, ' ')
|
||||
|
@ -35,5 +35,5 @@ def callback_progress(bytes_downloaded, bytes_total):
|
|||
#print(METER.report(), METER_2.report())
|
||||
|
||||
print(URL)
|
||||
print('Progress'.rjust(9, ' '), 'bps over 10s'.rjust(15, ' '), 'bps overall'.rjust(15, ' '), 'elapsed'.rjust(10, ' '))
|
||||
print('Progress'.rjust(9, ' '), 'bps over 5s'.rjust(15, ' '), 'bps overall'.rjust(15, ' '), 'elapsed'.rjust(10, ' '))
|
||||
downloady.download_file(URL, 'nul', callback_progress=callback_progress)
|
6
Ratelimiter/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
Ratelimiter
|
||||
===========
|
||||
|
||||
Provides a `Ratelimiter` class to regulate timing. Create an instance with the appropriate allowance and timing rules, then just call `limiter.limit()` in your loop.
|
||||
|
||||
Note that allowance=10, period=10 is not the same as allowance=1, period=1. The first allows for more "burstiness" because all 10 operations can happen in the first second, as long as you wait for the other 9.
|
|
@ -2,10 +2,10 @@ import time
|
|||
|
||||
|
||||
class Ratelimiter:
|
||||
def __init__(self, allowance_per_period, period, operation_cost=1, mode='sleep'):
|
||||
def __init__(self, allowance_per_period, period=1, operation_cost=1, mode='sleep'):
|
||||
'''
|
||||
allowance_per_period:
|
||||
The number of operations we can perform per `period` seconds.
|
||||
Our spending balance per `period` seconds.
|
||||
|
||||
period:
|
||||
The number of seconds over which we can perform `allowance_per_period` operations.
|
||||
|
@ -15,12 +15,17 @@ class Ratelimiter:
|
|||
Pass a `cost` parameter to `self.limit` to use a nondefault value.
|
||||
|
||||
mode:
|
||||
'sleep': If we do not have the balance for an operation, sleep until we do.
|
||||
'sleep':
|
||||
If we do not have the balance for an operation, sleep until we do.
|
||||
Return True every time.
|
||||
'reject': If we do not have the balance for an operation, return False.
|
||||
|
||||
'reject':
|
||||
If we do not have the balance for an operation, return False.
|
||||
The cost is not subtracted, so hopefully we have enough next time.
|
||||
'''
|
||||
if mode not in ('sleep', 'reject'):
|
||||
raise ValueError('Invalid mode %s' % repr(mode))
|
||||
|
||||
self.allowance_per_period = allowance_per_period
|
||||
self.period = period
|
||||
self.operation_cost = operation_cost
|
||||
|
@ -28,29 +33,34 @@ class Ratelimiter:
|
|||
|
||||
self.last_operation = time.time()
|
||||
self.balance = 0
|
||||
self.gain_rate = allowance_per_period / period
|
||||
|
||||
@property
|
||||
def gain_rate(self):
|
||||
return self.allowance_per_period / self.period
|
||||
|
||||
def limit(self, cost=None):
|
||||
'''
|
||||
See the main class docstring for info about cost and mode behavior.
|
||||
'''
|
||||
if cost is None:
|
||||
cost = self.operation_cost
|
||||
timediff = time.time() - self.last_operation
|
||||
self.balance += timediff * self.gain_rate
|
||||
|
||||
time_diff = time.time() - self.last_operation
|
||||
self.balance += time_diff * self.gain_rate
|
||||
self.balance = min(self.balance, self.allowance_per_period)
|
||||
successful = False
|
||||
|
||||
deficit = cost - self.balance
|
||||
if deficit > 0 and self.mode == 'sleep':
|
||||
time_needed = (deficit / self.gain_rate)
|
||||
#print(self.balance, deficit, 'Need to sleep %f' % time_needed)
|
||||
time.sleep(time_needed)
|
||||
self.balance = cost
|
||||
|
||||
#print(self.balance)
|
||||
if self.balance >= cost:
|
||||
#print('pass')
|
||||
self.balance -= cost
|
||||
successful = True
|
||||
succesful = True
|
||||
else:
|
||||
if self.mode == 'reject':
|
||||
succesful = False
|
||||
else:
|
||||
deficit = cost - self.balance
|
||||
time_needed = deficit / self.gain_rate
|
||||
time.sleep(time_needed)
|
||||
self.balance = 0
|
||||
succesful = True
|
||||
|
||||
self.last_operation = time.time()
|
||||
|
||||
return successful
|
||||
return succesful
|
||||
|
|
270
SinWave/sinwave.py
Normal file
|
@ -0,0 +1,270 @@
|
|||
import math
|
||||
import random
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
import tkinter
|
||||
|
||||
# Nice patterns
|
||||
# 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345
|
||||
# 0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330
|
||||
# 0, 15, 30, 45, 60, 180, 195, 210, 225, 240
|
||||
# 0, 90, 180, 270
|
||||
# ░▒▓
|
||||
|
||||
SCREEN_WIDTH = 114
|
||||
|
||||
DEFAULT_LINE = {
|
||||
'character': '#',
|
||||
'degree': 0,
|
||||
'degree_step': 1,
|
||||
'horizontal_offset': 0,
|
||||
'line_thickness': 1,
|
||||
'original_degree': 0,
|
||||
'damping': 3,
|
||||
'depth': 0,
|
||||
}
|
||||
variables = {
|
||||
'clock': 0,
|
||||
'frames':[],
|
||||
'delay': 0.01,
|
||||
'lines':[
|
||||
]
|
||||
}
|
||||
|
||||
class LineControl(tkinter.Frame):
|
||||
def __init__(self, master, line, *args, **kwargs):
|
||||
tkinter.Frame.__init__(self, master, *args, **kwargs)
|
||||
|
||||
self.line = line
|
||||
for x in range(4):
|
||||
self.columnconfigure(x, minsize=40)
|
||||
row = 0
|
||||
tkinter.Button(self, text='\u21bb', command=self.refresh).grid(row=row, column=0, sticky='news')
|
||||
tkinter.Button(self, text='Clone', command=self.clone).grid(row=row, column=1, columnspan=2, sticky='news')
|
||||
tkinter.Button(self, text='X', command=self.suicide).grid(row=row, column=3, sticky='news')
|
||||
row += 1
|
||||
|
||||
tkinter.Label(self, text='Degree:').grid(row=row, column=0, sticky='e')
|
||||
self.entry_degree = tkinter.Entry(self, width=3)
|
||||
self.entry_degree.insert(0, self.line['original_degree'])
|
||||
self.entry_degree.grid(row=row, column=1, sticky='news')
|
||||
tkinter.Button(self, text='Set', command=self.apply_degree).grid(row=row, column=2, sticky='news')
|
||||
row += 1
|
||||
|
||||
def create_buttonpair(key, row):
|
||||
minus = tkinter.Button(self, text='-', command=lambda *a: self.step_variable(key, -1, self.value_labels[key]))
|
||||
minus.grid(row=row, column=1, sticky='news')
|
||||
plus = tkinter.Button(self, text='+', command=lambda *a: self.step_variable(key, +1, self.value_labels[key]))
|
||||
plus.grid(row=row, column=2, sticky='news')
|
||||
|
||||
self.value_labels = {}
|
||||
tkinter.Label(self, text='Step:').grid(row=row, column=0, sticky='e')
|
||||
create_buttonpair('degree_step', row)
|
||||
self.value_labels['degree_step'] = tkinter.Label(self, text=str(self.line['degree_step']))
|
||||
self.value_labels['degree_step'].grid(row=row, column=3)
|
||||
row += 1
|
||||
|
||||
tkinter.Label(self, text='Thick:').grid(row=row, column=0, sticky='e')
|
||||
create_buttonpair('line_thickness', row)
|
||||
self.value_labels['line_thickness'] = tkinter.Label(self, text=str(self.line['line_thickness']))
|
||||
self.value_labels['line_thickness'].grid(row=row, column=3)
|
||||
row += 1
|
||||
|
||||
tkinter.Label(self, text='Offset:').grid(row=row, column=0, sticky='e')
|
||||
create_buttonpair('horizontal_offset', row)
|
||||
self.value_labels['horizontal_offset'] = tkinter.Label(self, text=str(self.line['horizontal_offset']))
|
||||
self.value_labels['horizontal_offset'].grid(row=row, column=3)
|
||||
row += 1
|
||||
|
||||
tkinter.Label(self, text='Dampen:').grid(row=row, column=0, sticky='e')
|
||||
create_buttonpair('damping', row)
|
||||
self.value_labels['damping'] = tkinter.Label(self, text=str(self.line['damping']))
|
||||
self.value_labels['damping'].grid(row=row, column=3)
|
||||
row += 1
|
||||
|
||||
tkinter.Label(self, text='Depth:').grid(row=row, column=0, sticky='e')
|
||||
create_buttonpair('depth', row)
|
||||
self.value_labels['depth'] = tkinter.Label(self, text=str(self.line['depth']))
|
||||
self.value_labels['depth'].grid(row=row, column=3)
|
||||
row += 1
|
||||
|
||||
tkinter.Label(self, text='Char:').grid(row=row, column=0, sticky='e')
|
||||
self.entry_character = tkinter.Entry(self, width=2, font=('Consolas', 12))
|
||||
self.entry_character.grid(row=row, column=1, sticky='news')
|
||||
self.entry_character.insert(0, self.line['character'])
|
||||
tkinter.Button(self, text='Set', command=self.apply_character).grid(row=row, column=2, sticky='news')
|
||||
row += 1
|
||||
|
||||
def apply_character(self, *trash):
|
||||
char = self.entry_character.get()
|
||||
try:
|
||||
assert len(char) == 1
|
||||
except:
|
||||
return
|
||||
self.line['character'] = char
|
||||
|
||||
def apply_degree(self, *trash):
|
||||
value = self.entry_degree.get()
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
return
|
||||
self.line['original_degree'] = value % 360
|
||||
self.refresh()
|
||||
regrid_frames()
|
||||
|
||||
def clone(self, *trash):
|
||||
line = self.line.copy()
|
||||
create_line_frame(line=line)
|
||||
regrid_frames()
|
||||
|
||||
def refresh(self, *trash):
|
||||
self.line['degree'] = self.line['original_degree'] + (self.line['degree_step'] * variables['clock'])
|
||||
|
||||
def step_variable(self, key, direction, label):
|
||||
value = self.line[key]
|
||||
value += direction
|
||||
|
||||
if key == 'line_thickness':
|
||||
value = max(value, 0)
|
||||
elif key == 'horizontal_offset':
|
||||
value = value % SCREEN_WIDTH
|
||||
elif key == 'delay':
|
||||
value = max(value, 0.001)
|
||||
elif key in ('damping', 'depth'):
|
||||
value = max(value, 0)
|
||||
value = min(value, int(SCREEN_WIDTH / 2))
|
||||
|
||||
self.line[key] = value
|
||||
label.configure(text=str(value)[:5])
|
||||
|
||||
def suicide(self, *trash):
|
||||
unregister_line(self.line)
|
||||
variables['frames'].remove(self)
|
||||
self.grid_forget()
|
||||
regrid_frames()
|
||||
|
||||
def create_line(degree_offset, **kwargs):
|
||||
line = DEFAULT_LINE.copy()
|
||||
line.update(kwargs)
|
||||
line['original_degree'] = degree_offset
|
||||
line['degree'] = (variables['clock'] + degree_offset) % 360
|
||||
# Line ID exists solely to make sure that even clones are unique.
|
||||
line['id'] = random.getrandbits(32)
|
||||
return line
|
||||
|
||||
def create_line_frame(offset=None, line=None):
|
||||
if line is None:
|
||||
if offset is None:
|
||||
offset = entry_add.get()
|
||||
offset = offset.replace(' ', '')
|
||||
offset = offset.split(',')
|
||||
try:
|
||||
offset = [int(o) for o in offset]
|
||||
except ValueError:
|
||||
return
|
||||
entry_add.delete(0, 'end')
|
||||
|
||||
lines = []
|
||||
for x in offset:
|
||||
line = create_line(x)
|
||||
lines.append(line)
|
||||
else:
|
||||
line['id'] = random.getrandbits(32)
|
||||
lines = [line]
|
||||
|
||||
for line in lines:
|
||||
register_line(line)
|
||||
frame = LineControl(t, line, relief='groove', borderwidth=2)
|
||||
variables['frames'].append(frame)
|
||||
regrid_frames()
|
||||
|
||||
def map_range(x, old_low, old_high, new_low, new_high):
|
||||
'''
|
||||
Given a number x in range [old_low, old_high], return corresponding
|
||||
number in range [new_low, new_high].
|
||||
'''
|
||||
if x > old_high or x < old_low:
|
||||
raise ValueError('%d not in range [%d..%d]' % (x, old_low, old_high))
|
||||
percentage = (x - old_low) / (old_high - old_low)
|
||||
y = (percentage * (new_high - new_low)) + new_low
|
||||
return y
|
||||
|
||||
def position_from_degree(d, damping_units):
|
||||
rad = math.radians(d)
|
||||
sin = math.sin(rad)
|
||||
sin = map_range(sin, -1, 1, 0, 1)
|
||||
position = sin * (SCREEN_WIDTH - 1)
|
||||
position = map_range(position, 0, SCREEN_WIDTH, damping_units, SCREEN_WIDTH-damping_units)
|
||||
#position = (position * (1- (2 * damping_percent))) + damping_units
|
||||
position = round(position)
|
||||
position = position % SCREEN_WIDTH
|
||||
return position
|
||||
|
||||
def print_loop():
|
||||
while True:
|
||||
next_frame = time.time() + variables['delay']
|
||||
variables['clock'] = (variables['clock'] + 1) % 360
|
||||
|
||||
screen = ([' '] * SCREEN_WIDTH)
|
||||
z_indexes = {}
|
||||
|
||||
for line_vars in variables['lines']:
|
||||
start_degree = line_vars['degree']
|
||||
end_degree = (start_degree + line_vars['degree_step']) % 360
|
||||
line_vars['degree'] = end_degree
|
||||
|
||||
damping = line_vars['damping']
|
||||
start_pos = position_from_degree(start_degree, damping)
|
||||
end_pos = position_from_degree(end_degree, damping)
|
||||
(start_pos, end_pos) = sorted([start_pos, end_pos])
|
||||
|
||||
start_pos -= line_vars['line_thickness']
|
||||
end_pos += line_vars['line_thickness']
|
||||
|
||||
mini_step = line_vars['degree_step'] / max((end_pos - start_pos), 1)
|
||||
for (index, position) in enumerate(range(start_pos, end_pos+1)):
|
||||
position = (position + line_vars['horizontal_offset']) % SCREEN_WIDTH
|
||||
this_rads = math.radians(start_degree + (mini_step * index))
|
||||
z_index = math.cos(this_rads)
|
||||
z_index = map_range(z_index, -1, 1, line_vars['depth'], SCREEN_WIDTH-line_vars['depth'])
|
||||
#print(line_vars['character'], z_index)
|
||||
if z_index > z_indexes.get(position, -2):
|
||||
screen[position] = line_vars['character']
|
||||
z_indexes[position] = z_index
|
||||
|
||||
print('%03d' % variables['clock'], ''.join(screen))
|
||||
delay = max(next_frame - time.time(), 0)
|
||||
time.sleep(delay)
|
||||
|
||||
def register_line(line):
|
||||
variables['lines'].append(line)
|
||||
|
||||
def regrid_frames():
|
||||
frames = variables['frames']
|
||||
#frames.sort(key=lambda x: x.line['original_degree'])
|
||||
for (index, frame) in enumerate(frames):
|
||||
(row, column) = divmod(index, 4)
|
||||
row += 1
|
||||
frame.grid(row=row, column=column)
|
||||
|
||||
def unregister_line(line):
|
||||
variables['lines'].remove(line)
|
||||
|
||||
t = tkinter.Tk()
|
||||
|
||||
frame_add = tkinter.Frame(t)
|
||||
entry_add = tkinter.Entry(frame_add)
|
||||
entry_add.grid(row=0, column=0)
|
||||
tkinter.Button(frame_add, text='+', command=create_line_frame).grid(row=0, column=1)
|
||||
frame_add.grid(row=0, column=0)
|
||||
|
||||
frame_delay = tkinter.Frame(t)
|
||||
tkinter.Label(frame_delay, text='Speed:')
|
||||
thread = threading.Thread(target=print_loop)
|
||||
thread.daemon=True
|
||||
thread.start()
|
||||
|
||||
create_line_frame([0])
|
||||
t.mainloop()
|
|
@ -1,7 +1,16 @@
|
|||
Steganographic
|
||||
==============
|
||||
|
||||
2015 01 15:
|
||||
2016 05 12:
|
||||
Now pads the remaining pixels with random data. Previously, the fact that the secret
|
||||
file resided in only as many pixels as it needed meant there was a sudden decrease in
|
||||
visual noise when the secret ended. Now there is noise throughout.
|
||||
Your secret file should resemble random noise for this to be effective. Thus
|
||||
encryption is recommended.
|
||||
This does not change decoding, because the decoder only reads as many bytes as
|
||||
needed.
|
||||
|
||||
2016 01 15:
|
||||
Now supports variable "bitness", the number of bits per color channel to overwrite.
|
||||
Previously, bitness was always 1, to maximize transparency.
|
||||
Now, bitness can be 1-8 to favor transparency or information density.
|
||||
|
|
|
@ -178,10 +178,6 @@ def set_bit(number, index, newvalue):
|
|||
#### ## #### #### #### #### #### #### #### #### #### ##
|
||||
############## #### #### ######## ###### ########## ##############
|
||||
def encode(imagefilename, secretfilename, bitness=1):
|
||||
global image
|
||||
global pixel
|
||||
global pixel_index
|
||||
global channel_index
|
||||
pixel_index = 0
|
||||
channel_index = 0
|
||||
|
||||
|
@ -198,18 +194,18 @@ def encode(imagefilename, secretfilename, bitness=1):
|
|||
image = Image.open(imagefilename)
|
||||
image_steg = BitsToImage(image, bitness)
|
||||
|
||||
totalpixels = image.size[0] * image.size[1]
|
||||
if totalpixels < HEADER_SIZE:
|
||||
total_pixels = image.size[0] * image.size[1]
|
||||
if total_pixels < HEADER_SIZE:
|
||||
raise StegError('Image cannot have fewer than %d pixels. They are used to store Secret\'s length' % HEADER_SIZE)
|
||||
|
||||
secret_extension = os.path.splitext(secretfilename)[1][1:]
|
||||
secret_content_length = (secret_size) + (len(secret_extension)) + 1
|
||||
requiredpixels = math.ceil(((secret_content_length * 8) + 32) / (3 * bitness))
|
||||
if totalpixels < requiredpixels:
|
||||
if total_pixels < requiredpixels:
|
||||
raise StegError('Image does not have enough pixels to store the Secret. '
|
||||
'Must have at least %d pixels' % requiredpixels)
|
||||
|
||||
print('%d pixels available, %d required' % (totalpixels, requiredpixels))
|
||||
print('%d pixels available, %d required' % (total_pixels, requiredpixels))
|
||||
|
||||
# --> YOU ARE HERE <--
|
||||
|
||||
|
@ -226,23 +222,40 @@ def encode(imagefilename, secretfilename, bitness=1):
|
|||
|
||||
# Write the secret data
|
||||
bytes_written = 0
|
||||
done = False
|
||||
secretfile = open(secretfilename, 'rb')
|
||||
while not done:
|
||||
if bytes_written % 1024 == 0:
|
||||
percentage = (bytes_written + 1) / secret_size
|
||||
percentage = '%07.3f%%\r' % (100 * percentage)
|
||||
print(percentage, end='')
|
||||
one_complete_file = False
|
||||
|
||||
bytes = secretfile.read(FILE_READ_SIZE)
|
||||
# Yes, we're assuming the whole file fits in memory.
|
||||
secret_file = open(secretfilename, 'rb')
|
||||
secret_data = secret_file.read()
|
||||
secret_data_size = len(secret_data)
|
||||
secret_index = 0
|
||||
for pixel_number in range(total_pixels):
|
||||
if pixel_number % 4 == 0:
|
||||
#percentage = (bytes_written + 1) / secret_size
|
||||
#percentage = '%07.3f%%\r' % (100 * percentage)
|
||||
#print(percentage, end='')
|
||||
print(pixel_number)
|
||||
|
||||
done = len(bytes) == 0
|
||||
secret_chunk = secret_data[secret_index:secret_index+FILE_READ_SIZE]
|
||||
secret_index += FILE_READ_SIZE
|
||||
if len(secret_chunk) < FILE_READ_SIZE:
|
||||
one_complete_file = True
|
||||
#secret_index = 0
|
||||
rs = FILE_READ_SIZE - len(secret_chunk)
|
||||
secret_chunk += os.urandom(rs)
|
||||
#secret_chunk += secret_data[secret_index:secret_index+rs]
|
||||
|
||||
bytes = list(bytes)
|
||||
bytes = [binary(byte) for byte in bytes]
|
||||
bytes_written += len(bytes)
|
||||
bytes = ''.join(bytes)
|
||||
image_steg.write(bytes)
|
||||
secret_chunk = list(secret_chunk)
|
||||
secret_chunk = [binary(byte) for byte in secret_chunk]
|
||||
bytes_written += len(secret_chunk)
|
||||
secret_chunk = ''.join(secret_chunk)
|
||||
try:
|
||||
image_steg.write(secret_chunk)
|
||||
except IndexError:
|
||||
if one_complete_file:
|
||||
break
|
||||
else:
|
||||
raise
|
||||
|
||||
# haha
|
||||
print('100.000%')
|
||||
|
@ -271,7 +284,6 @@ def encode(imagefilename, secretfilename, bitness=1):
|
|||
#### #### #### ## #### #### #### #### #### #### #### ##
|
||||
########## ############## ######## ###### ########## ##############
|
||||
def decode(imagefilename, bitness=1):
|
||||
|
||||
print('Extracting content from "%s"' % imagefilename)
|
||||
image = Image.open(imagefilename)
|
||||
image_steg = ImageToBits(image, bitness)
|
||||
|
@ -331,16 +343,16 @@ if __name__ == '__main__':
|
|||
command = listget(sys.argv, 1, '').lower()
|
||||
if command not in ['encode', 'decode']:
|
||||
print('Usage:')
|
||||
print('> steganographic.py encode imagefilename.png secretfilename.ext')
|
||||
print('> steganographic.py decode lacedimagename.png')
|
||||
print('> steganographic.py encode imagefilename.png secretfilename.ext bitness')
|
||||
print('> steganographic.py decode lacedimagename.png bitness')
|
||||
quit()
|
||||
|
||||
imagefilename = sys.argv[2]
|
||||
|
||||
if command == 'encode':
|
||||
secretfilename = sys.argv[3]
|
||||
bitness = int(listget(sys.argv, 4, 1))
|
||||
bitness = int(sys.argv[4])
|
||||
encode(imagefilename, secretfilename, bitness)
|
||||
else:
|
||||
bitness = int(listget(sys.argv, 3, 1))
|
||||
bitness = int(sys.argv[3])
|
||||
decode(imagefilename, bitness)
|