Scanning + computer vision experiments

This commit is contained in:
Jeremy Penner 2024-09-21 20:22:42 -04:00
parent a595e437b6
commit 6796588213
10 changed files with 1150 additions and 66 deletions

176
cvtest.py Normal file
View file

@ -0,0 +1,176 @@
import cv2
import imutils
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib import colors
RESIZE_RATIO = 0.07
def shrink(img):
return cv2.resize(img, None, fx=RESIZE_RATIO, fy=RESIZE_RATIO)
def noop(*x):
pass
def darken_color(img, sat=20, val=225):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
s[s > sat] = 255
v[v < val] = 0
hsv2 = cv2.merge((h, s, v))
return cv2.cvtColor(hsv2, cv2.COLOR_HSV2BGR)
def interactive_edge(filename):
img = shrink(cv2.imread(filename))
cv2.namedWindow("image")
cv2.createTrackbar("threshold1", "image", 5, 50, noop)
cv2.createTrackbar("threshold2", "image", 20, 50, noop)
while 1:
threshold1 = cv2.getTrackbarPos("threshold1", "image")
threshold2 = cv2.getTrackbarPos("threshold2", "image")
edged = edge_img(img, threshold1 * 10, threshold2 * 10)
# contours = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# contours = imutils.grab_contours(contours)
# shapes = cv2.drawContours(edged.copy(), contours, -1, (255, 255, 255), -1)
cv2.imshow("image", edged)
if cv2.waitKey(0) < 0:
break
cv2.destroyWindow("image")
def edge_img(orig, t1=50, t2=200):
img = cv2.GaussianBlur(orig.copy(), (5, 5), 0)
img = darken_color(img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_blurred = cv2.GaussianBlur(gray, (5, 5), 0)
return cv2.Canny(gray_blurred, t1, t2)
def approximate_contour(c):
peri = cv2.arcLength(c, True)
return cv2.approxPolyDP(c, 0.02 * peri, True)
def group_contours(contours):
remaining = contours.copy()
groups = []
while len(remaining) > 0:
group = [remaining[0]]
groups.append(group)
grew = False
remaining = remaining[1:]
while True:
nextremaining = []
l, t, r, b = cv2.boundingRect(remaining[0])
for c in remaining:
l2, t2, r2, b2 = cv2.boundingRect(c)
if r2 < l or l2 > r or t2 > b or b2 < t:
# they do not intersect
nextremaining.append(c)
else:
# they do intersect
grew = True
group.append(c)
l = min(l, l2)
t = min(t, t2)
r = max(r, r2)
b = max(b, b2)
remaining = nextremaining
if not grew:
break
return groups
def autocrop_contour(orig, contour):
approx = approximate_contour(contour)
if len(approx) == 4:
# It's a rectangle - perspective-correct it
pass
else:
# It's a disc - crop around it
pass
def interactive_contour(filename):
orig = cv2.imread(filename)
orig = shrink(orig)
edge = edge_img(orig)
contours = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
contours = [approximate_contour(c) for c in contours]
cv2.namedWindow("image")
cv2.createTrackbar("contour", "image", 0, len(contours) - 1, noop)
while True:
icontour = cv2.getTrackbarPos("contour", "image")
img = cv2.drawContours(orig.copy(), contours, icontour, (255, 0, 255), 3)
cv2.imshow("image", img)
if cv2.waitKey() < 0:
break
def get_pixel_colors(rgbimg):
pixel_colors = rgbimg.reshape((np.shape(rgbimg)[0] * np.shape(rgbimg)[1], 3))
norm = colors.Normalize(vmin=-1.0, vmax=1.0)
norm.autoscale(pixel_colors)
return norm(pixel_colors).tolist()
def plot_hsv(rgbimg):
hsv = cv2.cvtColor(rgbimg.copy(), cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
fig = plt.figure()
axis = fig.add_subplot(1, 1, 1, projection="3d")
axis.scatter(
h.flatten(),
s.flatten(),
v.flatten(),
facecolors=get_pixel_colors(rgbimg),
marker=".",
)
axis.set_xlabel("Hue")
axis.set_ylabel("Sat")
axis.set_zlabel("Val")
plt.show()
def interactive_darken(filename):
img = cv2.imread(filename)
img = shrink(img)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
cv2.namedWindow("image")
cv2.createTrackbar("Sat", "image", 30, 255, noop)
cv2.createTrackbar("Val", "image", 225, 255, noop)
while True:
sat = cv2.getTrackbarPos("Sat", "image")
val = cv2.getTrackbarPos("Val", "image")
h, s, v = cv2.split(hsv)
s[s > sat] = 255
v[v < val] = 0
hsv2 = cv2.merge((h, s, v))
bgr = cv2.cvtColor(hsv2, cv2.COLOR_HSV2BGR)
cv2.imshow("image", bgr)
if cv2.waitKey() < 0:
break
if __name__ == "__main__":
interactive_edge("testimg3.jpg")

160
poetry.lock generated
View file

@ -31,6 +31,14 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.1.0" version = "19.1.0"
[[package]]
category = "dev"
description = "Specifications for callback functions passed in to an API"
name = "backcall"
optional = false
python-versions = "*"
version = "0.1.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
@ -74,6 +82,17 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.1" version = "0.4.1"
[[package]]
category = "main"
description = "Composable style cycles"
name = "cycler"
optional = false
python-versions = "*"
version = "0.10.0"
[package.dependencies]
six = "*"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Better living through Python with decorators" description = "Better living through Python with decorators"
@ -124,6 +143,14 @@ version = "0.23"
[package.dependencies] [package.dependencies]
zipp = ">=0.5" zipp = ">=0.5"
[[package]]
category = "main"
description = "A series of convenience functions to make basic image processing functions such as translation, rotation, resizing, skeletonization, displaying Matplotlib images, sorting contours, detecting edges, and much more easier with OpenCV and both Python 2.7 and Python 3."
name = "imutils"
optional = false
python-versions = "*"
version = "0.5.3"
[[package]] [[package]]
category = "dev" category = "dev"
description = "IPython Kernel for Jupyter" description = "IPython Kernel for Jupyter"
@ -143,20 +170,20 @@ category = "dev"
description = "IPython: Productive Interactive Computing" description = "IPython: Productive Interactive Computing"
name = "ipython" name = "ipython"
optional = false optional = false
python-versions = ">=3.3" python-versions = ">=3.5"
version = "6.2.1" version = "7.8.0"
[package.dependencies] [package.dependencies]
appnope = "*" appnope = "*"
backcall = "*"
colorama = "*" colorama = "*"
decorator = "*" decorator = "*"
jedi = ">=0.10" jedi = ">=0.10"
pexpect = "*" pexpect = "*"
pickleshare = "*" pickleshare = "*"
prompt-toolkit = ">=1.0.4,<2.0.0" prompt-toolkit = ">=2.0.0,<2.1.0"
pygments = "*" pygments = "*"
setuptools = ">=18.5" setuptools = ">=18.5"
simplegeneric = ">0.8"
traitlets = ">=4.2" traitlets = ">=4.2"
[[package]] [[package]]
@ -258,14 +285,14 @@ category = "dev"
description = "Jupyter terminal console" description = "Jupyter terminal console"
name = "jupyter-console" name = "jupyter-console"
optional = false optional = false
python-versions = "*" python-versions = ">=3.5"
version = "5.2.0" version = "6.0.0"
[package.dependencies] [package.dependencies]
ipykernel = "*" ipykernel = "*"
ipython = "*" ipython = "*"
jupyter-client = "*" jupyter-client = "*"
prompt-toolkit = ">=1.0.0,<2.0.0" prompt-toolkit = ">=2.0.0,<2.1.0"
pygments = "*" pygments = "*"
[[package]] [[package]]
@ -279,6 +306,17 @@ version = "4.5.0"
[package.dependencies] [package.dependencies]
traitlets = "*" traitlets = "*"
[[package]]
category = "main"
description = "A fast implementation of the Cassowary constraint solver"
name = "kiwisolver"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.1.0"
[package.dependencies]
setuptools = "*"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
@ -287,6 +325,21 @@ optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.1.1" version = "1.1.1"
[[package]]
category = "main"
description = "Python plotting package"
name = "matplotlib"
optional = false
python-versions = ">=3.6"
version = "3.1.1"
[package.dependencies]
cycler = ">=0.10"
kiwisolver = ">=1.0.1"
numpy = ">=1.11"
pyparsing = ">=2.0.1,<2.0.4 || >2.0.4,<2.1.2 || >2.1.2,<2.1.6 || >2.1.6"
python-dateutil = ">=2.1"
[[package]] [[package]]
category = "dev" category = "dev"
description = "McCabe checker, plugin for flake8" description = "McCabe checker, plugin for flake8"
@ -369,6 +422,25 @@ terminado = ">=0.8.1"
tornado = ">=5.0" tornado = ">=5.0"
traitlets = ">=4.2.1" traitlets = ">=4.2.1"
[[package]]
category = "main"
description = "NumPy is the fundamental package for array computing with Python."
name = "numpy"
optional = false
python-versions = ">=3.5"
version = "1.17.2"
[[package]]
category = "main"
description = "Wrapper package for OpenCV python bindings."
name = "opencv-python"
optional = false
python-versions = "*"
version = "4.1.1.26"
[package.dependencies]
numpy = ">=1.11.1"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Utilities for writing pandoc filters in python" description = "Utilities for writing pandoc filters in python"
@ -435,12 +507,12 @@ python-versions = "*"
version = "0.7.1" version = "0.7.1"
[[package]] [[package]]
category = "main" category = "dev"
description = "Library for building powerful interactive command lines in Python" description = "Library for building powerful interactive command lines in Python"
name = "prompt-toolkit" name = "prompt-toolkit"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.0.14" version = "2.0.9"
[package.dependencies] [package.dependencies]
six = ">=1.9.0" six = ">=1.9.0"
@ -480,7 +552,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.1.1" version = "2.1.1"
[[package]] [[package]]
category = "main" category = "dev"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
name = "pygments" name = "pygments"
optional = false optional = false
@ -489,28 +561,23 @@ version = "2.4.2"
[[package]] [[package]]
category = "main" category = "main"
description = "A Python module for collection of common interactive command line user interfaces, based on Inquirer.js" description = "Python library to access and use image scanners (Linux/Windows/etc)"
name = "pyinquirer" name = "pyinsane2"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.0.3" version = "2.0.13"
[package.dependencies]
Pygments = ">=2.2.0"
prompt_toolkit = "1.0.14"
regex = ">=2016.11.21"
[[package]]
category = "main"
description = "Pure Python implementation of the Sane API (using ctypes) and abstration layer"
name = "pyinsane"
optional = false
python-versions = "*"
version = "1.4.0"
[package.dependencies] [package.dependencies]
Pillow = "*" Pillow = "*"
[[package]]
category = "main"
description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.2"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Persistent/Functional/Immutable data structures" description = "Persistent/Functional/Immutable data structures"
@ -541,7 +608,7 @@ setuptools = "*"
six = ">=1.10.0" six = ">=1.10.0"
[[package]] [[package]]
category = "dev" category = "main"
description = "Extensions to the standard Python datetime module" description = "Extensions to the standard Python datetime module"
name = "python-dateutil" name = "python-dateutil"
optional = false optional = false
@ -593,14 +660,6 @@ jupyter-core = "*"
pygments = "*" pygments = "*"
traitlets = "*" traitlets = "*"
[[package]]
category = "main"
description = "Alternative regular expression module, to replace re."
name = "regex"
optional = false
python-versions = "*"
version = "2019.08.19"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Send file to trash natively under Mac OS X, Windows and Linux." description = "Send file to trash natively under Mac OS X, Windows and Linux."
@ -609,14 +668,6 @@ optional = false
python-versions = "*" python-versions = "*"
version = "1.5.0" version = "1.5.0"
[[package]]
category = "dev"
description = "Simple generic functions (similar to Python's own len(), pickle.dump(), etc.)"
name = "simplegeneric"
optional = false
python-versions = "*"
version = "0.8.1"
[[package]] [[package]]
category = "main" category = "main"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
@ -676,7 +727,7 @@ ipython-genutils = "*"
six = "*" six = "*"
[[package]] [[package]]
category = "main" category = "dev"
description = "Measures number of Terminal column cells of wide-character codes" description = "Measures number of Terminal column cells of wide-character codes"
name = "wcwidth" name = "wcwidth"
optional = false optional = false
@ -715,7 +766,7 @@ version = "0.6.0"
more-itertools = "*" more-itertools = "*"
[metadata] [metadata]
content-hash = "a0b14789772e6aef430877c07f16231d14758c6a230276d81cb6466f3cd97c02" content-hash = "7e200ef69278ec9f82a2bb37e54d6966e2190cd269bb1d714987dc4db9ab94f5"
python-versions = "^3.7" python-versions = "^3.7"
[metadata.hashes] [metadata.hashes]
@ -723,17 +774,20 @@ appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "
appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"] appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"]
atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"]
backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"]
black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"]
bleach = ["213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", "3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"] bleach = ["213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", "3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"]
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
cycler = ["1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", "cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"]
decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"] decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"]
defusedxml = ["6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", "f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"] defusedxml = ["6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", "f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"]
entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"]
flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"]
importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"] importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"]
imutils = ["857af6169d90e4a0a814130b9b107f5d611150ce440107e1c1233521c6fb1e2b"]
ipykernel = ["167c3ef08450f5e060b76c749905acb0e0fbef9365899377a4a1eae728864383", "b503913e0b4cce7ed2de965457dfb2edd633e8234161a60e23f2fe2161345d12"] ipykernel = ["167c3ef08450f5e060b76c749905acb0e0fbef9365899377a4a1eae728864383", "b503913e0b4cce7ed2de965457dfb2edd633e8234161a60e23f2fe2161345d12"]
ipython = ["51c158a6c8b899898d1c91c6b51a34110196815cc905f9be0fa5878e19355608", "fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3"] ipython = ["c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b", "dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1"]
ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"] ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"]
ipywidgets = ["13ffeca438e0c0f91ae583dc22f50379b9d6b28390ac7be8b757140e9a771516", "e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97"] ipywidgets = ["13ffeca438e0c0f91ae583dc22f50379b9d6b28390ac7be8b757140e9a771516", "e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97"]
jedi = ["786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"] jedi = ["786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"]
@ -741,15 +795,19 @@ jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "1
jsonschema = ["5f9c0a719ca2ce14c5de2fd350a64fd2d13e8539db29836a86adc990bb1a068f", "8d4a2b7b6c2237e0199c8ea1a6d3e05bf118e289ae2b9d7ba444182a2959560d"] jsonschema = ["5f9c0a719ca2ce14c5de2fd350a64fd2d13e8539db29836a86adc990bb1a068f", "8d4a2b7b6c2237e0199c8ea1a6d3e05bf118e289ae2b9d7ba444182a2959560d"]
jupyter = ["3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", "5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", "d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"] jupyter = ["3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", "5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", "d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"]
jupyter-client = ["6a6d415c62179728f6d9295b37356d8f6833e9e01c2b6e1901dc555571f57b21", "f406f214f9daa92be110d5b83d62f3451ffc73d3522db7350f0554683533ab18"] jupyter-client = ["6a6d415c62179728f6d9295b37356d8f6833e9e01c2b6e1901dc555571f57b21", "f406f214f9daa92be110d5b83d62f3451ffc73d3522db7350f0554683533ab18"]
jupyter-console = ["3f928b817fc82cda95e431eb4c2b5eb21be5c483c2b43f424761a966bb808094", "545dedd3aaaa355148093c5609f0229aeb121b4852995c2accfa64fe3e0e55cd"] jupyter-console = ["308ce876354924fb6c540b41d5d6d08acfc946984bf0c97777c1ddcb42e0b2f5", "cc80a97a5c389cbd30252ffb5ce7cefd4b66bde98219edd16bf5cb6f84bb3568"]
jupyter-core = ["2c6e7c1e9f2ac45b5c2ceea5730bc9008d92fe59d0725eac57b04c0edfba24f7", "f4fa22d6cf25f34807c995f22d2923693575c70f02557bcbfbe59bd5ec8d8b84"] jupyter-core = ["2c6e7c1e9f2ac45b5c2ceea5730bc9008d92fe59d0725eac57b04c0edfba24f7", "f4fa22d6cf25f34807c995f22d2923693575c70f02557bcbfbe59bd5ec8d8b84"]
kiwisolver = ["05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f", "26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7", "3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe", "400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c", "47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5", "53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75", "58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187", "5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641", "5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883", "682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5", "79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2", "7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3", "8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389", "8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897", "939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a", "9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c", "a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326", "a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0", "acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e", "b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544", "d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995", "d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f", "db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee", "e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004", "e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2", "f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9", "f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a", "f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f"]
markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
matplotlib = ["1febd22afe1489b13c6749ea059d392c03261b2950d1d45c17e3aed812080c93", "31a30d03f39528c79f3a592857be62a08595dec4ac034978ecd0f814fa0eec2d", "4442ce720907f67a79d45de9ada47be81ce17e6c2f448b3c64765af93f6829c9", "796edbd1182cbffa7e1e7a97f1e141f875a8501ba8dd834269ae3cd45a8c976f", "934e6243df7165aad097572abf5b6003c77c9b6c480c3c4de6f2ef1b5fdd4ec0", "bab9d848dbf1517bc58d1f486772e99919b19efef5dd8596d4b26f9f5ee08b6b", "c1fe1e6cdaa53f11f088b7470c2056c0df7d80ee4858dadf6cbe433fcba4323b", "e5b8aeca9276a3a988caebe9f08366ed519fff98f77c6df5b64d7603d0e42e36", "ec6bd0a6a58df3628ff269978f4a4b924a0d371ad8ce1f8e2b635b99e482877a"]
mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
mistune = ["59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", "88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"] mistune = ["59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", "88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"]
more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
nbconvert = ["427a468ec26e7d68a529b95f578d5cbf018cb4c1f889e897681c2b6d11897695", "48d3c342057a2cf21e8df820d49ff27ab9f25fc72b8f15606bd47967333b2709"] nbconvert = ["427a468ec26e7d68a529b95f578d5cbf018cb4c1f889e897681c2b6d11897695", "48d3c342057a2cf21e8df820d49ff27ab9f25fc72b8f15606bd47967333b2709"]
nbformat = ["b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", "f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402"] nbformat = ["b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", "f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402"]
notebook = ["660976fe4fe45c7aa55e04bf4bccb9f9566749ff637e9020af3422f9921f9a5d", "b0a290f5cc7792d50a21bec62b3c221dd820bf00efa916ce9aeec4b5354bde20"] notebook = ["660976fe4fe45c7aa55e04bf4bccb9f9566749ff637e9020af3422f9921f9a5d", "b0a290f5cc7792d50a21bec62b3c221dd820bf00efa916ce9aeec4b5354bde20"]
numpy = ["05dbfe72684cc14b92568de1bc1f41e5f62b00f714afc9adee42f6311738091f", "0d82cb7271a577529d07bbb05cb58675f2deb09772175fab96dc8de025d8ac05", "10132aa1fef99adc85a905d82e8497a580f83739837d7cbd234649f2e9b9dc58", "12322df2e21f033a60c80319c25011194cd2a21294cc66fee0908aeae2c27832", "16f19b3aa775dddc9814e02a46b8e6ae6a54ed8cf143962b4e53f0471dbd7b16", "3d0b0989dd2d066db006158de7220802899a1e5c8cf622abe2d0bd158fd01c2c", "438a3f0e7b681642898fd7993d38e2bf140a2d1eafaf3e89bb626db7f50db355", "5fd214f482ab53f2cea57414c5fb3e58895b17df6e6f5bca5be6a0bb6aea23bb", "73615d3edc84dd7c4aeb212fa3748fb83217e00d201875a47327f55363cef2df", "7bd355ad7496f4ce1d235e9814ec81ee3d28308d591c067ce92e49f745ba2c2f", "7d077f2976b8f3de08a0dcf5d72083f4af5411e8fddacd662aae27baa2601196", "a4092682778dc48093e8bda8d26ee8360153e2047826f95a3f5eae09f0ae3abf", "b458de8624c9f6034af492372eb2fee41a8e605f03f4732f43fc099e227858b2", "e70fc8ff03a961f13363c2c95ef8285e0cf6a720f8271836f852cc0fa64e97c8", "ee8e9d7cad5fe6dde50ede0d2e978d81eafeaa6233fb0b8719f60214cf226578", "f4a4f6aba148858a5a5d546a99280f71f5ee6ec8182a7d195af1a914195b21a2"]
opencv-python = ["01505b131dc35f60e99a5da98b77156e37f872ae0ff5596e5e68d526bb572d3c", "0478a1037505ddde312806c960a5e8958d2cf7a2885e8f2f5dde74c4028e0b04", "17810b89f9ef8e8537e75332acf533e619e26ccadbf1b73f24bf338f2d327ddd", "19ad2ea9fb32946761b47b9d6eed51876a8329da127f27788263fecd66651ba0", "1a250edb739baf3e7c25d99a2ee252aac4f59a97e0bee39237eaa490fd0281d3", "3505468970448f66cd776cb9e179570c87988f94b5cf9bcbc4c2d88bd88bbdf1", "4e04a91da157885359f487534433340b2d709927559c80acf62c28167e59be02", "5a49cffcdec5e37217672579c3343565926d999642844efa9c6a031ed5f32318", "604b2ce3d4a86480ced0813da7fba269b4605ad9fea26cd2144d8077928d4b49", "61cbb8fa9565a0480c46028599431ad8f19181a7fac8070a700515fd54cd7377", "62d7c6e511c9454f099616315c695d02a584048e1affe034b39160db7a2ae34d", "6555272dd9efd412d17cdc1a4f4c2da5753c099d95d9ff01aca54bb9782fb5cf", "67d994c6b2b14cb9239e85dc7dfa6c08ef7cf6eb4def80c0af6141dfacc8cbb9", "68c9cbe538666c4667523821cc56caee49389bea06bae4c0fc2cd68bd264226a", "822ad8f628a9498f569c57d30865f5ef9ee17824cee0a1d456211f742028c135", "82d972429eb4fee22c1dc4204af2a2e981f010e5e4f66daea2a6c68381b79184", "9128924f5b58269ee221b8cf2d736f31bd3bb0391b92ee8504caadd68c8176a2", "9172cf8270572c494d8b2ae12ef87c0f6eed9d132927e614099f76843b0c91d7", "952bce4d30a8287b17721ddaad7f115dab268efee8576249ddfede80ec2ce404", "a8147718e70b1f170a3d26518e992160137365a4db0ed82a9efd3040f9f660d4", "bfdb636a3796ff223460ea0fcfda906b3b54f4bef22ae433a5b67e66fab00b25", "c9c3f27867153634e1083390920067008ebaaab78aeb09c4e0274e69746cb2c8", "d69be21973d450a4662ae6bd1b3df6b1af030e448d7276380b0d1adf7c8c2ae6", "db1479636812a6579a3753b72a6fefaa73190f32bf7b19e483f8bc750cebe1a5", "db8313d755962a7dd61e5c22a651e0743208adfdb255c6ec8904ce9cb02940c6", "e4625a6b032e7797958aeb630d6e3e91e3896d285020aae612e6d7b342d6dfea", "e8397a26966a1290836a52c34b362aabc65a422b9ffabcbbdec1862f023ccab8"]
pandocfilters = ["b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"] pandocfilters = ["b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"]
parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"] parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"]
pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"] pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"]
@ -757,14 +815,14 @@ pickleshare = ["87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca
pillow = ["0804f77cb1e9b6dbd37601cee11283bba39a8d44b9ddb053400c58e0c0d7d9de", "0ab7c5b5d04691bcbd570658667dd1e21ca311c62dcfd315ad2255b1cd37f64f", "0b3e6cf3ea1f8cecd625f1420b931c83ce74f00c29a0ff1ce4385f99900ac7c4", "0c6ce6ae03a50b0306a683696234b8bc88c5b292d4181ae365b89bd90250ab08", "1454ee7297a81c8308ad61d74c849486efa1badc543453c4b90db0bf99decc1c", "23efd7f83f2ad6036e2b9ef27a46df7e333de1ad9087d341d87e12225d0142b2", "365c06a45712cd723ec16fa4ceb32ce46ad201eb7bbf6d3c16b063c72b61a3ed", "38301fbc0af865baa4752ddae1bb3cbb24b3d8f221bf2850aad96b243306fa03", "3aef1af1a91798536bbab35d70d35750bd2884f0832c88aeb2499aa2d1ed4992", "3c86051d41d1c8b28b9dde08ac93e73aa842991995b12771b0af28da49086bbf", "3fe0ab49537d9330c9bba7f16a5f8b02da615b5c809cdf7124f356a0f182eccd", "406c856e0f6fc330322a319457d9ff6162834050cda2cf1eaaaea4b771d01914", "45a619d5c1915957449264c81c008934452e3fd3604e36809212300b2a4dab68", "49f90f147883a0c3778fd29d3eb169d56416f25758d0f66775db9184debc8010", "504f5334bfd974490a86fef3e3b494cd3c332a8a680d2f258ca03388b40ae230", "51fe9cfcd32c849c6f36ca293648f279fc5097ca8dd6e518b10df3a6a9a13431", "571b5a758baf1cb6a04233fb23d6cf1ca60b31f9f641b1700bfaab1194020555", "5ac381e8b1259925287ccc5a87d9cf6322a2dc88ae28a97fe3e196385288413f", "6052a9e9af4a9a2cc01da4bbee81d42d33feca2bde247c4916d8274b12bb31a4", "6153db744a743c0c8c91b8e3b9d40e0b13a5d31dbf8a12748c6d9bfd3ddc01ad", "6fd63afd14a16f5d6b408f623cc2142917a1f92855f0df997e09a49f0341be8a", "70acbcaba2a638923c2d337e0edea210505708d7859b87c2bd81e8f9902ae826", "70b1594d56ed32d56ed21a7fbb2a5c6fd7446cdb7b21e749c9791eac3a64d9e4", "76638865c83b1bb33bcac2a61ce4d13c17dba2204969dedb9ab60ef62bede686", "7b2ec162c87fc496aa568258ac88631a2ce0acfe681a9af40842fc55deaedc99", "7b403ea842b70c4fa0a4969a5d8d86e932c941095b7cda077ea68f7b98ead30b", "7be698a28175eae5354da94f5f3dc787d5efae6aca7ad1f286a781afde6a27dd", "7cee2cef07c8d76894ebefc54e4bb707dfc7f258ad155bd61d87f6cd487a70ff", "7d16d4498f8b374fc625c4037742fbdd7f9ac383fd50b06f4df00c81ef60e829", "82840783842b27933cc6388800cb547f31caf436f7e23384d456bdf5fc8dfe49", "8755e600b33f4e8c76a590b42acc35d24f4dc801a5868519ce569b9462d77598", "9159285ab4030c6f85e001468cb5886de05e6bd9304e9e7d46b983f7d2fad0cc", "b50bc1780681b127e28f0075dfb81d6135c3a293e0c1d0211133c75e2179b6c0", "b5aa19f1da16b4f5e47b6930053f08cba77ceccaed68748061b0ec24860e510c", "bd0582f831ad5bcad6ca001deba4568573a4675437db17c4031939156ff339fa", "cdd53acd3afb9878a2289a1b55807871f9877c81174ae0d3763e52f907131d25", "cfd40d8a4b59f7567620410f966bb1f32dc555b2b19f82a91b147fac296f645c", "e150c5aed6e67321edc6893faa6701581ca2d393472f39142a00e551bcd249a5", "e3ae410089de680e8f84c68b755b42bc42c0ceb8c03dbea88a5099747091d38e", "e403b37c6a253ebca5d0f2e5624643997aaae529dc96299162418ef54e29eb70", "e9046e559c299b395b39ac7dbf16005308821c2f24a63cae2ab173bd6aa11616", "ef6be704ae2bc8ad0ebc5cb850ee9139493b0fc4e81abcc240fb392a63ebc808", "f8dc19d92896558f9c4317ee365729ead9d7bbcf2052a9a19a3ef17abbb8ac5b"] pillow = ["0804f77cb1e9b6dbd37601cee11283bba39a8d44b9ddb053400c58e0c0d7d9de", "0ab7c5b5d04691bcbd570658667dd1e21ca311c62dcfd315ad2255b1cd37f64f", "0b3e6cf3ea1f8cecd625f1420b931c83ce74f00c29a0ff1ce4385f99900ac7c4", "0c6ce6ae03a50b0306a683696234b8bc88c5b292d4181ae365b89bd90250ab08", "1454ee7297a81c8308ad61d74c849486efa1badc543453c4b90db0bf99decc1c", "23efd7f83f2ad6036e2b9ef27a46df7e333de1ad9087d341d87e12225d0142b2", "365c06a45712cd723ec16fa4ceb32ce46ad201eb7bbf6d3c16b063c72b61a3ed", "38301fbc0af865baa4752ddae1bb3cbb24b3d8f221bf2850aad96b243306fa03", "3aef1af1a91798536bbab35d70d35750bd2884f0832c88aeb2499aa2d1ed4992", "3c86051d41d1c8b28b9dde08ac93e73aa842991995b12771b0af28da49086bbf", "3fe0ab49537d9330c9bba7f16a5f8b02da615b5c809cdf7124f356a0f182eccd", "406c856e0f6fc330322a319457d9ff6162834050cda2cf1eaaaea4b771d01914", "45a619d5c1915957449264c81c008934452e3fd3604e36809212300b2a4dab68", "49f90f147883a0c3778fd29d3eb169d56416f25758d0f66775db9184debc8010", "504f5334bfd974490a86fef3e3b494cd3c332a8a680d2f258ca03388b40ae230", "51fe9cfcd32c849c6f36ca293648f279fc5097ca8dd6e518b10df3a6a9a13431", "571b5a758baf1cb6a04233fb23d6cf1ca60b31f9f641b1700bfaab1194020555", "5ac381e8b1259925287ccc5a87d9cf6322a2dc88ae28a97fe3e196385288413f", "6052a9e9af4a9a2cc01da4bbee81d42d33feca2bde247c4916d8274b12bb31a4", "6153db744a743c0c8c91b8e3b9d40e0b13a5d31dbf8a12748c6d9bfd3ddc01ad", "6fd63afd14a16f5d6b408f623cc2142917a1f92855f0df997e09a49f0341be8a", "70acbcaba2a638923c2d337e0edea210505708d7859b87c2bd81e8f9902ae826", "70b1594d56ed32d56ed21a7fbb2a5c6fd7446cdb7b21e749c9791eac3a64d9e4", "76638865c83b1bb33bcac2a61ce4d13c17dba2204969dedb9ab60ef62bede686", "7b2ec162c87fc496aa568258ac88631a2ce0acfe681a9af40842fc55deaedc99", "7b403ea842b70c4fa0a4969a5d8d86e932c941095b7cda077ea68f7b98ead30b", "7be698a28175eae5354da94f5f3dc787d5efae6aca7ad1f286a781afde6a27dd", "7cee2cef07c8d76894ebefc54e4bb707dfc7f258ad155bd61d87f6cd487a70ff", "7d16d4498f8b374fc625c4037742fbdd7f9ac383fd50b06f4df00c81ef60e829", "82840783842b27933cc6388800cb547f31caf436f7e23384d456bdf5fc8dfe49", "8755e600b33f4e8c76a590b42acc35d24f4dc801a5868519ce569b9462d77598", "9159285ab4030c6f85e001468cb5886de05e6bd9304e9e7d46b983f7d2fad0cc", "b50bc1780681b127e28f0075dfb81d6135c3a293e0c1d0211133c75e2179b6c0", "b5aa19f1da16b4f5e47b6930053f08cba77ceccaed68748061b0ec24860e510c", "bd0582f831ad5bcad6ca001deba4568573a4675437db17c4031939156ff339fa", "cdd53acd3afb9878a2289a1b55807871f9877c81174ae0d3763e52f907131d25", "cfd40d8a4b59f7567620410f966bb1f32dc555b2b19f82a91b147fac296f645c", "e150c5aed6e67321edc6893faa6701581ca2d393472f39142a00e551bcd249a5", "e3ae410089de680e8f84c68b755b42bc42c0ceb8c03dbea88a5099747091d38e", "e403b37c6a253ebca5d0f2e5624643997aaae529dc96299162418ef54e29eb70", "e9046e559c299b395b39ac7dbf16005308821c2f24a63cae2ab173bd6aa11616", "ef6be704ae2bc8ad0ebc5cb850ee9139493b0fc4e81abcc240fb392a63ebc808", "f8dc19d92896558f9c4317ee365729ead9d7bbcf2052a9a19a3ef17abbb8ac5b"]
pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"] pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"]
prometheus-client = ["71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da"] prometheus-client = ["71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da"]
prompt-toolkit = ["7281b5199235adaef6980942840c43753e4ab20dfe41338da634fb41c194f9d8", "82c7f8e07d7a0411ff5367a5a8ff520f0112b9179f3e599ee8ad2ad9b943d911", "cc66413b1b4b17021675d9f2d15d57e640b06ddfd99bb724c73484126d22622f"] prompt-toolkit = ["11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", "2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", "977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"]
ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"] ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"]
py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"]
pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"]
pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"]
pyinquirer = ["c9a92d68d7727fbd886a7908c08fd9e9773e5dc211bf5cbf836ba90d366dee51"] pyinsane2 = ["0d519531d552e4512776225eb400a6a4a9bfc83a08918ec7fea19cb2fa7ec4ee", "a172fc2947ce9d3ed0b3f3b204b5e2fd57658f672d346c54f7f29d727fe56d0c", "df3ffdb2abd52fb4b5ca1f47df8c39a995ce04bf1f6f09250cb9e77cb0dddfb8"]
pyinsane = ["11df7b8abc0875a00cf2257b736fcf7b7913bd93e0428bf5b7decb9f143df74c"] pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"]
pyrsistent = ["34b47fa169d6006b32e99d4b3c4031f155e6e68ebcc107d6454852e8e0ee6533"] pyrsistent = ["34b47fa169d6006b32e99d4b3c4031f155e6e68ebcc107d6454852e8e0ee6533"]
pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"] pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"]
python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"] python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"]
@ -772,9 +830,7 @@ pywin32 = ["0443e9bb196e72480f50cbddc2cf98fbb858a77d02e281ba79489ea3287b36e9", "
pywinpty = ["0e01321e53a230233358a6d608a1a8bc86c3882cf82769ba3c62ca387dc9cc51", "333e0bc5fca8ad9e9a1516ebedb2a65da38dc1f399f8b2ea57d6cccec1ff2cc8", "3ca3123aa6340ab31bbf9bd012b92e72f9ec905e4c9ee152cc997403e1778cd3", "44a6dddcf2abf402e22f87e2c9a341f7d0b296afbec3d28184c8de4d7f514ee4", "53d94d574c3d4da2df5b1c3ae728b8d90e4d33502b0388576bbd4ddeb4de0f77", "c3955f162c53dde968f3fc11361658f1d83b683bfe601d4b6f94bb01ea4300bc", "cec9894ecb34de3d7b1ca121dd98433035b9f8949b5095e84b103b349231509c", "dcd45912e2fe2e6f72cee997a4da6ed1ad2056165a277ce5ec7f7ac98dcdf667", "f2bcdd9a2ffd8b223752a971b3d377fb7bfed85f140ec9710f1218d760f2ccb7"] pywinpty = ["0e01321e53a230233358a6d608a1a8bc86c3882cf82769ba3c62ca387dc9cc51", "333e0bc5fca8ad9e9a1516ebedb2a65da38dc1f399f8b2ea57d6cccec1ff2cc8", "3ca3123aa6340ab31bbf9bd012b92e72f9ec905e4c9ee152cc997403e1778cd3", "44a6dddcf2abf402e22f87e2c9a341f7d0b296afbec3d28184c8de4d7f514ee4", "53d94d574c3d4da2df5b1c3ae728b8d90e4d33502b0388576bbd4ddeb4de0f77", "c3955f162c53dde968f3fc11361658f1d83b683bfe601d4b6f94bb01ea4300bc", "cec9894ecb34de3d7b1ca121dd98433035b9f8949b5095e84b103b349231509c", "dcd45912e2fe2e6f72cee997a4da6ed1ad2056165a277ce5ec7f7ac98dcdf667", "f2bcdd9a2ffd8b223752a971b3d377fb7bfed85f140ec9710f1218d760f2ccb7"]
pyzmq = ["01636e95a88d60118479041c6aaaaf5419c6485b7b1d37c9c4dd424b7b9f1121", "021dba0d1436516092c624359e5da51472b11ba8edffa334218912f7e8b65467", "0463bd941b6aead494d4035f7eebd70035293dd6caf8425993e85ad41de13fa3", "05fd51edd81eed798fccafdd49c936b6c166ffae7b32482e4d6d6a2e196af4e6", "1fadc8fbdf3d22753c36d4172169d184ee6654f8d6539e7af25029643363c490", "22efa0596cf245a78a99060fe5682c4cd00c58bb7614271129215c889062db80", "260c70b7c018905ec3659d0f04db735ac830fe27236e43b9dc0532cf7c9873ef", "2762c45e289732d4450406cedca35a9d4d71e449131ba2f491e0bf473e3d2ff2", "2fc6cada8dc53521c1189596f1898d45c5f68603194d3a6453d6db4b27f4e12e", "343b9710a61f2b167673bea1974e70b5dccfe64b5ed10626798f08c1f7227e72", "41bf96d5f554598a0632c3ec28e3026f1d6591a50f580df38eff0b8067efb9e7", "856b2cdf7a1e2cbb84928e1e8db0ea4018709b39804103d3a409e5584f553f57", "85b869abc894672de9aecdf032158ea8ad01e2f0c3b09ef60e3687fb79418096", "93f44739db69234c013a16990e43db1aa0af3cf5a4b8b377d028ff24515fbeb3", "98fa3e75ccb22c0dc99654e3dd9ff693b956861459e8c8e8734dd6247b89eb29", "9a22c94d2e93af8bebd4fcf5fa38830f5e3b1ff0d4424e2912b07651eb1bafb4", "a7d3f4b4bbb5d7866ae727763268b5c15797cbd7b63ea17f3b0ec1067da8994b", "b645a49376547b3816433a7e2d2a99135c8e651e50497e7ecac3bd126e4bea16", "cf0765822e78cf9e45451647a346d443f66792aba906bc340f4e0ac7870c169c", "dc398e1e047efb18bfab7a8989346c6921a847feae2cad69fedf6ca12fb99e2c", "dd5995ae2e80044e33b5077fb4bc2b0c1788ac6feaf15a6b87a00c14b4bdd682", "e03fe5e07e70f245dc9013a9d48ae8cc4b10c33a1968039c5a3b64b5d01d083d", "ea09a306144dff2795e48439883349819bef2c53c0ee62a3c2fae429451843bb", "f4e37f33da282c3c319849877e34f97f0a3acec09622ec61b7333205bdd13b52", "fa4bad0d1d173dee3e8ef3c3eb6b2bb6c723fc7a661eeecc1ecb2fa99860dd45"] pyzmq = ["01636e95a88d60118479041c6aaaaf5419c6485b7b1d37c9c4dd424b7b9f1121", "021dba0d1436516092c624359e5da51472b11ba8edffa334218912f7e8b65467", "0463bd941b6aead494d4035f7eebd70035293dd6caf8425993e85ad41de13fa3", "05fd51edd81eed798fccafdd49c936b6c166ffae7b32482e4d6d6a2e196af4e6", "1fadc8fbdf3d22753c36d4172169d184ee6654f8d6539e7af25029643363c490", "22efa0596cf245a78a99060fe5682c4cd00c58bb7614271129215c889062db80", "260c70b7c018905ec3659d0f04db735ac830fe27236e43b9dc0532cf7c9873ef", "2762c45e289732d4450406cedca35a9d4d71e449131ba2f491e0bf473e3d2ff2", "2fc6cada8dc53521c1189596f1898d45c5f68603194d3a6453d6db4b27f4e12e", "343b9710a61f2b167673bea1974e70b5dccfe64b5ed10626798f08c1f7227e72", "41bf96d5f554598a0632c3ec28e3026f1d6591a50f580df38eff0b8067efb9e7", "856b2cdf7a1e2cbb84928e1e8db0ea4018709b39804103d3a409e5584f553f57", "85b869abc894672de9aecdf032158ea8ad01e2f0c3b09ef60e3687fb79418096", "93f44739db69234c013a16990e43db1aa0af3cf5a4b8b377d028ff24515fbeb3", "98fa3e75ccb22c0dc99654e3dd9ff693b956861459e8c8e8734dd6247b89eb29", "9a22c94d2e93af8bebd4fcf5fa38830f5e3b1ff0d4424e2912b07651eb1bafb4", "a7d3f4b4bbb5d7866ae727763268b5c15797cbd7b63ea17f3b0ec1067da8994b", "b645a49376547b3816433a7e2d2a99135c8e651e50497e7ecac3bd126e4bea16", "cf0765822e78cf9e45451647a346d443f66792aba906bc340f4e0ac7870c169c", "dc398e1e047efb18bfab7a8989346c6921a847feae2cad69fedf6ca12fb99e2c", "dd5995ae2e80044e33b5077fb4bc2b0c1788ac6feaf15a6b87a00c14b4bdd682", "e03fe5e07e70f245dc9013a9d48ae8cc4b10c33a1968039c5a3b64b5d01d083d", "ea09a306144dff2795e48439883349819bef2c53c0ee62a3c2fae429451843bb", "f4e37f33da282c3c319849877e34f97f0a3acec09622ec61b7333205bdd13b52", "fa4bad0d1d173dee3e8ef3c3eb6b2bb6c723fc7a661eeecc1ecb2fa99860dd45"]
qtconsole = ["40d5d8e00d070ea266dbf6f0da74c4b9597b8b8d67cd8233c3ffd8debf923703", "b91e7412587e6cfe1644696538f73baf5611e837be5406633218443b2827c6d9"] qtconsole = ["40d5d8e00d070ea266dbf6f0da74c4b9597b8b8d67cd8233c3ffd8debf923703", "b91e7412587e6cfe1644696538f73baf5611e837be5406633218443b2827c6d9"]
regex = ["1e9f9bc44ca195baf0040b1938e6801d2f3409661c15fe57f8164c678cfc663f", "587b62d48ca359d2d4f02d486f1f0aa9a20fbaf23a9d4198c4bed72ab2f6c849", "835ccdcdc612821edf132c20aef3eaaecfb884c9454fdc480d5887562594ac61", "93f6c9da57e704e128d90736430c5c59dd733327882b371b0cae8833106c2a21", "a46f27d267665016acb3ec8c6046ec5eae8cf80befe85ba47f43c6f5ec636dcd", "c5c8999b3a341b21ac2c6ec704cfcccbc50f1fedd61b6a8ee915ca7fd4b0a557", "d4d1829cf97632673aa49f378b0a2c3925acd795148c5ace8ef854217abbee89", "d96479257e8e4d1d7800adb26bf9c5ca5bab1648a1eddcac84d107b73dc68327", "f20f4912daf443220436759858f96fefbfc6c6ba9e67835fd6e4e9b73582791a", "f2b37b5b2c2a9d56d9e88efef200ec09c36c7f323f9d58d0b985a90923df386d", "fe765b809a1f7ce642c2edeee351e7ebd84391640031ba4b60af8d91a9045890"]
send2trash = ["60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2", "f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"] send2trash = ["60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2", "f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"]
simplegeneric = ["dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"]
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
terminado = ["d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460", "de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2"] terminado = ["d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460", "de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2"]
testpath = ["46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109", "b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8"] testpath = ["46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109", "b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8"]

View file

@ -6,8 +6,10 @@ authors = ["Jeremy Penner <jeremy@sporktania.com>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.7" python = "^3.7"
PyInquirer = "^1.0" pyinsane2 = "^2.0"
pyinsane = "^1.4" opencv-python = "^4.1"
imutils = "^0.5.3"
matplotlib = "^3.1"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^3.0" pytest = "^3.0"

View file

@ -1,10 +1,10 @@
__version__ = "0.1.0" __version__ = "0.1.0"
from cuebin import CueBin # from cuebin import CueBin
# from file import restore, backup # from file import restore, backup
import glob # import glob
for cuefile in glob.glob("D:\\ROMs\\PC\\*.cue"): # for cuefile in glob.glob("D:\\ROMs\\PC\\*.cue"):
CueBin(cuefile).fixAll() # CueBin(cuefile).fixAll()
# restore(cuefile) # restore(cuefile)

View file

@ -1,7 +0,0 @@
from PyInquirer import prompt
def prompt1(q):
q = q.copy()
q["name"] = "response"
return prompt([q])["response"]

20
romtool/scan.py Normal file
View file

@ -0,0 +1,20 @@
import pyinsane2
import cv2
import imutils
pyinsane2.init()
scanner = pyinsane2.get_devices()[0]
def scan():
pyinsane2.set_scanner_opt(scanner, "resolution", [600])
pyinsane2.set_scanner_opt(scanner, "mode", ["Color"])
pyinsane2.maximize_scan_area(scanner)
scan = scanner.scan()
try:
while True:
scan.scan.read()
except EOFError:
pass
return scan.images[0]

837
sw Normal file
View file

@ -0,0 +1,837 @@
#!/usr/bin/env python
import internetarchive
import json
import sys
import os
from glob import glob as pyglob
import sh
import re
import requests
from urllib import urlretrieve
from urlparse import urljoin
import unicodedata
from flask import Flask, render_template, request, make_response, send_from_directory
import requests
from bs4 import BeautifulSoup
from binascii import a2b_base64
from ConfigParser import SafeConfigParser
VALID_OPTIONS = set(['user', 'dbdir', 'db', 'dosbox_install', 'default_collection'])
def load_config():
config = SafeConfigParser()
files = [os.path.expanduser('~/.config/sw.ini')]
path = os.getcwd()
while path != '/':
files.insert(1, os.path.join(path, 'sw.ini'))
path = os.path.dirname(path)
config.read(files)
for k, v in config.items('config'):
if k.lower() in VALID_OPTIONS:
globals()[k.upper()] = v
load_config()
if 'DB' not in globals():
DB=os.path.join(DBDIR, 'ia.json')
# DEFAULT_COLLECTION = 'softwarelibrary_msdos_shareware'
# DEFAULT_COLLECTION = 'open_source_software'
# DEFAULT_COLLECTION = 'softwarelibrary_win3_shareware'
# DEFAULT_COLLECTION = 'glorious_trainwrecks'
def genid(name, spec):
# return 'msdos_' + name + '_shareware'
# return 'actionpoint_' + name
# return 'win3_' + name # + '_knp'
return 'gtrwx_' + name
def gen_metadata(spec, orig_file):
return
def fixklik(spec):
klikfiles = {'exe': None, 'gam': None, 'img': None}
for line in sh.unzip('-l', spec['upload']).splitlines():
match = re.match(r"\s*\d+\s+\S+\s\S+\s+(.+)", line)
file = match.group(1) if match else ''
for ext in klikfiles.iterkeys():
if file.lower().endswith('.' + ext):
if klikfiles[ext]:
return
klikfiles[ext] = file
for fn in klikfiles.itervalues():
if not fn or not klikfiles['exe'] or fn[:-3] != klikfiles['exe'][:-3]:
return
exe = klikfiles['exe']
if '/' in exe:
unzipdir = os.path.join(DBDIR, 'tmp')
if os.path.exists(unzipdir):
sh.rm('-r', unzipdir)
sh.mkdir('-p', unzipdir)
dirOld = os.getcwd()
zipfile = os.path.abspath(spec['upload'])
os.chdir(unzipdir)
sh.unzip('-q', zipfile)
print exe, exe[:exe.rfind('/')], zipfile
zipdir(exe[:exe.rfind('/')], zipfile)
exe = exe[exe.rfind('/') + 1:]
spec['upload'] = zipfile
os.chdir(dirOld)
spec['emulator_start'] = 'd:\\runapp ' + exe
spec['dosbox_drive_d'] = 'emularity_win31/win31.zip'
def gen_metadata_json(spec, orig_file):
spec['subject'] = 'klik & play;Geocities'
print orig_file, os.path.split(orig_file)[1]
try:
gamesjson_path = os.path.join(os.path.split(orig_file)[0], "games.json")
print gamesjson_path
with open(gamesjson_path, 'rt') as f:
meta = json.load(f)
print meta
if os.path.split(orig_file)[1] in meta:
vals = meta[os.path.split(orig_file)[1]]
if len(vals) >= 1:
spec['title'] = vals[0]
if len(vals) >= 2:
spec['description'] = vals[1]
if len(vals) >= 3:
spec['creator'] = vals[2]
site = None
if '_AUTHOR' in meta:
if 'creator' not in spec:
spec['creator'] = meta['_AUTHOR']
site = meta['_AUTHOR']
if '_SITE' in meta:
site = meta['_SITE']
if '_URL' in meta:
link = '<a href="' + meta['_URL'] + '">'
if site:
link += site
else:
link += 'this Geocities site'
link += '</a>'
spec['description'] = (spec.get('description', '') + """
(Retrieved from """ + link + """.)""")
except:
pass
SPEC_DEFAULTS = {
'emulator': 'dosbox',
'emulator_ext': 'zip',
'collection': DEFAULT_COLLECTION,
'mediatype': 'software',
'dosbox_drive_d': 'emularity_win31/win31.zip'
}
def genid_piratekart(name, spec):
spec['creator'] = 'ShaperMC'
default_tags = ["glorious trainwrecks", "klik & play", "The 100-in-1 Klik & Play Pirate Kart"]
def matchheader(start, line):
match = re.match(start + ".*: *(.*)", line)
if match:
return match.group(1)
gametxt = patchstr(sh.unzip('-p', '-C', spec['upload'], 'game.txt', _encoding='cp437'))
for line in gametxt.split('\n'):
if matchheader("D", line):
spec['description'] = matchheader("D", line).strip()
elif matchheader("N", line):
spec['title'] = matchheader("N", line).strip()
elif matchheader("(?:G|Tag)", line):
tags = [tag.strip() for tag in matchheader("(?:G|Tag)", line).split(',')]
print tags
tags = tags + default_tags
spec['subject'] = ";".join(tags)
for tag in tags:
if tag.startswith("kotmk"):
name = tag + "_" + name
break
def loaddb():
with open(DB, 'rt') as f:
return json.load(f)
def savedb(db):
with open(DB, 'wt') as f:
json.dump(db, f)
def scan(db):
"""Finds everything I've uploaded and adds it to the DB."""
for result in internetarchive.search.Search('uploader:(jeremy@sporktania.com)', ['identifier', 'title', 'collection']):
id = result['identifier']
if id not in db:
db[id] = {'status': 'unknown'}
db[id]['title'] = result['title']
db[id]['collection'] = result['collection']
def diff(db, newdb_filename):
"""Returns a db with all of the items in newdb that don't exist in db."""
with open(newdb_filename, 'rt') as f:
newdb = json.load(f)
diffdb = {}
for id, val in newdb.iteritems():
if db.get(id) != val:
diffdb[id] = val
return diffdb
def check(db, collection=None, ignore=set(['fine', 'dark'])):
"""Prints items and their status, for reporting non-working items to Jason."""
status = {'reshoot': []}
for id, val in db.iteritems():
if collection != None and collection not in val.get('collection', []):
continue
if val['status'] not in status:
status[val['status']] = []
if val.get('reshoot'):
status['reshoot'].append(id)
status[val['status']].append(id)
for s, ids in status.iteritems():
if s not in ignore:
print "\n"
print s + ":"
for id in ids:
print "http://archive.org/details/" + id
def setstatus(db, id, status, title=None):
if id not in db:
db[id] = {}
db[id]['status'] = status
if title:
db[id]['title'] = title
savedb(db)
def patchstr(s):
"""Remove line-drawing (non-ascii) characters from a string"""
return "".join(ch for ch in unicode(s) if unicodedata.category(ch)[0]!="C" or ch == '\n')
def safefilename(s):
return re.sub('[^a-zA-Z0-9_-]', '_', s)
def fix(spec):
"""Tries to generate an ID and description"""
print "fix", spec
if 'upload' in spec:
fullname, ext = os.path.splitext(spec['upload'])
spec['upload'] = fullname + ext.lower()
name = safefilename(os.path.basename(fullname))
print name
if 'id' not in spec:
spec['id'] = genid(name, spec)
if 'description' not in spec:
try:
spec['description'] = patchstr(sh.unzip('-p', '-C', spec['upload'], 'FILE_ID.DIZ', _encoding='cp437'))
except sh.ErrorReturnCode:
pass
def defprompt(name):
def decorator(func):
func.name = name
return func
return decorator
def mvprompt(name):
@defprompt(name)
def mv(spec):
destpath = os.path.join(os.path.dirname(spec['upload']), name)
sh.mkdir('-p', destpath)
sh.mv(spec['upload'], destpath)
return mv
def dbinst(zip):
sh.rm('-r', DOSBOX_INSTALL)
sh.mkdir('-p', os.path.join(DOSBOX_INSTALL, 'INSTALL'))
sh.cp(zip, DOSBOX_INSTALL)
dirOld = os.getcwd()
os.chdir(os.path.join(DOSBOX_INSTALL, 'INSTALL'))
sh.unzip('-q', '../' + os.path.basename(zip))
sh.open('../..')
os.chdir(dirOld)
def dbinstexe(exe):
sh.rm('-r', DOSBOX_INSTALL)
sh.mkdir('-p', os.path.join(DOSBOX_INSTALL, 'INSTALL'))
sh.cp(exe, os.path.join(DOSBOX_INSTALL, 'INSTALL'))
sh.open(os.path.join(DOSBOX_INSTALL, '..'))
@defprompt('pkginst')
def pkginstprompt(spec):
if pkginst(spec):
return "sync" if prompt(spec) else "ignore"
return "ignore"
@defprompt('dbinst')
def dbinstprompt(spec):
dbinst(spec['upload'])
x = raw_input("Hit Enter when done (x to cancel): ")
if x == 'x':
return 'ignore'
return pkginstprompt(spec)
@defprompt('skip')
def skipprompt(spec):
pass
@defprompt('metadata')
def sync_only_metadata(spec):
del spec['upload']
sync(spec, ensure_new=False)
@defprompt('win31')
def win31prompt(spec):
spec['dosbox_drive_d'] = 'emularity_win31/win31.zip'
return 'ignore'
@defprompt('dos')
def dosprompt(spec):
spec['dosbox_drive_d'] = None
return 'ignore'
@defprompt('maxcpu')
def maxcpuprompt(spec):
unzipdir = os.path.join(DBDIR, 'tmp')
if os.path.exists(unzipdir):
sh.rm('-r', unzipdir)
sh.mkdir('-p', unzipdir)
dirOld = os.getcwd()
zipfile = os.path.abspath(spec['upload'])
os.chdir(unzipdir)
sh.unzip('-q', zipfile)
sh.cp(os.path.join(DBDIR, 'maxcpu.dosbox.conf'), 'dosbox.conf')
zipdir('.', zipfile)
os.chdir(dirOld)
return 'ignore'
def markprompt(db, status):
@defprompt(status)
def mark(spec):
setstatus(db, spec['id'], status, spec.get('title'))
return mark
def run_prompts(input, spec, prompts):
while True:
if prompts:
for k, v in prompts.iteritems():
print k + '=' + v.name,
print ''
result = raw_input(input)
if result in prompts:
result = prompts[result](spec)
if result != "ignore":
return (False, result)
else:
return (True, result)
def prompt(spec, prompts={}, prompt_title=True):
if 'upload' in spec and ('title' not in spec or 'emulator_start' not in spec):
print spec['id']
if 'description' in spec:
print spec['description']
try:
print sh.grep(sh.unzip('-l', spec['upload']), '-i', '.[bec][axo][tem]')
except sh.ErrorReturnCode:
print "Unexpected error:", sys.exc_info()[0]
print "no exe found, skipping"
return None
(isexe, exe) = run_prompts('EXE: ', spec, prompts)
if isexe:
if spec.get('dosbox_drive_d', SPEC_DEFAULTS.get('dosbox_drive_d')):
spec['emulator_start'] = 'd:\\runapp ' + exe
else:
spec['emulator_start'] = exe
if 'title' not in spec and prompt_title:
spec['title'] = raw_input('Title: ')
return True
elif exe == 'sync':
return True
if 'upload' in spec and 'emulator_start' in spec and 'title' in spec:
return True
def pkginst(spec, uploadPath=None, prompts={}):
print sh.ls('-al', DOSBOX_INSTALL)
(success, d) = run_prompts('Dir: ', spec, prompts)
if not success:
return d == 'sync'
dirToZip = os.path.join(DOSBOX_INSTALL, d)
if not os.path.exists(dirToZip):
return False
name = spec.get('id')
if not name:
name = raw_input('ID: ')
if not uploadPath:
uploadPath = os.path.join(DBDIR, name + '.zip')
zipdir(dirToZip, uploadPath)
spec['upload'] = uploadPath
return True
def zipdir(dirToZip, zipPath):
dirOld = os.getcwd()
zipPath = os.path.abspath(zipPath)
os.chdir(dirToZip)
if os.path.exists(zipPath):
sh.rm(zipPath)
sh.zip('-r', zipPath, sh.glob('./*'))
os.chdir(dirOld)
def sync(spec, db=None, ensure_new=True):
print json.dumps(spec)
item = internetarchive.get_item(spec['id'])
if ensure_new and item.exists:
raise Exception("Item " + spec['id'] + " already exists!")
mdOld = item.metadata.get('metadata', {})
mdNew = {}
for key in ['title', 'description', 'emulator_start', 'emulator', 'emulator_ext', 'dosbox_drive_d', 'subject', 'creator']:
if key in spec and (key not in mdOld or mdOld[key] != spec[key]):
mdNew[key] = spec[key]
for key, value in SPEC_DEFAULTS.iteritems():
if key not in mdOld and key not in spec:
mdNew[key] = value
try:
if 'upload' in spec:
print "uploading", spec['upload'], "to", spec['id']
item.upload(spec['upload'], metadata=mdNew)
if mdNew and mdOld:
print "updating metadata for", spec['id'], mdNew
item.modify_metadata(mdNew)
except requests.exceptions.HTTPError as e:
print e
print e.response
raise
if db:
setstatus(db, spec['id'], 'unknown', spec['title'])
def fixprompt(spec, extraprompts={}):
prompts = {'s': skipprompt, 'i': dbinstprompt, 'p': pkginstprompt, 'z': sync_only_metadata, 'd': dosprompt}#, 'w': win31prompt}
prompts.update(extraprompts)
fix(spec)
return prompt(spec, prompts)
def fixpromptsync(spec, db=None, extraprompts={}):
if fixprompt(spec, extraprompts):
sync(spec, db)
return True
def reinstall(id, db):
# download the zip
item = internetarchive.get_item(id)
zipfile = item.get_files(formats='ZIP')[0]
zipfilename = os.path.join(DBDIR, zipfile.name)
if os.path.exists(zipfilename):
os.unlink(zipfilename)
print id, db.get(id, {}).get('title')
print "downloading", zipfile.name
zipfile.download(zipfilename)
# install and reupload
dbinst(zipfilename)
raw_input('Hit enter when finished:')
spec = {'id': id}
@defprompt('exe')
def exeprompt(spec):
spec['upload'] = zipfilename
prompt(spec, prompt_title=False)
del spec['upload']
sync(spec, db)
prompts = {'w': markprompt(db, 'windows'),
'i': markprompt(db, 'inappropriate'),
'b': markprompt(db, 'busted'),
'e': exeprompt}
if pkginst(spec, zipfilename, prompts):
prompt(spec, prompt_title=False, prompts={'m': maxcpuprompt})
sync(spec)
setstatus(db, id, 'unknown')
savedb(db)
def installers(db):
for id, val in db.iteritems():
if val.get('status') == 'installer':
reinstall(id, db)
def batchfix(db):
for id, val in db.iteritems():
if val['status'] == 'sync':
sync({'id': id, 'emulator': 'dosbox-sync'})
setstatus(db, id, 'unknown')
db[id]['reshoot'] = True
savedb(db)
def updatestatus(db, statusfrom, statusto):
for id, val in db.iteritems():
if val['status'] == statusfrom:
val['status'] = statusto
savedb(db)
def clearreshoot(db):
for id, val in db.iteritems():
if val.get('reshoot'):
del val['reshoot']
savedb(db)
def iterfiles(ext, dir='.'):
return [fn for fn in os.listdir(dir) if re.search(r'\.' + ext + r'$', fn, re.IGNORECASE)]
def iterzipfiles(dir='.'):
return iterfiles('zip')
def zips(db, prefix, ext='zip'):
prompts = {
'u': mvprompt('uploaded'),
'm': mvprompt('multi'),
'n': mvprompt('notapprop'),
'd': mvprompt('dup'),
'z': sync_only_metadata
}
for zipfile in iterfiles(ext):
spec = {'upload': zipfile}
gen_metadata(spec, zipfile)
filename = os.path.basename(zipfile)
description = None
# description = patchstr(sh.sed(sh.grep('-i', '^' + filename, 'FILES1.BBS'), '-e', 's/^' + filename + r'[^ ]* *//'))
print description
if fixprompt(spec, extraprompts=prompts):
spec['title'] = prefix + spec['title']
if not spec.get('description'):
spec['description'] = description
print spec['description']
sync(spec, db)
mvprompt('uploaded')(spec)
def exes(db, prefix):
prompts = {
'u': mvprompt('uploaded'),
'm': mvprompt('multi'),
'n': mvprompt('notapprop'),
'd': mvprompt('dup')
}
for exefile in iterfiles('exe'):
dbinstexe(exefile)
x = raw_input("Hit Enter when done (x to cancel): ")
if x == 'x':
continue
spec = {'upload': exefile}
fix(spec)
if pkginst(spec, prompts=prompts):
if prompt(spec):
sync(spec, db)
mvprompt('uploaded')(spec)
def serve(db):
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
ctx = {'db': db}
def gen_tmpl(page):
def from_tmpl():
return render_template(page + '.html', **ctx)
from_tmpl.__name__ = "from_tmpl_" + page
app.route('/' + page)(from_tmpl)
for page in ['check']:
gen_tmpl(page)
@app.route('/db', methods=['PUT'])
def putdb():
if request.json:
ctx['db'] = request.json
savedb(ctx['db'])
return "OK cool"
@app.route('/', defaults={'filename': 'index.html'})
@app.route('/static/<filename>')
def staticfile(filename):
return send_from_directory(os.path.join(DBDIR, 'static'), filename)
@app.route('/screenshot', methods=['POST'])
def postscreenshot():
dataUris = request.form.getlist('image')
url = request.form['url']
name = re.match('.*/([^/]+)/?$', url).group(1)
dirname = os.path.join(DBDIR, name)
if len(dataUris) > 1:
dirindex = 1
while os.path.exists(os.path.join(dirname, 'animation' + str(dirindex))):
dirindex += 1
dirname = os.path.join(dirname, 'animation' + str(dirindex))
if not os.path.exists(dirname):
os.makedirs(dirname)
print dirname, len(dataUris)
filenames = []
index = 1
for dataUri in dataUris:
data = a2b_base64(dataUri[dataUri.index(',')+1:])
while os.path.exists(os.path.join(dirname, 'screen' + str(index) + '.png')):
index += 1
filename = os.path.join(dirname, 'screen' + str(index) + '.png')
with open(filename, 'wb') as f:
f.write(data)
filenames.append(filename)
if len(filenames) > 1:
# generate an animated giiiffff
gifname = dirname + '.gif'
filenames.append(gifname)
sh.gm('convert', '-delay', 5, '-loop', 0, *filenames)
sh.gifsicle('--batch', '-O3', gifname)
return "yup good"
import ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(os.path.join(DBDIR, 'server.crt'), os.path.join(DBDIR, 'server.key'))
app.run(ssl_context=context)
def join_elements(elements):
text = ''
for element in elements:
textAdd = ''
if element.name == 'p':
if text:
textAdd = '\n\n'
textAdd += join_elements(element.contents)
elif element.name == 'br':
textAdd = ''
elif unicode(element).strip():
textAdd = unicode(element)
if textAdd:
text += textAdd
return text
def scrapefile(project, url, spec):
fn = os.path.join(DBDIR, project, url.split('/')[-1])
if not os.path.exists(fn):
dirn = os.path.split(fn)[0]
if not os.path.exists(dirn):
os.makedirs(dirn)
urlretrieve(url, fn)
if not spec:
spec = {}
spec['upload'] = fn
return spec
def scrapegame(url, spec=None):
soup = BeautifulSoup(requests.get(url).text)
content = soup.find('div', 'node').find('div', 'content')
descElems = [e for e in content.contents if e.name != 'div' and not (e.name == 'table' and e['id'] == 'attachments')]
desc = join_elements(descElems)
desc += '\n\n(Retrieved from <a href="' + url + '">Glorious Trainwrecks</a>.)'
title = soup.find('div', id='center').h2.contents[0]
if content.find('div', 'field-field-gamefile'):
dlurl = content.find('div', 'field-field-gamefile').a['href']
else:
for attachment_link in content.find('table', id="attachments").find_all("a"):
dlurl = attachment_link['href']
if dlurl.lower().endswith('.zip'):
break
user = soup.find('span', 'submitted').a.contents[0]
tags = 'glorious trainwrecks;Klik & Play'
legacy_event = content.find('div', 'field-field-field-event')
if legacy_event and legacy_event.find('div', 'field-item').contents[0] == 'Pirate Kart 2':
tags += ";The 529 in 1 Klik and Play Pirate Kart Part II: Klik Harder"
event = content.find('div', 'field-field-event-created-for')
if event and not event.a.contents[0].startswith("THE 371-"):
tags += ';' + event.a.contents[0]
if soup.find('div', 'terms'):
for taglink in soup.find('div', 'terms').find_all('a'):
tags += ';' + taglink.contents[0]
spec = scrapefile('gtrwx', dlurl, spec)
spec.update({
'title': title,
'creator': user,
'description': desc,
'subject': tags
})
return spec
def scrapecomment(url, spec=None):
comment_id = url.split("#")[1]
soup = BeautifulSoup(requests.get(url).text)
comment = soup.find('a', id=comment_id).find_next_sibling('div', 'comment')
user = comment.find('span', 'submitted').a.contents[0]
title = comment.h3.a.contents[0]
event_title = soup.find('div', id='center').h2.contents[0]
content = comment.find('div', 'content')
desc = join_elements([e for e in content.contents if not (e.name == 'table' and e['id'] == 'attachments')])
desc += '\n\n(Retrieved from <a href="' + url + '">Glorious Trainwrecks</a>.)'
attachments = [a['href'] for a in content.find_all('a') if a['href'].split('.')[-1].lower() not in ['png', 'jpg']]
if len(attachments) == 0:
return None
if len(attachments) > 1:
iattach = 1
for attachment in attachments:
print iattach, attachment
iattach += 1
iattach = raw_input('Which #: ')
try:
iattach = int(iattach)
attachment = attachments[iattach - 1]
except:
return None
else:
attachment = attachments[0]
spec = scrapefile('gtrwx', urljoin(url, attachment), spec)
spec.update({
'title': title,
'creator': user,
'description': desc,
'subject': 'glorious trainwrecks;Klik & Play;' + event_title
})
return spec
def scrapeuser(username):
games = []
url = 'http://www.glorioustrainwrecks.com/games/*/' + username
while url:
soup = BeautifulSoup(requests.get(url).text)
for td in soup.find_all('td', 'view-field-node-title'):
games.append((urljoin(url, td.a['href']), td.a.contents[0]))
nextLink = soup.find(title='Go to next page')
if nextLink:
url = urljoin(url, nextLink['href'])
else:
url = None
return games
def runcmd(db, cmd, args):
if cmd == 'scan':
scan(db)
savedb(db)
elif cmd == 'check':
check(db)
elif cmd == 'diff':
diffdb = diff(db, args[0])
runcmd(diffdb, args[1], args[2:])
savedb(db)
elif cmd == 'mergedb':
with open(args[0], 'rt') as f:
dbnew = json.load(f)
db.update(dbnew)
savedb(db)
elif cmd == 'dumpdb':
print json.dumps(db)
elif cmd == 'db':
global DB
db_old = DB
DB = os.path.abspath(args[0])
runcmd(loaddb(), args[1], args[2:])
DB = db_old
elif cmd == 'sync':
sync(json.loads(args[0]))
elif cmd == 'pkginst':
spec = {}
pkginst(spec)
fixpromptsync(spec, db)
elif cmd == 'zip':
spec = {'upload': args[0]}
gen_metadata(spec, args[0])
fixpromptsync(spec, db)
elif cmd == 'gtgame':
if '#' in args[0]:
spec = scrapecomment(args[0])
else:
spec = scrapegame(args[0])
fixklik(spec)
fixpromptsync(spec, db)
elif cmd == 'gtuser':
games = scrapeuser(args[0])
for url, title in games:
spec = scrapegame(url)
if spec:
fixklik(spec)
fixpromptsync(spec, db)
elif cmd == 'zips':
prefix = args[0] + ": " if len(args) > 0 else ""
zips(db, prefix)
elif cmd == 'exezips':
prefix = args[0] + ": " if len(args) > 0 else ""
zips(db, prefix, 'exe')
elif cmd == 'exes':
prefix = args[0] + ": " if len(args) > 0 else ""
exes(db, prefix)
elif cmd == 'dir':
d = args[0]
if d.endswith('/'):
d = d[:-1]
zipf = os.path.join(DBDIR, safefilename(os.path.basename(d)) + '.zip')
zipdir(d, zipf)
spec = {'upload': zipf}
gen_metadata(spec, d)
if len(args) > 1:
spec['id'] = args[1]
fixpromptsync(spec, db)
elif cmd == 'dirs':
for d in os.listdir('.'):
if os.path.isdir(d):
zipf = os.path.join(DBDIR, safefilename(os.path.basename(d)) + '.zip')
zipdir(d, zipf)
spec = {'upload': zipf}
gen_metadata(spec, d)
fixpromptsync(spec, db)
elif cmd == 'installers':
try:
installers(db)
except (KeyboardInterrupt, SystemExit):
pass
savedb(db)
elif cmd == 'batchfix':
batchfix(db)
elif cmd == 'setstatus':
updatestatus(db, args[0], args[1])
elif cmd == 'clearreshoot':
clearreshoot(db)
elif cmd == 'fixext':
for id, val in db.iteritems():
if val.get('status') == 'unknown':
item = internetarchive.get_item(id)
zipfile = item.get_files(formats='ZIP')[0]
md = item.metadata.get('metadata', {})
print "checking", id, zipfile.name, md.get('emulator_ext')
if os.path.splitext(zipfile.name)[1] == '.ZIP' and md.get('emulator_ext') == 'zip':
print "fixing", id
spec = {'id': id, 'emulator_ext': 'ZIP'}
sync(spec)
elif cmd == 'reinstall':
reinstall(args[0], db)
savedb(db)
elif cmd == 'serve':
serve(db)
if __name__ == '__main__':
runcmd(loaddb(), sys.argv[1], sys.argv[2:])

BIN
testimg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

BIN
testimg2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
testimg3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB