Scanning + computer vision experiments
This commit is contained in:
parent
a595e437b6
commit
6796588213
176
cvtest.py
Normal file
176
cvtest.py
Normal 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
160
poetry.lock
generated
|
@ -31,6 +31,14 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
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]]
|
||||
category = "dev"
|
||||
description = "The uncompromising code formatter."
|
||||
|
@ -74,6 +82,17 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
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]]
|
||||
category = "dev"
|
||||
description = "Better living through Python with decorators"
|
||||
|
@ -124,6 +143,14 @@ version = "0.23"
|
|||
[package.dependencies]
|
||||
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]]
|
||||
category = "dev"
|
||||
description = "IPython Kernel for Jupyter"
|
||||
|
@ -143,20 +170,20 @@ category = "dev"
|
|||
description = "IPython: Productive Interactive Computing"
|
||||
name = "ipython"
|
||||
optional = false
|
||||
python-versions = ">=3.3"
|
||||
version = "6.2.1"
|
||||
python-versions = ">=3.5"
|
||||
version = "7.8.0"
|
||||
|
||||
[package.dependencies]
|
||||
appnope = "*"
|
||||
backcall = "*"
|
||||
colorama = "*"
|
||||
decorator = "*"
|
||||
jedi = ">=0.10"
|
||||
pexpect = "*"
|
||||
pickleshare = "*"
|
||||
prompt-toolkit = ">=1.0.4,<2.0.0"
|
||||
prompt-toolkit = ">=2.0.0,<2.1.0"
|
||||
pygments = "*"
|
||||
setuptools = ">=18.5"
|
||||
simplegeneric = ">0.8"
|
||||
traitlets = ">=4.2"
|
||||
|
||||
[[package]]
|
||||
|
@ -258,14 +285,14 @@ category = "dev"
|
|||
description = "Jupyter terminal console"
|
||||
name = "jupyter-console"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "5.2.0"
|
||||
python-versions = ">=3.5"
|
||||
version = "6.0.0"
|
||||
|
||||
[package.dependencies]
|
||||
ipykernel = "*"
|
||||
ipython = "*"
|
||||
jupyter-client = "*"
|
||||
prompt-toolkit = ">=1.0.0,<2.0.0"
|
||||
prompt-toolkit = ">=2.0.0,<2.1.0"
|
||||
pygments = "*"
|
||||
|
||||
[[package]]
|
||||
|
@ -279,6 +306,17 @@ version = "4.5.0"
|
|||
[package.dependencies]
|
||||
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]]
|
||||
category = "dev"
|
||||
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.*"
|
||||
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]]
|
||||
category = "dev"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
|
@ -369,6 +422,25 @@ terminado = ">=0.8.1"
|
|||
tornado = ">=5.0"
|
||||
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]]
|
||||
category = "dev"
|
||||
description = "Utilities for writing pandoc filters in python"
|
||||
|
@ -435,12 +507,12 @@ python-versions = "*"
|
|||
version = "0.7.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
category = "dev"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
name = "prompt-toolkit"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.0.14"
|
||||
version = "2.0.9"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.9.0"
|
||||
|
@ -480,7 +552,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||
version = "2.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
category = "dev"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
name = "pygments"
|
||||
optional = false
|
||||
|
@ -489,28 +561,23 @@ version = "2.4.2"
|
|||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A Python module for collection of common interactive command line user interfaces, based on Inquirer.js"
|
||||
name = "pyinquirer"
|
||||
description = "Python library to access and use image scanners (Linux/Windows/etc)"
|
||||
name = "pyinsane2"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.0.3"
|
||||
|
||||
[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"
|
||||
version = "2.0.13"
|
||||
|
||||
[package.dependencies]
|
||||
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]]
|
||||
category = "dev"
|
||||
description = "Persistent/Functional/Immutable data structures"
|
||||
|
@ -541,7 +608,7 @@ setuptools = "*"
|
|||
six = ">=1.10.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
category = "main"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
name = "python-dateutil"
|
||||
optional = false
|
||||
|
@ -593,14 +660,6 @@ jupyter-core = "*"
|
|||
pygments = "*"
|
||||
traitlets = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
name = "regex"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2019.08.19"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Send file to trash natively under Mac OS X, Windows and Linux."
|
||||
|
@ -609,14 +668,6 @@ optional = false
|
|||
python-versions = "*"
|
||||
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]]
|
||||
category = "main"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
|
@ -676,7 +727,7 @@ ipython-genutils = "*"
|
|||
six = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
category = "dev"
|
||||
description = "Measures number of Terminal column cells of wide-character codes"
|
||||
name = "wcwidth"
|
||||
optional = false
|
||||
|
@ -715,7 +766,7 @@ version = "0.6.0"
|
|||
more-itertools = "*"
|
||||
|
||||
[metadata]
|
||||
content-hash = "a0b14789772e6aef430877c07f16231d14758c6a230276d81cb6466f3cd97c02"
|
||||
content-hash = "7e200ef69278ec9f82a2bb37e54d6966e2190cd269bb1d714987dc4db9ab94f5"
|
||||
python-versions = "^3.7"
|
||||
|
||||
[metadata.hashes]
|
||||
|
@ -723,17 +774,20 @@ appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "
|
|||
appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"]
|
||||
atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
|
||||
attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"]
|
||||
backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"]
|
||||
black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"]
|
||||
bleach = ["213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", "3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"]
|
||||
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
|
||||
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
|
||||
cycler = ["1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", "cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"]
|
||||
decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"]
|
||||
defusedxml = ["6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", "f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"]
|
||||
entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"]
|
||||
flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"]
|
||||
importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"]
|
||||
imutils = ["857af6169d90e4a0a814130b9b107f5d611150ce440107e1c1233521c6fb1e2b"]
|
||||
ipykernel = ["167c3ef08450f5e060b76c749905acb0e0fbef9365899377a4a1eae728864383", "b503913e0b4cce7ed2de965457dfb2edd633e8234161a60e23f2fe2161345d12"]
|
||||
ipython = ["51c158a6c8b899898d1c91c6b51a34110196815cc905f9be0fa5878e19355608", "fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3"]
|
||||
ipython = ["c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b", "dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1"]
|
||||
ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"]
|
||||
ipywidgets = ["13ffeca438e0c0f91ae583dc22f50379b9d6b28390ac7be8b757140e9a771516", "e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97"]
|
||||
jedi = ["786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"]
|
||||
|
@ -741,15 +795,19 @@ jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "1
|
|||
jsonschema = ["5f9c0a719ca2ce14c5de2fd350a64fd2d13e8539db29836a86adc990bb1a068f", "8d4a2b7b6c2237e0199c8ea1a6d3e05bf118e289ae2b9d7ba444182a2959560d"]
|
||||
jupyter = ["3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", "5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", "d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"]
|
||||
jupyter-client = ["6a6d415c62179728f6d9295b37356d8f6833e9e01c2b6e1901dc555571f57b21", "f406f214f9daa92be110d5b83d62f3451ffc73d3522db7350f0554683533ab18"]
|
||||
jupyter-console = ["3f928b817fc82cda95e431eb4c2b5eb21be5c483c2b43f424761a966bb808094", "545dedd3aaaa355148093c5609f0229aeb121b4852995c2accfa64fe3e0e55cd"]
|
||||
jupyter-console = ["308ce876354924fb6c540b41d5d6d08acfc946984bf0c97777c1ddcb42e0b2f5", "cc80a97a5c389cbd30252ffb5ce7cefd4b66bde98219edd16bf5cb6f84bb3568"]
|
||||
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"]
|
||||
matplotlib = ["1febd22afe1489b13c6749ea059d392c03261b2950d1d45c17e3aed812080c93", "31a30d03f39528c79f3a592857be62a08595dec4ac034978ecd0f814fa0eec2d", "4442ce720907f67a79d45de9ada47be81ce17e6c2f448b3c64765af93f6829c9", "796edbd1182cbffa7e1e7a97f1e141f875a8501ba8dd834269ae3cd45a8c976f", "934e6243df7165aad097572abf5b6003c77c9b6c480c3c4de6f2ef1b5fdd4ec0", "bab9d848dbf1517bc58d1f486772e99919b19efef5dd8596d4b26f9f5ee08b6b", "c1fe1e6cdaa53f11f088b7470c2056c0df7d80ee4858dadf6cbe433fcba4323b", "e5b8aeca9276a3a988caebe9f08366ed519fff98f77c6df5b64d7603d0e42e36", "ec6bd0a6a58df3628ff269978f4a4b924a0d371ad8ce1f8e2b635b99e482877a"]
|
||||
mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
|
||||
mistune = ["59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", "88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"]
|
||||
more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
|
||||
nbconvert = ["427a468ec26e7d68a529b95f578d5cbf018cb4c1f889e897681c2b6d11897695", "48d3c342057a2cf21e8df820d49ff27ab9f25fc72b8f15606bd47967333b2709"]
|
||||
nbformat = ["b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", "f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402"]
|
||||
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"]
|
||||
parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"]
|
||||
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"]
|
||||
pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"]
|
||||
prometheus-client = ["71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da"]
|
||||
prompt-toolkit = ["7281b5199235adaef6980942840c43753e4ab20dfe41338da634fb41c194f9d8", "82c7f8e07d7a0411ff5367a5a8ff520f0112b9179f3e599ee8ad2ad9b943d911", "cc66413b1b4b17021675d9f2d15d57e640b06ddfd99bb724c73484126d22622f"]
|
||||
prompt-toolkit = ["11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", "2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", "977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"]
|
||||
ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"]
|
||||
py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
|
||||
pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"]
|
||||
pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"]
|
||||
pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"]
|
||||
pyinquirer = ["c9a92d68d7727fbd886a7908c08fd9e9773e5dc211bf5cbf836ba90d366dee51"]
|
||||
pyinsane = ["11df7b8abc0875a00cf2257b736fcf7b7913bd93e0428bf5b7decb9f143df74c"]
|
||||
pyinsane2 = ["0d519531d552e4512776225eb400a6a4a9bfc83a08918ec7fea19cb2fa7ec4ee", "a172fc2947ce9d3ed0b3f3b204b5e2fd57658f672d346c54f7f29d727fe56d0c", "df3ffdb2abd52fb4b5ca1f47df8c39a995ce04bf1f6f09250cb9e77cb0dddfb8"]
|
||||
pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"]
|
||||
pyrsistent = ["34b47fa169d6006b32e99d4b3c4031f155e6e68ebcc107d6454852e8e0ee6533"]
|
||||
pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"]
|
||||
python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"]
|
||||
|
@ -772,9 +830,7 @@ pywin32 = ["0443e9bb196e72480f50cbddc2cf98fbb858a77d02e281ba79489ea3287b36e9", "
|
|||
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"]
|
||||
qtconsole = ["40d5d8e00d070ea266dbf6f0da74c4b9597b8b8d67cd8233c3ffd8debf923703", "b91e7412587e6cfe1644696538f73baf5611e837be5406633218443b2827c6d9"]
|
||||
regex = ["1e9f9bc44ca195baf0040b1938e6801d2f3409661c15fe57f8164c678cfc663f", "587b62d48ca359d2d4f02d486f1f0aa9a20fbaf23a9d4198c4bed72ab2f6c849", "835ccdcdc612821edf132c20aef3eaaecfb884c9454fdc480d5887562594ac61", "93f6c9da57e704e128d90736430c5c59dd733327882b371b0cae8833106c2a21", "a46f27d267665016acb3ec8c6046ec5eae8cf80befe85ba47f43c6f5ec636dcd", "c5c8999b3a341b21ac2c6ec704cfcccbc50f1fedd61b6a8ee915ca7fd4b0a557", "d4d1829cf97632673aa49f378b0a2c3925acd795148c5ace8ef854217abbee89", "d96479257e8e4d1d7800adb26bf9c5ca5bab1648a1eddcac84d107b73dc68327", "f20f4912daf443220436759858f96fefbfc6c6ba9e67835fd6e4e9b73582791a", "f2b37b5b2c2a9d56d9e88efef200ec09c36c7f323f9d58d0b985a90923df386d", "fe765b809a1f7ce642c2edeee351e7ebd84391640031ba4b60af8d91a9045890"]
|
||||
send2trash = ["60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2", "f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"]
|
||||
simplegeneric = ["dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"]
|
||||
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
|
||||
terminado = ["d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460", "de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2"]
|
||||
testpath = ["46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109", "b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8"]
|
||||
|
|
|
@ -6,8 +6,10 @@ authors = ["Jeremy Penner <jeremy@sporktania.com>"]
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
PyInquirer = "^1.0"
|
||||
pyinsane = "^1.4"
|
||||
pyinsane2 = "^2.0"
|
||||
opencv-python = "^4.1"
|
||||
imutils = "^0.5.3"
|
||||
matplotlib = "^3.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^3.0"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
__version__ = "0.1.0"
|
||||
|
||||
from cuebin import CueBin
|
||||
# from cuebin import CueBin
|
||||
|
||||
# from file import restore, backup
|
||||
import glob
|
||||
# import glob
|
||||
|
||||
for cuefile in glob.glob("D:\\ROMs\\PC\\*.cue"):
|
||||
CueBin(cuefile).fixAll()
|
||||
# restore(cuefile)
|
||||
# for cuefile in glob.glob("D:\\ROMs\\PC\\*.cue"):
|
||||
# CueBin(cuefile).fixAll()
|
||||
# restore(cuefile)
|
||||
|
|
|
@ -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
20
romtool/scan.py
Normal 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
837
sw
Normal 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
BIN
testimg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 MiB |
BIN
testimg2.jpg
Normal file
BIN
testimg2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 MiB |
BIN
testimg3.jpg
Normal file
BIN
testimg3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 MiB |
Loading…
Reference in a new issue