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
|
OpenDirDL
|
||||||
downloads open directories
|
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.
|
Recursively fetch directories and build a database of file URLs.
|
||||||
|
|
||||||
> opendirdl digest !clipboard <flags>
|
|
||||||
> opendirdl digest http://website.com/directory/ <flags>
|
> opendirdl digest http://website.com/directory/ <flags>
|
||||||
|
> opendirdl digest !clipboard <flags>
|
||||||
|
|
||||||
flags:
|
flags:
|
||||||
-f | --fullscan:
|
-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":
|
-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:
|
||||||
Download the files whose URLs are enabled in the database.
|
Download the files whose URLs are Enabled in the database.
|
||||||
|
|
||||||
> opendirdl download website.com.db <flags>
|
> opendirdl download website.com.db <flags>
|
||||||
|
|
||||||
flags:
|
flags:
|
||||||
-o "x" | --outputdir "x":
|
-o "x" | --outputdir "x":
|
||||||
Save the files to a custom directory, "x". By default, files are saved to a folder named
|
Save the files to a custom directory, "x". By default, files are saved
|
||||||
after the web domain.
|
to a folder named after the web domain.
|
||||||
|
|
||||||
-ow | --overwrite:
|
-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:
|
-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:
|
keep_pattern:
|
||||||
Enable URLs which match a regex pattern. Matches are based on the percent-encoded strings!
|
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:
|
remove_pattern:
|
||||||
Disable URLs which match a regex pattern. Matches are based on the percent-encoded strings!
|
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_basenames:
|
||||||
List enabled URLs in order of their base filename. This makes it easier to find titles of
|
List enabled URLs in order of their base filename. This makes it easier to
|
||||||
interest in a directory that is very scattered or poorly organized.
|
find titles of interest in a directory that is very scattered or poorly
|
||||||
|
organized.
|
||||||
|
|
||||||
> opendirdl list_basenames website.com.db <flags>
|
> opendirdl list_basenames website.com.db <flags>
|
||||||
|
|
||||||
flags:
|
flags:
|
||||||
-o "x.txt" | --outputfile "x.txt":
|
-o "x.txt" | --outputfile "x.txt":
|
||||||
Output the results to a file instead of stdout. This is useful if the filenames contain
|
Output the results to a file instead of stdout. This is useful if the
|
||||||
special characters that crash Python, or are so long that the console becomes unreadable.
|
filenames contain special characters that crash Python, or are so long
|
||||||
|
that the console becomes unreadable.
|
||||||
|
|
||||||
MEASURE:
|
measure:
|
||||||
Sum up the filesizes of all enabled URLs.
|
Sum up the filesizes of all Enabled URLs.
|
||||||
|
|
||||||
> opendirdl measure website.com.db <flags>
|
> opendirdl measure database.db <flags>
|
||||||
|
|
||||||
flags:
|
flags:
|
||||||
-f | --fullscan:
|
-f | --fullscan:
|
||||||
When included, perform HEAD requests on any URL whose size is not known. If this flag is
|
When included, perform HEAD requests when a file's size is not known.
|
||||||
not included, and some file's size is unkown, you will receive a printed note.
|
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
|
# a function, because they are not used anywhere else and we don't need to waste
|
||||||
# time importing them usually.
|
# time importing them usually.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.append('C:\\git\\else\\ratelimiter'); import ratelimiter
|
sys.path.append('C:\\git\\else\\ratelimiter'); import ratelimiter
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
## import bs4
|
## ~import bs4
|
||||||
## import hashlib
|
## ~import hashlib
|
||||||
import os
|
import os
|
||||||
## import re
|
## ~import re
|
||||||
import requests
|
import requests
|
||||||
import shutil
|
import shutil
|
||||||
import sqlite3
|
import sqlite3
|
||||||
## tkinter
|
## ~tkinter
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
FILENAME_BADCHARS = '/\\:*?"<>|'
|
FILENAME_BADCHARS = '/\\:*?"<>|'
|
||||||
|
@ -249,6 +269,7 @@ class Walker:
|
||||||
databasename = databasename.replace(':', '')
|
databasename = databasename.replace(':', '')
|
||||||
self.databasename = databasename
|
self.databasename = databasename
|
||||||
|
|
||||||
|
safeprint('Opening %s' % self.databasename)
|
||||||
self.sql = sqlite3.connect(self.databasename)
|
self.sql = sqlite3.connect(self.databasename)
|
||||||
self.cur = self.sql.cursor()
|
self.cur = self.sql.cursor()
|
||||||
db_init(self.sql, self.cur)
|
db_init(self.sql, self.cur)
|
||||||
|
@ -269,7 +290,7 @@ class Walker:
|
||||||
External links, index sort links, and desktop.ini are discarded.
|
External links, index sort links, and desktop.ini are discarded.
|
||||||
'''
|
'''
|
||||||
import bs4
|
import bs4
|
||||||
soup = bs4.BeautifulSoup(response.text)
|
soup = bs4.BeautifulSoup(response.text, 'html.parser')
|
||||||
elements = soup.findAll(tag)
|
elements = soup.findAll(tag)
|
||||||
for element in elements:
|
for element in elements:
|
||||||
try:
|
try:
|
||||||
|
@ -615,10 +636,10 @@ def filter_pattern(databasename, regex, action='keep', *trash):
|
||||||
|
|
||||||
should_keep = (keep and contains)
|
should_keep = (keep and contains)
|
||||||
if keep and contains and not current_do_dl:
|
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])
|
cur.execute('UPDATE urls SET do_download = 1 WHERE url == ?', [url])
|
||||||
if remove and contains and current_do_dl:
|
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])
|
cur.execute('UPDATE urls SET do_download = 0 WHERE url == ?', [url])
|
||||||
sql.commit()
|
sql.commit()
|
||||||
|
|
||||||
|
@ -735,6 +756,9 @@ def remove_pattern(args):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
if listget(sys.argv, 1, '').lower() in ('help', '-h', '--help'):
|
||||||
|
print(DOCSTRING)
|
||||||
|
quit()
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers()
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
|
@ -758,7 +782,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
p_list_basenames = subparsers.add_parser('list_basenames')
|
p_list_basenames = subparsers.add_parser('list_basenames')
|
||||||
p_list_basenames.add_argument('databasename')
|
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_list_basenames.set_defaults(func=list_basenames)
|
||||||
|
|
||||||
p_measure = subparsers.add_parser('measure')
|
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
|
This class is used to calculate a rolling average of
|
||||||
units per second over `span` seconds.
|
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
|
Set `span` to None to calculate unit/s over the lifetime of the object
|
||||||
after the first digest, rather than over a span.
|
after the first digest, rather than over a span.
|
||||||
This saves the effort of tracking timestamps. Don't just use a large number!
|
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.sum = 0
|
||||||
self.span = span
|
self.span = span
|
||||||
|
|
||||||
|
@ -23,10 +19,9 @@ class RateMeter:
|
||||||
self.first_digest = None
|
self.first_digest = None
|
||||||
|
|
||||||
def digest(self, value):
|
def digest(self, value):
|
||||||
now = math.ceil(time.time())
|
now = time.time()
|
||||||
self.sum += value
|
self.sum += value
|
||||||
|
|
||||||
|
|
||||||
if self.span is None:
|
if self.span is None:
|
||||||
if self.first_digest is None:
|
if self.first_digest is None:
|
||||||
self.first_digest = now
|
self.first_digest = now
|
||||||
|
|
|
@ -4,8 +4,8 @@ import ratemeter
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
|
|
||||||
URL = 'http://cdn.speedof.me/sample32768k.bin?r=0.8817502672426312'
|
URL = 'http://cdn.speedof.me/sample32768k.bin?r=0.881750426312'
|
||||||
METER = ratemeter.RateMeter(span=10)
|
METER = ratemeter.RateMeter(span=5)
|
||||||
METER_2 = ratemeter.RateMeter(span=None)
|
METER_2 = ratemeter.RateMeter(span=None)
|
||||||
class G:
|
class G:
|
||||||
pass
|
pass
|
||||||
|
@ -13,7 +13,7 @@ class G:
|
||||||
g = G()
|
g = G()
|
||||||
g.total = 0
|
g.total = 0
|
||||||
g.start = None
|
g.start = None
|
||||||
g.last = int(time.time())
|
g.last = time.time()
|
||||||
|
|
||||||
def callback_progress(bytes_downloaded, bytes_total):
|
def callback_progress(bytes_downloaded, bytes_total):
|
||||||
if g.start is None:
|
if g.start is None:
|
||||||
|
@ -25,7 +25,7 @@ def callback_progress(bytes_downloaded, bytes_total):
|
||||||
METER.digest(chunk)
|
METER.digest(chunk)
|
||||||
METER_2.digest(chunk)
|
METER_2.digest(chunk)
|
||||||
now = round(time.time(), 1)
|
now = round(time.time(), 1)
|
||||||
if now > g.last:
|
if now > g.last or (bytes_downloaded >= bytes_total):
|
||||||
g.last = now
|
g.last = now
|
||||||
percent = percent.rjust(9, ' ')
|
percent = percent.rjust(9, ' ')
|
||||||
rate = bytestring.bytestring(METER.report()[2]).rjust(15, ' ')
|
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(METER.report(), METER_2.report())
|
||||||
|
|
||||||
print(URL)
|
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)
|
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:
|
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:
|
allowance_per_period:
|
||||||
The number of operations we can perform per `period` seconds.
|
Our spending balance per `period` seconds.
|
||||||
|
|
||||||
period:
|
period:
|
||||||
The number of seconds over which we can perform `allowance_per_period` operations.
|
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.
|
Pass a `cost` parameter to `self.limit` to use a nondefault value.
|
||||||
|
|
||||||
mode:
|
mode:
|
||||||
'sleep': If we do not have the balance for an operation, sleep until we do.
|
'sleep':
|
||||||
Return True every time.
|
If we do not have the balance for an operation, sleep until we do.
|
||||||
'reject': If we do not have the balance for an operation, return False.
|
Return True every time.
|
||||||
|
|
||||||
|
'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'):
|
if mode not in ('sleep', 'reject'):
|
||||||
raise ValueError('Invalid mode %s' % repr(mode))
|
raise ValueError('Invalid mode %s' % repr(mode))
|
||||||
|
|
||||||
self.allowance_per_period = allowance_per_period
|
self.allowance_per_period = allowance_per_period
|
||||||
self.period = period
|
self.period = period
|
||||||
self.operation_cost = operation_cost
|
self.operation_cost = operation_cost
|
||||||
|
@ -28,29 +33,34 @@ class Ratelimiter:
|
||||||
|
|
||||||
self.last_operation = time.time()
|
self.last_operation = time.time()
|
||||||
self.balance = 0
|
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):
|
def limit(self, cost=None):
|
||||||
|
'''
|
||||||
|
See the main class docstring for info about cost and mode behavior.
|
||||||
|
'''
|
||||||
if cost is None:
|
if cost is None:
|
||||||
cost = self.operation_cost
|
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)
|
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:
|
if self.balance >= cost:
|
||||||
#print('pass')
|
|
||||||
self.balance -= cost
|
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()
|
self.last_operation = time.time()
|
||||||
|
return succesful
|
||||||
return successful
|
|
||||||
|
|
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
|
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.
|
Now supports variable "bitness", the number of bits per color channel to overwrite.
|
||||||
Previously, bitness was always 1, to maximize transparency.
|
Previously, bitness was always 1, to maximize transparency.
|
||||||
Now, bitness can be 1-8 to favor transparency or information density.
|
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):
|
def encode(imagefilename, secretfilename, bitness=1):
|
||||||
global image
|
|
||||||
global pixel
|
|
||||||
global pixel_index
|
|
||||||
global channel_index
|
|
||||||
pixel_index = 0
|
pixel_index = 0
|
||||||
channel_index = 0
|
channel_index = 0
|
||||||
|
|
||||||
|
@ -198,18 +194,18 @@ def encode(imagefilename, secretfilename, bitness=1):
|
||||||
image = Image.open(imagefilename)
|
image = Image.open(imagefilename)
|
||||||
image_steg = BitsToImage(image, bitness)
|
image_steg = BitsToImage(image, bitness)
|
||||||
|
|
||||||
totalpixels = image.size[0] * image.size[1]
|
total_pixels = image.size[0] * image.size[1]
|
||||||
if totalpixels < HEADER_SIZE:
|
if total_pixels < HEADER_SIZE:
|
||||||
raise StegError('Image cannot have fewer than %d pixels. They are used to store Secret\'s length' % 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_extension = os.path.splitext(secretfilename)[1][1:]
|
||||||
secret_content_length = (secret_size) + (len(secret_extension)) + 1
|
secret_content_length = (secret_size) + (len(secret_extension)) + 1
|
||||||
requiredpixels = math.ceil(((secret_content_length * 8) + 32) / (3 * bitness))
|
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. '
|
raise StegError('Image does not have enough pixels to store the Secret. '
|
||||||
'Must have at least %d pixels' % requiredpixels)
|
'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 <--
|
# --> YOU ARE HERE <--
|
||||||
|
|
||||||
|
@ -226,23 +222,40 @@ def encode(imagefilename, secretfilename, bitness=1):
|
||||||
|
|
||||||
# Write the secret data
|
# Write the secret data
|
||||||
bytes_written = 0
|
bytes_written = 0
|
||||||
done = False
|
one_complete_file = 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='')
|
|
||||||
|
|
||||||
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)
|
secret_chunk = list(secret_chunk)
|
||||||
bytes = [binary(byte) for byte in bytes]
|
secret_chunk = [binary(byte) for byte in secret_chunk]
|
||||||
bytes_written += len(bytes)
|
bytes_written += len(secret_chunk)
|
||||||
bytes = ''.join(bytes)
|
secret_chunk = ''.join(secret_chunk)
|
||||||
image_steg.write(bytes)
|
try:
|
||||||
|
image_steg.write(secret_chunk)
|
||||||
|
except IndexError:
|
||||||
|
if one_complete_file:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# haha
|
# haha
|
||||||
print('100.000%')
|
print('100.000%')
|
||||||
|
@ -271,7 +284,6 @@ def encode(imagefilename, secretfilename, bitness=1):
|
||||||
#### #### #### ## #### #### #### #### #### #### #### ##
|
#### #### #### ## #### #### #### #### #### #### #### ##
|
||||||
########## ############## ######## ###### ########## ##############
|
########## ############## ######## ###### ########## ##############
|
||||||
def decode(imagefilename, bitness=1):
|
def decode(imagefilename, bitness=1):
|
||||||
|
|
||||||
print('Extracting content from "%s"' % imagefilename)
|
print('Extracting content from "%s"' % imagefilename)
|
||||||
image = Image.open(imagefilename)
|
image = Image.open(imagefilename)
|
||||||
image_steg = ImageToBits(image, bitness)
|
image_steg = ImageToBits(image, bitness)
|
||||||
|
@ -331,16 +343,16 @@ if __name__ == '__main__':
|
||||||
command = listget(sys.argv, 1, '').lower()
|
command = listget(sys.argv, 1, '').lower()
|
||||||
if command not in ['encode', 'decode']:
|
if command not in ['encode', 'decode']:
|
||||||
print('Usage:')
|
print('Usage:')
|
||||||
print('> steganographic.py encode imagefilename.png secretfilename.ext')
|
print('> steganographic.py encode imagefilename.png secretfilename.ext bitness')
|
||||||
print('> steganographic.py decode lacedimagename.png')
|
print('> steganographic.py decode lacedimagename.png bitness')
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
imagefilename = sys.argv[2]
|
imagefilename = sys.argv[2]
|
||||||
|
|
||||||
if command == 'encode':
|
if command == 'encode':
|
||||||
secretfilename = sys.argv[3]
|
secretfilename = sys.argv[3]
|
||||||
bitness = int(listget(sys.argv, 4, 1))
|
bitness = int(sys.argv[4])
|
||||||
encode(imagefilename, secretfilename, bitness)
|
encode(imagefilename, secretfilename, bitness)
|
||||||
else:
|
else:
|
||||||
bitness = int(listget(sys.argv, 3, 1))
|
bitness = int(sys.argv[3])
|
||||||
decode(imagefilename, bitness)
|
decode(imagefilename, bitness)
|