diff --git a/ImageFilters/ear.jpg b/ImageFilters/ear.jpg
new file mode 100644
index 0000000..3ee58ee
Binary files /dev/null and b/ImageFilters/ear.jpg differ
diff --git a/ImageFilters/ear.png b/ImageFilters/ear.png
new file mode 100644
index 0000000..e5aab4e
Binary files /dev/null and b/ImageFilters/ear.png differ
diff --git a/ImageFilters/imagefilters.py b/ImageFilters/imagefilters.py
new file mode 100644
index 0000000..3b8e093
--- /dev/null
+++ b/ImageFilters/imagefilters.py
@@ -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')
\ No newline at end of file
diff --git a/Minecraft3DVector/README.md b/Minecraft3DVector/README.md
new file mode 100644
index 0000000..363f2c2
--- /dev/null
+++ b/Minecraft3DVector/README.md
@@ -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.
\ No newline at end of file
diff --git a/Minecraft3DVector/examples/rendering_furnace_2048.png b/Minecraft3DVector/examples/rendering_furnace_2048.png
new file mode 100644
index 0000000..eddcb7c
Binary files /dev/null and b/Minecraft3DVector/examples/rendering_furnace_2048.png differ
diff --git a/Minecraft3DVector/examples/rendering_hellsand_1024.png b/Minecraft3DVector/examples/rendering_hellsand_1024.png
new file mode 100644
index 0000000..003973d
Binary files /dev/null and b/Minecraft3DVector/examples/rendering_hellsand_1024.png differ
diff --git a/Minecraft3DVector/examples/rendering_minecraft_2048.png b/Minecraft3DVector/examples/rendering_minecraft_2048.png
new file mode 100644
index 0000000..a95c29c
Binary files /dev/null and b/Minecraft3DVector/examples/rendering_minecraft_2048.png differ
diff --git a/Minecraft3DVector/examples/rendering_obsidian_1024.png b/Minecraft3DVector/examples/rendering_obsidian_1024.png
new file mode 100644
index 0000000..7b2ac8d
Binary files /dev/null and b/Minecraft3DVector/examples/rendering_obsidian_1024.png differ
diff --git a/Minecraft3DVector/examples/rendering_tree_1024.png b/Minecraft3DVector/examples/rendering_tree_1024.png
new file mode 100644
index 0000000..f39914f
Binary files /dev/null and b/Minecraft3DVector/examples/rendering_tree_1024.png differ
diff --git a/Minecraft3DVector/examples/source_furnace_left.png b/Minecraft3DVector/examples/source_furnace_left.png
new file mode 100644
index 0000000..92c89f3
Binary files /dev/null and b/Minecraft3DVector/examples/source_furnace_left.png differ
diff --git a/Minecraft3DVector/examples/source_furnace_right.png b/Minecraft3DVector/examples/source_furnace_right.png
new file mode 100644
index 0000000..115f73d
Binary files /dev/null and b/Minecraft3DVector/examples/source_furnace_right.png differ
diff --git a/Minecraft3DVector/examples/source_furnace_top.png b/Minecraft3DVector/examples/source_furnace_top.png
new file mode 100644
index 0000000..a3a5a08
Binary files /dev/null and b/Minecraft3DVector/examples/source_furnace_top.png differ
diff --git a/Minecraft3DVector/examples/source_hellsand.png b/Minecraft3DVector/examples/source_hellsand.png
new file mode 100644
index 0000000..fca7e8f
Binary files /dev/null and b/Minecraft3DVector/examples/source_hellsand.png differ
diff --git a/Minecraft3DVector/examples/source_minecraft_left.png b/Minecraft3DVector/examples/source_minecraft_left.png
new file mode 100644
index 0000000..f703584
Binary files /dev/null and b/Minecraft3DVector/examples/source_minecraft_left.png differ
diff --git a/Minecraft3DVector/examples/source_minecraft_right.png b/Minecraft3DVector/examples/source_minecraft_right.png
new file mode 100644
index 0000000..5456138
Binary files /dev/null and b/Minecraft3DVector/examples/source_minecraft_right.png differ
diff --git a/Minecraft3DVector/examples/source_minecraft_top.png b/Minecraft3DVector/examples/source_minecraft_top.png
new file mode 100644
index 0000000..245e68b
Binary files /dev/null and b/Minecraft3DVector/examples/source_minecraft_top.png differ
diff --git a/Minecraft3DVector/examples/source_obsidian.png b/Minecraft3DVector/examples/source_obsidian.png
new file mode 100644
index 0000000..ff0a683
Binary files /dev/null and b/Minecraft3DVector/examples/source_obsidian.png differ
diff --git a/Minecraft3DVector/examples/source_tree_side.png b/Minecraft3DVector/examples/source_tree_side.png
new file mode 100644
index 0000000..914cb5f
Binary files /dev/null and b/Minecraft3DVector/examples/source_tree_side.png differ
diff --git a/Minecraft3DVector/examples/source_tree_top.png b/Minecraft3DVector/examples/source_tree_top.png
new file mode 100644
index 0000000..7a44e77
Binary files /dev/null and b/Minecraft3DVector/examples/source_tree_top.png differ
diff --git a/Minecraft3DVector/examples/svg_furnace.svg b/Minecraft3DVector/examples/svg_furnace.svg
new file mode 100644
index 0000000..ad213f5
--- /dev/null
+++ b/Minecraft3DVector/examples/svg_furnace.svg
@@ -0,0 +1,9319 @@
+
+
\ No newline at end of file
diff --git a/Minecraft3DVector/examples/svg_hellsand.svg b/Minecraft3DVector/examples/svg_hellsand.svg
new file mode 100644
index 0000000..855e6e8
--- /dev/null
+++ b/Minecraft3DVector/examples/svg_hellsand.svg
@@ -0,0 +1,9319 @@
+
+
\ No newline at end of file
diff --git a/Minecraft3DVector/examples/svg_minecraft.svg b/Minecraft3DVector/examples/svg_minecraft.svg
new file mode 100644
index 0000000..11dde3a
--- /dev/null
+++ b/Minecraft3DVector/examples/svg_minecraft.svg
@@ -0,0 +1,9319 @@
+
+
\ No newline at end of file
diff --git a/Minecraft3DVector/examples/svg_obsidian.svg b/Minecraft3DVector/examples/svg_obsidian.svg
new file mode 100644
index 0000000..cf8090e
--- /dev/null
+++ b/Minecraft3DVector/examples/svg_obsidian.svg
@@ -0,0 +1,9319 @@
+
+
\ No newline at end of file
diff --git a/Minecraft3DVector/examples/svg_tree.svg b/Minecraft3DVector/examples/svg_tree.svg
new file mode 100644
index 0000000..101f1a6
--- /dev/null
+++ b/Minecraft3DVector/examples/svg_tree.svg
@@ -0,0 +1,9319 @@
+
+
\ No newline at end of file
diff --git a/Minecraft3DVector/mc3dvector.py b/Minecraft3DVector/mc3dvector.py
new file mode 100644
index 0000000..0cfaf4c
--- /dev/null
+++ b/Minecraft3DVector/mc3dvector.py
@@ -0,0 +1,200 @@
+TEMPLATE_PRIMARY = '''
+
+
+'''
+TEMPLATE_PIXEL = '''
+
+'''
+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)
\ No newline at end of file
diff --git a/OpenDirDL/opendirdl.py b/OpenDirDL/opendirdl.py
index 458a3bc..9078dca 100644
--- a/OpenDirDL/opendirdl.py
+++ b/OpenDirDL/opendirdl.py
@@ -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
> opendirdl digest http://website.com/directory/
+ > opendirdl digest !clipboard
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:
-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:
-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
+ > opendirdl measure database.db
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')
diff --git a/RateMeter/README.md b/RateMeter/README.md
new file mode 100644
index 0000000..37269f5
--- /dev/null
+++ b/RateMeter/README.md
@@ -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.
\ No newline at end of file
diff --git a/RateMeter/ratemeter.py b/RateMeter/ratemeter.py
index 4abbb25..4b4b58d 100644
--- a/RateMeter/ratemeter.py
+++ b/RateMeter/ratemeter.py
@@ -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
diff --git a/RateMeter/speedtest.py b/RateMeter/speedtest.py
index 3cdf0eb..8a2d923 100644
--- a/RateMeter/speedtest.py
+++ b/RateMeter/speedtest.py
@@ -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)
\ No newline at end of file
diff --git a/Ratelimiter/README.md b/Ratelimiter/README.md
new file mode 100644
index 0000000..2a273ce
--- /dev/null
+++ b/Ratelimiter/README.md
@@ -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.
\ No newline at end of file
diff --git a/Ratelimiter/ratelimiter.py b/Ratelimiter/ratelimiter.py
index 55f856e..f7953c1 100644
--- a/Ratelimiter/ratelimiter.py
+++ b/Ratelimiter/ratelimiter.py
@@ -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.
- Return True every time.
- 'reject': If we do not have the balance for an operation, return False.
+ '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.
+ 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
diff --git a/SinWave/sinwave.py b/SinWave/sinwave.py
new file mode 100644
index 0000000..94b0ee1
--- /dev/null
+++ b/SinWave/sinwave.py
@@ -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()
\ No newline at end of file
diff --git a/Steganographic/README.md b/Steganographic/README.md
index 88b15ab..8ba50b6 100644
--- a/Steganographic/README.md
+++ b/Steganographic/README.md
@@ -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.
diff --git a/Steganographic/steganographic.py b/Steganographic/steganographic.py
index f5da08c..3cf53e8 100644
--- a/Steganographic/steganographic.py
+++ b/Steganographic/steganographic.py
@@ -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)
\ No newline at end of file