diff --git a/gridsplitter/slice.py b/gridsplitter/slice.py index 24175d7..50bed11 100644 --- a/gridsplitter/slice.py +++ b/gridsplitter/slice.py @@ -1,23 +1,23 @@ #!/usr/bin/env python # Copyright (c) 2012 tyrok1 -# +# # This software is provided 'as-is', without any express or implied warranty. In # no event will the authors be held liable for any damages arising from the use # of this software. -# +# # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it freely, # subject to the following restrictions: -# +# # 1. The origin of this software must not be misrepresented; you must not claim # that you wrote the original software. If you use this software in a product, # an acknowledgment in the product documentation would be appreciated but is not # required. -# +# # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. -# +# # 3. This notice may not be removed or altered from any source distribution. # The license template for this license is available at: @@ -27,8 +27,6 @@ from PIL import Image -from PIL import ImageChops -from PIL import ImageFilter from PIL import ImageDraw from PIL import ImageOps from math import hypot @@ -49,40 +47,44 @@ # Each square is saved as this size outputSize = (320, 207) + # Open the source file and pick out the color to search for def GetSource(filename): original = Image.open(filename) original = ImageOps.exif_transpose(original) return original + # Gets a luminance channel representing the similarity to the desired color def GetColor(im, channel): channels = [-0.5, -0.5, -0.5, 0] channels[int(channel)] = 1 return ImageOps.autocontrast(im.convert("L", channels)) + def FindMatchingPixels(im): # Find coordinates of pixels which match the desired color above a certain threshold intColorThreshold = int(colorThreshold / 100.0 * 255.0) matchingPixels = [] for x in range(0, 159): for y in range(0, 159): - if(im.getpixel((x,y)) > intColorThreshold): - #found one + if im.getpixel((x, y)) > intColorThreshold: + # found one matchingPixels.append((x, y)) - + return matchingPixels + # Find the dots in an image # Returned values are each [strength, centerX, centerY] def FindDots(im): # Search parameters maxDotSize = 10 - + # Find all matching pixels to search matchingPixels = FindMatchingPixels(im) dots = [] - + # For each matching pixel... for m in matchingPixels: # Try and find a dot which is close to it @@ -91,9 +93,9 @@ def FindDots(im): # Calculate distance from the dot's center vec = (m[0] - d[1], m[1] - d[2]) dist = hypot(vec[0], vec[1]) - + # If it's close enough... - if(dist < maxDotSize): + if dist < maxDotSize: # Recalculate the average center of the dot d[0] = d[0] + 1 amount = 1.0 / d[0] @@ -101,51 +103,58 @@ def FindDots(im): d[2] = d[2] * (1.0 - amount) + (m[1] + 0.5) * amount found = True break - + # If no close dot was found, start a new one - if(not found): + if not found: dots.append([1, m[0], m[1]]) - + return dots + # Transform dots back into source image space def TransformDots(dots, searchImage, originalImage): # Transforms a single dot def TransformDot(dot): - return (int(dot[1] * originalImage.size[0] / searchImage.size[0]), int(dot[2] * originalImage.size[1] / searchImage.size[1])) - + return ( + int(dot[1] * originalImage.size[0] / searchImage.size[0]), + int(dot[2] * originalImage.size[1] / searchImage.size[1]), + ) + return [TransformDot(dot) for dot in dots] + # Find a box given a top-left point def FindQuad(dots, tlDotIndex): # Search parameters angles = [pi / 2, pi / 4, 0] tolerance = [0.25, 0.5, 0.25] minDists = [2, 10, 2] - + return FindDotsInRanges(dots, tlDotIndex, angles, tolerance, minDists) + # Find dots in a line -def FindDotsInLine(dots, tlDotIndex, horizontal = None): +def FindDotsInLine(dots, tlDotIndex, horizontal=None): angles = [pi / 2] tolerance = [0.25] minDists = [2] - if(horizontal): + if horizontal: angles = [0] - + points = [tlDotIndex] point = None lastPoint = tlDotIndex while True: point = FindDotsInRanges(dots, lastPoint, angles, tolerance, minDists) - if(point is None or len(point) < 2 or point[1] is None): + if point is None or len(point) < 2 or point[1] is None: break - + points.append(point[1]) lastPoint = point[1] - + return points + # Find dots within specified angle ranges def FindDotsInRanges(dots, tlDotIndex, angles, tolerance, minDists): nearDists = [] @@ -159,175 +168,244 @@ def FindDotsInRanges(dots, tlDotIndex, angles, tolerance, minDists): # Calculate distance from this dot to the top left vec = (dot[0] - dots[tlDotIndex][0], dot[1] - dots[tlDotIndex][1]) dist = hypot(vec[0], vec[1]) - + for p in range(0, len(angleRanges)): # Initialize nearDists if they aren't already - if(len(nearDists) <= p): + if len(nearDists) <= p: nearDists.append(10000) - + # Initialize output points if they aren't already - if(len(outPoints) <= p + 1): + if len(outPoints) <= p + 1: outPoints.append(None) - - if(minDists[p] <= dist <= nearDists[p]): + + if minDists[p] <= dist <= nearDists[p]: angle = atan2(vec[1], vec[0]) - if(angleRanges[p][0] <= angle <= angleRanges[p][1]): - if(p != 1 or (vec[0] > 0 and vec[1] > 0)): + if angleRanges[p][0] <= angle <= angleRanges[p][1]: + if p != 1 or (vec[0] > 0 and vec[1] > 0): outPoints[p + 1] = dotIndex nearDists[p] = dist - + return outPoints + # Finds and slices squares from an image using a dot pattern -def SliceSquares(imageOriginal, channel, drawDebuggingGrid = None): +def SliceSquares(imageOriginal, channel, drawDebuggingGrid=None): # Finds a row of images from a top-left corner def FindRow(dots, tlDotIndex, rowNum): # Find the first quad quadIndices = FindQuad(dots, tlDotIndex) - + # Keep track of the bottom-left point of the first quad so we can return it later blPoint = None - if(len(quadIndices) > 1 and quadIndices[1] != None): + if len(quadIndices) > 1 and quadIndices[1] is not None: blPoint = quadIndices[1] - + # Start looking for more of 'em colNum = 0 squares = [] - while(len(quadIndices) > 3 and quadIndices[0] is not None and quadIndices[1] is not None and quadIndices[2] is not None and quadIndices[3] is not None): + while ( + len(quadIndices) > 3 + and quadIndices[0] is not None + and quadIndices[1] is not None + and quadIndices[2] is not None + and quadIndices[3] is not None + ): # Translate from indices into coordinates quad = [dots[index] for index in quadIndices] - + # Found a box - crop it out of the original - squares.append(ImageOps.autocontrast(imageOriginal.transform(outputSize, Image.QUAD, (quad[0][0], quad[0][1], quad[1][0], quad[1][1], quad[2][0], quad[2][1], quad[3][0], quad[3][1]), Image.BICUBIC), 2)) + squares.append( + ImageOps.autocontrast( + imageOriginal.transform( + outputSize, + Image.QUAD, + ( + quad[0][0], + quad[0][1], + quad[1][0], + quad[1][1], + quad[2][0], + quad[2][1], + quad[3][0], + quad[3][1], + ), + Image.BICUBIC, + ), + 2, + ) + ) colNum = colNum + 1 - + # See if we're drawing an output grid for debugging - if(drawDebuggingGrid): + if drawDebuggingGrid: for p in range(0, 3): - drawDebuggingGrid.line([(quad[p][0], quad[p][1]), (quad[p + 1][0], quad[p + 1][1])], fill=(0, 64 + (p * 48), 0), width=3) + drawDebuggingGrid.line( + [(quad[p][0], quad[p][1]), (quad[p + 1][0], quad[p + 1][1])], + fill=(0, 64 + (p * 48), 0), + width=3, + ) drawDebuggingGrid.line([(quad[3][0], quad[3][1]), (quad[0][0], quad[0][1])], fill=(0, 255, 0), width=3) - + # Continue on to the next quad in the line quadIndices = FindQuad(dots, quadIndices[3]) - + # Return both the first point's bottom-left corner and the squares we found return (blPoint, squares) - + # Get an image containing brighter areas that match the desired color imageLuminance = GetColor(imageOriginal, channel) - + # Downsize the search space to speed things up and make the search # more accurate imageSearch = imageLuminance.resize(searchSize, Image.LANCZOS) - + # Find all of the dots in the image dots = TransformDots(FindDots(imageSearch), imageSearch, imageOriginal) - if(drawDebuggingGrid): + if drawDebuggingGrid: for d in dots: drawDebuggingGrid.ellipse((d[0] - 10, d[1] - 10, d[0] + 10, d[1] + 10), fill="red") - + # If we have at least one dot... squares = [] - if(len(dots) > 0): + if len(dots) > 0: # Find the top-left dot topLeft = 0 topLeftDist = hypot(dots[0][0], dots[0][1]) for d in range(0, len(dots)): curDist = hypot(dots[d][0], dots[d][1]) - if(curDist < topLeftDist): + if curDist < topLeftDist: topLeft = d topLeftDist = curDist - + # See what style of dot placement we have topColumnDots = FindDotsInLine(dots, topLeft, True) leftRowDots = FindDotsInLine(dots, topLeft, False) - if(len(dots) < len(topColumnDots) * len(leftRowDots)): + if len(dots) < len(topColumnDots) * len(leftRowDots): # Interpolate the dots in between # First, check if we have all edge dots bottomColumnDots = FindDotsInLine(dots, leftRowDots[len(leftRowDots) - 1], True) rightRowDots = FindDotsInLine(dots, topColumnDots[len(topColumnDots) - 1], False) - + # Find lengths of top, right, bottom, and left edges so we can properly weight averages - topVec = (dots[topColumnDots[len(topColumnDots) - 1]][0] - dots[topColumnDots[0]][0], dots[topColumnDots[len(topColumnDots) - 1]][1] - dots[topColumnDots[0]][1]) + topVec = ( + dots[topColumnDots[len(topColumnDots) - 1]][0] - dots[topColumnDots[0]][0], + dots[topColumnDots[len(topColumnDots) - 1]][1] - dots[topColumnDots[0]][1], + ) topFullLength = hypot(topVec[0], topVec[1]) - rightVec = (dots[rightRowDots[len(rightRowDots) - 1]][0] - dots[rightRowDots[0]][0], dots[rightRowDots[len(rightRowDots) - 1]][1] - dots[rightRowDots[0]][1]) + rightVec = ( + dots[rightRowDots[len(rightRowDots) - 1]][0] - dots[rightRowDots[0]][0], + dots[rightRowDots[len(rightRowDots) - 1]][1] - dots[rightRowDots[0]][1], + ) rightFullLength = hypot(rightVec[0], rightVec[1]) - bottomVec = (dots[bottomColumnDots[len(bottomColumnDots) - 1]][0] - dots[bottomColumnDots[0]][0], dots[bottomColumnDots[len(bottomColumnDots) - 1]][1] - dots[bottomColumnDots[0]][1]) + bottomVec = ( + dots[bottomColumnDots[len(bottomColumnDots) - 1]][0] - dots[bottomColumnDots[0]][0], + dots[bottomColumnDots[len(bottomColumnDots) - 1]][1] - dots[bottomColumnDots[0]][1], + ) bottomFullLength = hypot(bottomVec[0], bottomVec[1]) - leftVec = (dots[leftRowDots[len(leftRowDots) - 1]][0] - dots[leftRowDots[0]][0], dots[leftRowDots[len(leftRowDots) - 1]][1] - dots[leftRowDots[0]][1]) + leftVec = ( + dots[leftRowDots[len(leftRowDots) - 1]][0] - dots[leftRowDots[0]][0], + dots[leftRowDots[len(leftRowDots) - 1]][1] - dots[leftRowDots[0]][1], + ) leftFullLength = hypot(leftVec[0], leftVec[1]) - - if(len(topColumnDots) > len(bottomColumnDots)): + + if len(topColumnDots) > len(bottomColumnDots): # Not enough dots on the bottom edge for c in range(1, len(topColumnDots) - 1): - topLength = hypot(dots[topColumnDots[c]][0] - dots[topColumnDots[0]][0], dots[topColumnDots[c]][1] - dots[topColumnDots[0]][1]) + topLength = hypot( + dots[topColumnDots[c]][0] - dots[topColumnDots[0]][0], + dots[topColumnDots[c]][1] - dots[topColumnDots[0]][1], + ) topRatio = topLength / topFullLength - newDot = (dots[bottomColumnDots[0]][0] + bottomVec[0] * topRatio, dots[bottomColumnDots[0]][1] + bottomVec[1] * topRatio) + newDot = ( + dots[bottomColumnDots[0]][0] + bottomVec[0] * topRatio, + dots[bottomColumnDots[0]][1] + bottomVec[1] * topRatio, + ) dots.append(newDot) - if(drawDebuggingGrid): - drawDebuggingGrid.ellipse((newDot[0] - 10, newDot[1] - 10, newDot[0] + 10, newDot[1] + 10), fill="blue") - + if drawDebuggingGrid: + drawDebuggingGrid.ellipse( + (newDot[0] - 10, newDot[1] - 10, newDot[0] + 10, newDot[1] + 10), fill="blue" + ) + # Update the list of bottom edge dots bottomColumnDots = FindDotsInLine(dots, leftRowDots[len(leftRowDots) - 1], True) - - if(len(leftRowDots) > len(rightRowDots)): + + if len(leftRowDots) > len(rightRowDots): # Not enough dots on the right edge for r in range(1, len(leftRowDots) - 1): - leftLength = hypot(dots[leftRowDots[r]][0] - dots[leftRowDots[0]][0], dots[leftRowDots[r]][1] - dots[leftRowDots[0]][1]) + leftLength = hypot( + dots[leftRowDots[r]][0] - dots[leftRowDots[0]][0], + dots[leftRowDots[r]][1] - dots[leftRowDots[0]][1], + ) leftRatio = leftLength / leftFullLength - newDot = (dots[rightRowDots[0]][0] + rightVec[0] * leftRatio, dots[rightRowDots[0]][1] + rightVec[1] * leftRatio) + newDot = ( + dots[rightRowDots[0]][0] + rightVec[0] * leftRatio, + dots[rightRowDots[0]][1] + rightVec[1] * leftRatio, + ) dots.append(newDot) - if(drawDebuggingGrid): - drawDebuggingGrid.ellipse((newDot[0] - 10, newDot[1] - 10, newDot[0] + 10, newDot[1] + 10), fill="blue") - + if drawDebuggingGrid: + drawDebuggingGrid.ellipse( + (newDot[0] - 10, newDot[1] - 10, newDot[0] + 10, newDot[1] + 10), fill="blue" + ) + # Update the list of right edge dots rightRowDots = FindDotsInLine(dots, topColumnDots[len(topColumnDots) - 1], False) - + # Interpolate middle dots based on the edges for row, leftDot in enumerate(leftRowDots[1:-1]): - try:# Generate a row-wide vector + try: # Generate a row-wide vector rightDot = rightRowDots[row + 1] rowVec = (dots[rightDot][0] - dots[leftDot][0], dots[rightDot][1] - dots[leftDot][1]) except Exception: print(f"rightRowDots: {rightRowDots}") - + for col, topDot in enumerate(topColumnDots[1:-1]): # Generate a column-wide vector bottomDot = bottomColumnDots[col + 1] colVec = (dots[bottomDot][0] - dots[topDot][0], dots[bottomDot][1] - dots[topDot][1]) - + # Figure out how far along the top and bottom sides we are - topLen = hypot(dots[topDot][0] - dots[topColumnDots[0]][0], dots[topDot][1] - dots[topColumnDots[0]][1]) + topLen = hypot( + dots[topDot][0] - dots[topColumnDots[0]][0], dots[topDot][1] - dots[topColumnDots[0]][1] + ) topRatio = topLen / topFullLength - bottomLen = hypot(dots[bottomDot][0] - dots[bottomColumnDots[0]][0], dots[bottomDot][1] - dots[bottomColumnDots[0]][1]) + bottomLen = hypot( + dots[bottomDot][0] - dots[bottomColumnDots[0]][0], + dots[bottomDot][1] - dots[bottomColumnDots[0]][1], + ) bottomRatio = bottomLen / bottomFullLength - + # Figure out how much of the top ratio vs. the bottom ratio we should use amountOfTop = 1.0 - (float(row + 1) / float(len(leftRowDots) - 1)) - + # Figure out how far along the row line we should go by weighted averaging the top and bottom ratios ratioWithinRow = (topRatio * amountOfTop) + (bottomRatio * (1 - amountOfTop)) - + # Interpolate the point - newDot = (dots[leftDot][0] + rowVec[0] * ratioWithinRow, dots[leftDot][1] + rowVec[1] * ratioWithinRow) + newDot = ( + dots[leftDot][0] + rowVec[0] * ratioWithinRow, + dots[leftDot][1] + rowVec[1] * ratioWithinRow, + ) dots.append(newDot) - if(drawDebuggingGrid): - drawDebuggingGrid.ellipse((newDot[0] - 10, newDot[1] - 10, newDot[0] + 10, newDot[1] + 10), fill="blue") - + if drawDebuggingGrid: + drawDebuggingGrid.ellipse( + (newDot[0] - 10, newDot[1] - 10, newDot[0] + 10, newDot[1] + 10), fill="blue" + ) + # Search for a row, then search for the next based on its bottom-left # point, until there are no more rows rowNum = 0 - while(topLeft is not None): + while topLeft is not None: (topLeft, newRow) = FindRow(dots, topLeft, rowNum) squares.append(newRow) rowNum = rowNum + 1 - + # Return whatever we found in a [y][x] array return squares -if(__name__ == "__main__"): + +if __name__ == "__main__": # Parse parameters - if(len(sys.argv) <= 1 or sys.argv[1] == "--help"): + if len(sys.argv) <= 1 or sys.argv[1] == "--help": print("Usage: slice.py inputfile [color [outputdir [width height]]]") print() print("inputfile = The input image file") @@ -339,48 +417,46 @@ def FindRow(dots, tlDotIndex, rowNum): print() print("Exit status for this script is the number of slices output.") exit(0) - + # Input filename argument inputFilename = sys.argv[1] - + # Color argument - if(len(sys.argv) <= 2): - channel = 2 + if len(sys.argv) <= 2: + channel = 2 else: - channel = sys.argv[2] - + channel = sys.argv[2] + # Output directory argument - if(len(sys.argv) <= 3): + if len(sys.argv) <= 3: outputDir = inputFilename[:-4] + sep else: outputDir = sys.argv[3] + sep - + # Width/height - if(len(sys.argv) >= 5): + if len(sys.argv) >= 5: outputSize = (int(sys.argv[4]), int(sys.argv[5])) - + # Create the output directory if it doesn't already exist - if(not path.exists(outputDir)): + if not path.exists(outputDir): mkdir(outputDir) - + # Downsize the search area to something a little more reasonable imageOriginal = GetSource(inputFilename) imageDebuggingGrid = GetColor(imageOriginal, channel).convert("RGB") drawDebuggingGrid = ImageDraw.Draw(imageDebuggingGrid) - + # Find all squares within the image squares = SliceSquares(GetSource(inputFilename), channel, drawDebuggingGrid) - + # Save them all out numSlices = 0 for y in range(0, len(squares)): for x in range(0, len(squares[y])): squares[y][x].save(outputDir + sep + "out-" + str(x) + "-" + str(y) + ".jpg") numSlices = numSlices + 1 - + # Save out a debugging image - imageDebuggingGrid.save(outputDir + sep + "lines.png"); - + imageDebuggingGrid.save(outputDir + sep + "lines.png") # Return the number of images saved exit(numSlices) - diff --git a/waznexserver/config.py b/waznexserver/config.py index b7ae42e..701c53e 100644 --- a/waznexserver/config.py +++ b/waznexserver/config.py @@ -11,10 +11,7 @@ IMAGE_FOLDER = os.path.join(DATA_FOLDER, 'images') DOWNSIZED_FOLDER = os.path.join(DATA_FOLDER, 'downsized') THUMBNAIL_FOLDER = os.path.join(DATA_FOLDER, 'thumbnails') -ALLOWED_EXTENSIONS = {'png', 'PNG', - 'jpg', 'JPG', - 'jpeg', 'JPEG', - 'gif', 'GIF'} +ALLOWED_EXTENSIONS = {'png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG', 'gif', 'GIF'} FILE_NAME_DT_FORMAT = '%Y-%m-%dT%H%M%S' PRETTY_DT_FORMAT = '%m/%d/%Y %I:%M:%S %p' ENABLE_GRIDSPLITTER = True @@ -22,7 +19,7 @@ GRIDSPLITTER_SLICER = "/opt/waznexserver/WaznexServer/gridsplitter/slice.py" SPLIT_FOLDER = os.path.join(DATA_FOLDER, 'sliced') GRIDSPLITTER_CELL_PREFIX = 'out-' -GRIDSPLITTER_COLOR = '2' # 0:red, 1:green or 2:blue +GRIDSPLITTER_COLOR = '2' # 0:red, 1:green or 2:blue GRIDSPLITTER_CELL_WIDTH = '320' GRIDSPLITTER_CELL_HEIGHT = '207' GRIDSPLITTER_MIN_COLS = 2 diff --git a/waznexserver/init_data.py b/waznexserver/init_data.py index b2c70c7..d3f7ed3 100755 --- a/waznexserver/init_data.py +++ b/waznexserver/init_data.py @@ -34,14 +34,14 @@ def create_database(): statuses = [s for s in dir(models) if s.startswith('IMAGESTATUS_')] for status in statuses: id = getattr(models, status) - s = models.ImageStatus(id, status.split('_',1)[1]) + s = models.ImageStatus(id, status.split('_', 1)[1]) db.session.add(s) # Find and add all of the ImageLevels in models.py levels = [l for l in dir(models) if l.startswith('IMAGELEVEL_')] for level in levels: id = getattr(models, level) - l = models.ImageLevel(id, level.split('_',1)[1]) + l = models.ImageLevel(id, level.split('_', 1)[1]) db.session.add(l) db.session.commit() diff --git a/waznexserver/models.py b/waznexserver/models.py index 6a1dac5..5ee4fb5 100644 --- a/waznexserver/models.py +++ b/waznexserver/models.py @@ -10,6 +10,7 @@ class Base(DeclarativeBase): pass + db = SQLAlchemy(model_class=Base) # Image Levels (basic thumbnails, full grid) @@ -17,14 +18,15 @@ class Base(DeclarativeBase): IMAGELEVEL_BASIC = 0 IMAGELEVEL_GRID = 1 -class ImageLevel (db.Model): + +class ImageLevel(db.Model): id = db.Column(db.Integer, primary_key=True) desc = db.Column(db.String(16)) - + def __init__(self, id, desc): self.id = id self.desc = desc - + def __repr__(self): return f'' @@ -35,14 +37,15 @@ def __repr__(self): IMAGESTATUS_IN_WORK = 1 IMAGESTATUS_DONE = 2 -class ImageStatus (db.Model): + +class ImageStatus(db.Model): id = db.Column(db.Integer, primary_key=True) desc = db.Column(db.String(16)) - + def __init__(self, id, desc): self.id = id self.desc = desc - + def __repr__(self): return f'' @@ -59,28 +62,25 @@ def __init__(self, upload_dt, filename): self.filename = filename self.status = IMAGESTATUS_NEW self.level = IMAGELEVEL_NOTHING - + def __repr__(self): - return ''.format(self.id, - self.filename, - self.status, - self.level) - + return f'' + def get_thumbnail_path(self): return os.path.join(app.config['THUMBNAIL_FOLDER'], self.filename) - + def get_downsized_path(self): return os.path.join(app.config['DOWNSIZED_FOLDER'], self.filename) - + def get_image_path(self): return os.path.join(app.config['IMAGE_FOLDER'], self.filename) - + def get_split_path(self): # Get the file name without extension fn_parts = self.filename.split('.') fn = ''.join(fn_parts[:-1]) return os.path.join(app.config['SPLIT_FOLDER'], fn) - + def get_split_rel_path(self): fn_parts = self.filename.split('.') return fn_parts[-2] @@ -88,23 +88,17 @@ def get_split_rel_path(self): class GridCell(db.Model): id = db.Column(db.Integer, primary_key=True) - fk_grid_item = db.Column(db.Integer, - db.ForeignKey('grid_item.id'), - index=True) + fk_grid_item = db.Column(db.Integer, db.ForeignKey('grid_item.id'), index=True) grid_item = db.relationship("GridItem") filename = db.Column(db.String(16)) col = db.Column(db.Integer) row = db.Column(db.Integer) - + def __init__(self, grid_item, filename, col, row): self.fk_grid_item = grid_item self.filename = filename self.col = col self.row = row - + def __repr__(self): - return ''.format(self.id, - self.grid_item, - self.filename, - self.col, - self.row) + return f'' diff --git a/waznexserver/process_grid.py b/waznexserver/process_grid.py index 824a827..c1e4a6c 100755 --- a/waznexserver/process_grid.py +++ b/waznexserver/process_grid.py @@ -3,7 +3,6 @@ import os import shutil import subprocess -import sys import traceback from PIL import Image @@ -18,25 +17,21 @@ def run_basic_transforms(grid_image): try: # Copy orig and create downsized version (1024x1024 max) - app.logger.info('Generating downsized image for ' + - grid_image.filename) - shutil.copy2(grid_image.get_image_path(), - grid_image.get_downsized_path()) + app.logger.info('Generating downsized image for ' + grid_image.filename) + shutil.copy2(grid_image.get_image_path(), grid_image.get_downsized_path()) downs = Image.open(grid_image.get_downsized_path()) - downs.thumbnail((1024,1024), Image.LANCZOS) + downs.thumbnail((1024, 1024), Image.LANCZOS) downs.save(grid_image.get_downsized_path(), "JPEG") # Copy orig and create thumbnail version app.logger.info('Generating thumbnail for ' + grid_image.filename) - shutil.copy2(grid_image.get_image_path(), - grid_image.get_thumbnail_path()) + shutil.copy2(grid_image.get_image_path(), grid_image.get_thumbnail_path()) thumb = Image.open(grid_image.get_thumbnail_path()) - thumb.thumbnail((316,316), Image.LANCZOS) + thumb.thumbnail((316, 316), Image.LANCZOS) thumb.save(grid_image.get_thumbnail_path(), "JPEG") except Exception: - print("Error while performing basic transforms on {}".format( - grid_image.filename)) + print(f"Error while performing basic transforms on {grid_image.filename}") traceback.print_exc() return False @@ -48,66 +43,66 @@ def run_gridsplitter(grid_image): if not config.GRIDSPLITTER_PYTHON or not config.GRIDSPLITTER_SLICER: print("GridSplitter is not configured. Check your config.py.") return False - + # Check validity of GRIDSPLITTER_PYTHON - slicer_python = os.path.abspath(config.GRIDSPLITTER_PYTHON) + slicer_python = os.path.abspath(config.GRIDSPLITTER_PYTHON) if not os.path.exists(slicer_python): print('Aborting: Could not find GridSplitter Python.') print(f'Tried: {slicer_python}') return False - + # Check validity of GRIDSPLITTER_SLICER slicer = os.path.abspath(config.GRIDSPLITTER_SLICER) if not os.path.exists(slicer): print('Aborting: Could not find GridSplitter.') print(f'Tried: {slicer}') return False - + # Run the splitter for this image - ret_val = subprocess.call([slicer_python, - slicer, - grid_image.get_image_path(), - config.GRIDSPLITTER_COLOR, - grid_image.get_split_path(), - config.GRIDSPLITTER_CELL_WIDTH, - config.GRIDSPLITTER_CELL_HEIGHT]) - #if ret_val: - # TODO Give slicer.py return values meaningful assignments + ret_val = subprocess.call( + [ + slicer_python, + slicer, + grid_image.get_image_path(), + config.GRIDSPLITTER_COLOR, + grid_image.get_split_path(), + config.GRIDSPLITTER_CELL_WIDTH, + config.GRIDSPLITTER_CELL_HEIGHT, + ] + ) + # if ret_val: + # TODO Give slicer.py return values meaningful assignments # print "Unknown error slicing: %s" % (grid_image.filename,) # return False - + # Run verification and sanity checks if not verify_gridsplitter(grid_image): return False - + # Build Cell Grid grid_dir = grid_image.get_split_path() - cells = [c for c in os.listdir(grid_dir) if\ - c.startswith(config.GRIDSPLITTER_CELL_PREFIX)] + cells = [c for c in os.listdir(grid_dir) if c.startswith(config.GRIDSPLITTER_CELL_PREFIX)] for cell in cells: parts = cell.split('.')[0].split("-") c = models.GridCell(grid_image.id, cell, int(parts[1]), int(parts[2])) db.session.add(c) - + app.logger.info("Successfully sliced: " + grid_image.filename) return True - + def verify_gridsplitter(grid_image): grid_dir = grid_image.get_split_path() - + # Find all of the cell images - cells = [c for c in os.listdir(grid_dir) if\ - c.startswith(config.GRIDSPLITTER_CELL_PREFIX)] - + cells = [c for c in os.listdir(grid_dir) if c.startswith(config.GRIDSPLITTER_CELL_PREFIX)] + # Verify rough count is within MIN and MAX - min_ct = config.GRIDSPLITTER_MIN_COLS *config.GRIDSPLITTER_MIN_ROWS - max_ct = config.GRIDSPLITTER_MAX_COLS *config.GRIDSPLITTER_MAX_ROWS + min_ct = config.GRIDSPLITTER_MIN_COLS * config.GRIDSPLITTER_MIN_ROWS + max_ct = config.GRIDSPLITTER_MAX_COLS * config.GRIDSPLITTER_MAX_ROWS cell_ct = len(cells) if (cell_ct < min_ct) or (cell_ct > max_ct): - print(("Cell count was out of range %d-%d:%d") % (min_ct, - max_ct, - cell_ct)) + print(("Cell count was out of range %d-%d:%d") % (min_ct, max_ct, cell_ct)) return False # Verify each cell is within MIN and MAX rows and cols high_col = -1 @@ -116,33 +111,29 @@ def verify_gridsplitter(grid_image): parts = cell.split('.')[0].split("-") col = int(parts[1]) row = int(parts[2]) - if col < 0 or col > config.GRIDSPLITTER_MAX_COLS or\ - row < 0 or row > config.GRIDSPLITTER_MAX_ROWS: - print("Column or row count was incorrect") + if col < 0 or col > config.GRIDSPLITTER_MAX_COLS or row < 0 or row > config.GRIDSPLITTER_MAX_ROWS: + print("Column or row count was incorrect") return False if col > high_col: high_col = col if row > high_row: high_row = row - if (high_col < (config.GRIDSPLITTER_MIN_COLS - 1)) or\ - (high_row < (config.GRIDSPLITTER_MIN_ROWS - 1)): - print("Too few rows or columns found.") + if (high_col < (config.GRIDSPLITTER_MIN_COLS - 1)) or (high_row < (config.GRIDSPLITTER_MIN_ROWS - 1)): + print("Too few rows or columns found.") return False - + # TODO Verify images form an actual grid - + return True - + def process_new_images(): - new_grids = db.session.query(models.GridItem).\ - filter_by(status=models.IMAGESTATUS_NEW).\ - order_by('upload_dt').all() + new_grids = db.session.query(models.GridItem).filter_by(status=models.IMAGESTATUS_NEW).order_by('upload_dt').all() for g in new_grids: db.session.add(g) g.status = models.IMAGESTATUS_IN_WORK - + try: # Do basic image transforms basic_result = run_basic_transforms(g) @@ -152,7 +143,7 @@ def process_new_images(): else: g.status = models.IMAGESTATUS_BAD print("Basic Failed") - + # Do advanced image transforms if basic_result and config.ENABLE_GRIDSPLITTER: gs_result = run_gridsplitter(g) @@ -161,9 +152,9 @@ def process_new_images(): print("GridSplitter OK") else: # Uncomment to mark it bad - #g.status = models.IMAGESTATUS_BAD + # g.status = models.IMAGESTATUS_BAD print("GridSplitter Failed") - + if basic_result: g.status = models.IMAGESTATUS_DONE diff --git a/waznexserver/tests/hammeruploads/hammerit.py b/waznexserver/tests/hammeruploads/hammerit.py deleted file mode 100644 index 412902c..0000000 --- a/waznexserver/tests/hammeruploads/hammerit.py +++ /dev/null @@ -1,28 +0,0 @@ -# A script to upload a bunch of images to see how your server -# handles the load. -# -# Based on http://atlee.ca/software/poster/ -# https://bitbucket.org/chrisatlee/poster -import os -from poster.encode import multipart_encode -from poster.streaminghttp import register_openers -import urllib.request, urllib.error, urllib.parse - -UPLOADS_PATH = "/home/ben/Projects/WaznexServer/waznexserver/tests/hammeruploads/uploads" -UPLOAD_URL = "http://waznex-dev.clusterbleep.net/upload/" - -def upload_images(): - # Get list of files from IMAGE_FOLDER and sort by "date" (filename) - image_list = os.listdir(UPLOADS_PATH) - for image in image_list: - try: - print("Uploading: " + image) - register_openers() - datagen, headers = multipart_encode({"file": open(UPLOADS_PATH + '/' + image, "rb")}) - request = urllib.request.Request(UPLOAD_URL, datagen, headers) - print(urllib.request.urlopen(request).read()) - except Exception: - pass # Skip files that don't work - -if __name__ == '__main__': - upload_images() diff --git a/waznexserver/tests/hammeruploads/poster/__init__.py b/waznexserver/tests/hammeruploads/poster/__init__.py deleted file mode 100644 index f822824..0000000 --- a/waznexserver/tests/hammeruploads/poster/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2011 Chris AtLee -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -"""poster module - -Support for streaming HTTP uploads, and multipart/form-data encoding - -```poster.version``` is a 3-tuple of integers representing the version number. -New releases of poster will always have a version number that compares greater -than an older version of poster. -New in version 0.6.""" - -import poster.streaminghttp -import poster.encode - -version = (0, 8, 2) # Thanks JP! diff --git a/waznexserver/tests/hammeruploads/poster/encode.py b/waznexserver/tests/hammeruploads/poster/encode.py deleted file mode 100644 index 34d7507..0000000 --- a/waznexserver/tests/hammeruploads/poster/encode.py +++ /dev/null @@ -1,414 +0,0 @@ -"""multipart/form-data encoding module - -This module provides functions that faciliate encoding name/value pairs -as multipart/form-data suitable for a HTTP POST or PUT request. - -multipart/form-data is the standard way to upload files over HTTP""" - -__all__ = ['gen_boundary', 'encode_and_quote', 'MultipartParam', - 'encode_string', 'encode_file_header', 'get_body_size', 'get_headers', - 'multipart_encode'] - -try: - import uuid - def gen_boundary(): - """Returns a random string to use as the boundary for a message""" - return uuid.uuid4().hex -except ImportError: - import random, sha - def gen_boundary(): - """Returns a random string to use as the boundary for a message""" - bits = random.getrandbits(160) - return sha.new(str(bits)).hexdigest() - -import urllib.request, urllib.parse, urllib.error, re, os, mimetypes -try: - from email.header import Header -except ImportError: - # Python 2.4 - from email.Header import Header - -def encode_and_quote(data): - """If ``data`` is unicode, return urllib.quote_plus(data.encode("utf-8")) - otherwise return urllib.quote_plus(data)""" - if data is None: - return None - - if isinstance(data, str): - data = data.encode("utf-8") - return urllib.parse.quote_plus(data) - -def _strify(s): - """If s is a unicode string, encode it to UTF-8 and return the results, - otherwise return str(s), or None if s is None""" - if s is None: - return None - if isinstance(s, str): - return s.encode("utf-8") - return str(s) - -class MultipartParam: - """Represents a single parameter in a multipart/form-data request - - ``name`` is the name of this parameter. - - If ``value`` is set, it must be a string or unicode object to use as the - data for this parameter. - - If ``filename`` is set, it is what to say that this parameter's filename - is. Note that this does not have to be the actual filename any local file. - - If ``filetype`` is set, it is used as the Content-Type for this parameter. - If unset it defaults to "text/plain; charset=utf8" - - If ``filesize`` is set, it specifies the length of the file ``fileobj`` - - If ``fileobj`` is set, it must be a file-like object that supports - .read(). - - Both ``value`` and ``fileobj`` must not be set, doing so will - raise a ValueError assertion. - - If ``fileobj`` is set, and ``filesize`` is not specified, then - the file's size will be determined first by stat'ing ``fileobj``'s - file descriptor, and if that fails, by seeking to the end of the file, - recording the current position as the size, and then by seeking back to the - beginning of the file. - - ``cb`` is a callable which will be called from iter_encode with (self, - current, total), representing the current parameter, current amount - transferred, and the total size. - """ - def __init__(self, name, value=None, filename=None, filetype=None, - filesize=None, fileobj=None, cb=None): - self.name = Header(name).encode() - self.value = _strify(value) - if filename is None: - self.filename = None - else: - if isinstance(filename, str): - # Encode with XML entities - self.filename = filename.encode("ascii", "xmlcharrefreplace") - else: - self.filename = str(filename) - self.filename = self.filename.encode("string_escape").\ - replace('"', '\\"') - self.filetype = _strify(filetype) - - self.filesize = filesize - self.fileobj = fileobj - self.cb = cb - - if self.value is not None and self.fileobj is not None: - raise ValueError("Only one of value or fileobj may be specified") - - if fileobj is not None and filesize is None: - # Try and determine the file size - try: - self.filesize = os.fstat(fileobj.fileno()).st_size - except (OSError, AttributeError): - try: - fileobj.seek(0, 2) - self.filesize = fileobj.tell() - fileobj.seek(0) - except: - raise ValueError("Could not determine filesize") - - def __cmp__(self, other): - attrs = ['name', 'value', 'filename', 'filetype', 'filesize', 'fileobj'] - myattrs = [getattr(self, a) for a in attrs] - oattrs = [getattr(other, a) for a in attrs] - return cmp(myattrs, oattrs) - - def reset(self): - if self.fileobj is not None: - self.fileobj.seek(0) - elif self.value is None: - raise ValueError("Don't know how to reset this parameter") - - @classmethod - def from_file(cls, paramname, filename): - """Returns a new MultipartParam object constructed from the local - file at ``filename``. - - ``filesize`` is determined by os.path.getsize(``filename``) - - ``filetype`` is determined by mimetypes.guess_type(``filename``)[0] - - ``filename`` is set to os.path.basename(``filename``) - """ - - return cls(paramname, filename=os.path.basename(filename), - filetype=mimetypes.guess_type(filename)[0], - filesize=os.path.getsize(filename), - fileobj=open(filename, "rb")) - - @classmethod - def from_params(cls, params): - """Returns a list of MultipartParam objects from a sequence of - name, value pairs, MultipartParam instances, - or from a mapping of names to values - - The values may be strings or file objects, or MultipartParam objects. - MultipartParam object names must match the given names in the - name,value pairs or mapping, if applicable.""" - if hasattr(params, 'items'): - params = list(params.items()) - - retval = [] - for item in params: - if isinstance(item, cls): - retval.append(item) - continue - name, value = item - if isinstance(value, cls): - assert value.name == name - retval.append(value) - continue - if hasattr(value, 'read'): - # Looks like a file object - filename = getattr(value, 'name', None) - if filename is not None: - filetype = mimetypes.guess_type(filename)[0] - else: - filetype = None - - retval.append(cls(name=name, filename=filename, - filetype=filetype, fileobj=value)) - else: - retval.append(cls(name, value)) - return retval - - def encode_hdr(self, boundary): - """Returns the header of the encoding of this parameter""" - boundary = encode_and_quote(boundary) - - headers = ["--%s" % boundary] - - if self.filename: - disposition = 'form-data; name="{}"; filename="{}"'.format(self.name, - self.filename) - else: - disposition = 'form-data; name="%s"' % self.name - - headers.append("Content-Disposition: %s" % disposition) - - if self.filetype: - filetype = self.filetype - else: - filetype = "text/plain; charset=utf-8" - - headers.append("Content-Type: %s" % filetype) - - headers.append("") - headers.append("") - - return "\r\n".join(headers) - - def encode(self, boundary): - """Returns the string encoding of this parameter""" - if self.value is None: - value = self.fileobj.read() - else: - value = self.value - - if re.search("^--%s$" % re.escape(boundary), value, re.M): - raise ValueError("boundary found in encoded string") - - return f"{self.encode_hdr(boundary)}{value}\r\n" - - def iter_encode(self, boundary, blocksize=4096): - """Yields the encoding of this parameter - If self.fileobj is set, then blocks of ``blocksize`` bytes are read and - yielded.""" - total = self.get_size(boundary) - current = 0 - if self.value is not None: - block = self.encode(boundary) - current += len(block) - yield block - if self.cb: - self.cb(self, current, total) - else: - block = self.encode_hdr(boundary) - current += len(block) - yield block - if self.cb: - self.cb(self, current, total) - last_block = "" - encoded_boundary = "--%s" % encode_and_quote(boundary) - boundary_exp = re.compile("^%s$" % re.escape(encoded_boundary), - re.M) - while True: - block = self.fileobj.read(blocksize) - if not block: - current += 2 - yield "\r\n" - if self.cb: - self.cb(self, current, total) - break - last_block += block - if boundary_exp.search(last_block): - raise ValueError("boundary found in file data") - last_block = last_block[-len(encoded_boundary)-2:] - current += len(block) - yield block - if self.cb: - self.cb(self, current, total) - - def get_size(self, boundary): - """Returns the size in bytes that this param will be when encoded - with the given boundary.""" - if self.filesize is not None: - valuesize = self.filesize - else: - valuesize = len(self.value) - - return len(self.encode_hdr(boundary)) + 2 + valuesize - -def encode_string(boundary, name, value): - """Returns ``name`` and ``value`` encoded as a multipart/form-data - variable. ``boundary`` is the boundary string used throughout - a single request to separate variables.""" - - return MultipartParam(name, value).encode(boundary) - -def encode_file_header(boundary, paramname, filesize, filename=None, - filetype=None): - """Returns the leading data for a multipart/form-data field that contains - file data. - - ``boundary`` is the boundary string used throughout a single request to - separate variables. - - ``paramname`` is the name of the variable in this request. - - ``filesize`` is the size of the file data. - - ``filename`` if specified is the filename to give to this field. This - field is only useful to the server for determining the original filename. - - ``filetype`` if specified is the MIME type of this file. - - The actual file data should be sent after this header has been sent. - """ - - return MultipartParam(paramname, filesize=filesize, filename=filename, - filetype=filetype).encode_hdr(boundary) - -def get_body_size(params, boundary): - """Returns the number of bytes that the multipart/form-data encoding - of ``params`` will be.""" - size = sum(p.get_size(boundary) for p in MultipartParam.from_params(params)) - return size + len(boundary) + 6 - -def get_headers(params, boundary): - """Returns a dictionary with Content-Type and Content-Length headers - for the multipart/form-data encoding of ``params``.""" - headers = {} - boundary = urllib.parse.quote_plus(boundary) - headers['Content-Type'] = "multipart/form-data; boundary=%s" % boundary - headers['Content-Length'] = str(get_body_size(params, boundary)) - return headers - -class multipart_yielder: - def __init__(self, params, boundary, cb): - self.params = params - self.boundary = boundary - self.cb = cb - - self.i = 0 - self.p = None - self.param_iter = None - self.current = 0 - self.total = get_body_size(params, boundary) - - def __iter__(self): - return self - - def __next__(self): - """generator function to yield multipart/form-data representation - of parameters""" - if self.param_iter is not None: - try: - block = next(self.param_iter) - self.current += len(block) - if self.cb: - self.cb(self.p, self.current, self.total) - return block - except StopIteration: - self.p = None - self.param_iter = None - - if self.i is None: - raise StopIteration - elif self.i >= len(self.params): - self.param_iter = None - self.p = None - self.i = None - block = "--%s--\r\n" % self.boundary - self.current += len(block) - if self.cb: - self.cb(self.p, self.current, self.total) - return block - - self.p = self.params[self.i] - self.param_iter = self.p.iter_encode(self.boundary) - self.i += 1 - return next(self) - - def reset(self): - self.i = 0 - self.current = 0 - for param in self.params: - param.reset() - -def multipart_encode(params, boundary=None, cb=None): - """Encode ``params`` as multipart/form-data. - - ``params`` should be a sequence of (name, value) pairs or MultipartParam - objects, or a mapping of names to values. - Values are either strings parameter values, or file-like objects to use as - the parameter value. The file-like objects must support .read() and either - .fileno() or both .seek() and .tell(). - - If ``boundary`` is set, then it as used as the MIME boundary. Otherwise - a randomly generated boundary will be used. In either case, if the - boundary string appears in the parameter values a ValueError will be - raised. - - If ``cb`` is set, it should be a callback which will get called as blocks - of data are encoded. It will be called with (param, current, total), - indicating the current parameter being encoded, the current amount encoded, - and the total amount to encode. - - Returns a tuple of `datagen`, `headers`, where `datagen` is a - generator that will yield blocks of data that make up the encoded - parameters, and `headers` is a dictionary with the assoicated - Content-Type and Content-Length headers. - - Examples: - - >>> datagen, headers = multipart_encode( [("key", "value1"), ("key", "value2")] ) - >>> s = "".join(datagen) - >>> assert "value2" in s and "value1" in s - - >>> p = MultipartParam("key", "value2") - >>> datagen, headers = multipart_encode( [("key", "value1"), p] ) - >>> s = "".join(datagen) - >>> assert "value2" in s and "value1" in s - - >>> datagen, headers = multipart_encode( {"key": "value1"} ) - >>> s = "".join(datagen) - >>> assert "value2" not in s and "value1" in s - - """ - if boundary is None: - boundary = gen_boundary() - else: - boundary = urllib.parse.quote_plus(boundary) - - headers = get_headers(params, boundary) - params = MultipartParam.from_params(params) - - return multipart_yielder(params, boundary, cb), headers diff --git a/waznexserver/tests/hammeruploads/poster/streaminghttp.py b/waznexserver/tests/hammeruploads/poster/streaminghttp.py deleted file mode 100644 index 3d7591d..0000000 --- a/waznexserver/tests/hammeruploads/poster/streaminghttp.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Streaming HTTP uploads module. - -This module extends the standard httplib and urllib2 objects so that -iterable objects can be used in the body of HTTP requests. - -In most cases all one should have to do is call :func:`register_openers()` -to register the new streaming http handlers which will take priority over -the default handlers, and then you can use iterable objects in the body -of HTTP requests. - -**N.B.** You must specify a Content-Length header if using an iterable object -since there is no way to determine in advance the total size that will be -yielded, and there is no way to reset an interator. - -Example usage: - ->>> from StringIO import StringIO ->>> import urllib2, poster.streaminghttp - ->>> opener = poster.streaminghttp.register_openers() - ->>> s = "Test file data" ->>> f = StringIO(s) - ->>> req = urllib2.Request("http://localhost:8080", f, -... {'Content-Length': str(len(s))}) -""" - -import http.client, urllib.request, urllib.error, urllib.parse, socket -from http.client import NotConnected - -__all__ = ['StreamingHTTPConnection', 'StreamingHTTPRedirectHandler', - 'StreamingHTTPHandler', 'register_openers'] - -if hasattr(httplib, 'HTTPS'): - __all__.extend(['StreamingHTTPSHandler', 'StreamingHTTPSConnection']) - -class _StreamingHTTPMixin: - """Mixin class for HTTP and HTTPS connections that implements a streaming - send method.""" - def send(self, value): - """Send ``value`` to the server. - - ``value`` can be a string object, a file-like object that supports - a .read() method, or an iterable object that supports a .next() - method. - """ - # Based on python 2.6's httplib.HTTPConnection.send() - if self.sock is None: - if self.auto_open: - self.connect() - else: - raise NotConnected() - - # send the data to the server. if we get a broken pipe, then close - # the socket. we want to reconnect when somebody tries to send again. - # - # NOTE: we DO propagate the error, though, because we cannot simply - # ignore the error... the caller will know if they can retry. - if self.debuglevel > 0: - print("send:", repr(value)) - try: - blocksize = 8192 - if hasattr(value, 'read') : - if hasattr(value, 'seek'): - value.seek(0) - if self.debuglevel > 0: - print("sendIng a read()able") - data = value.read(blocksize) - while data: - self.sock.sendall(data) - data = value.read(blocksize) - elif hasattr(value, 'next'): - if hasattr(value, 'reset'): - value.reset() - if self.debuglevel > 0: - print("sendIng an iterable") - for data in value: - self.sock.sendall(data) - else: - self.sock.sendall(value) - except OSError as v: - if v[0] == 32: # Broken pipe - self.close() - raise - -class StreamingHTTPConnection(_StreamingHTTPMixin, http.client.HTTPConnection): - """Subclass of `httplib.HTTPConnection` that overrides the `send()` method - to support iterable body objects""" - -class StreamingHTTPRedirectHandler(urllib.request.HTTPRedirectHandler): - """Subclass of `urllib2.HTTPRedirectHandler` that overrides the - `redirect_request` method to properly handle redirected POST requests - - This class is required because python 2.5's HTTPRedirectHandler does - not remove the Content-Type or Content-Length headers when requesting - the new resource, but the body of the original request is not preserved. - """ - - handler_order = urllib.request.HTTPRedirectHandler.handler_order - 1 - - # From python2.6 urllib2's HTTPRedirectHandler - def redirect_request(self, req, fp, code, msg, headers, newurl): - """Return a Request or None in response to a redirect. - - This is called by the http_error_30x methods when a - redirection response is received. If a redirection should - take place, return a new Request to allow http_error_30x to - perform the redirect. Otherwise, raise HTTPError if no-one - else should try to handle this url. Return None if you can't - but another Handler might. - """ - m = req.get_method() - if (code in (301, 302, 303, 307) and m in ("GET", "HEAD") - or code in (301, 302, 303) and m == "POST"): - # Strictly (according to RFC 2616), 301 or 302 in response - # to a POST MUST NOT cause a redirection without confirmation - # from the user (of urllib2, in this case). In practice, - # essentially all clients do redirect in this case, so we - # do the same. - # be conciliant with URIs containing a space - newurl = newurl.replace(' ', '%20') - newheaders = {k: v for k, v in list(req.headers.items()) - if k.lower() not in ( - "content-length", "content-type") - } - return urllib.request.Request(newurl, - headers=newheaders, - origin_req_host=req.get_origin_req_host(), - unverifiable=True) - else: - raise urllib.error.HTTPError(req.get_full_url(), code, msg, headers, fp) - -class StreamingHTTPHandler(urllib.request.HTTPHandler): - """Subclass of `urllib2.HTTPHandler` that uses - StreamingHTTPConnection as its http connection class.""" - - handler_order = urllib.request.HTTPHandler.handler_order - 1 - - def http_open(self, req): - """Open a StreamingHTTPConnection for the given request""" - return self.do_open(StreamingHTTPConnection, req) - - def http_request(self, req): - """Handle a HTTP request. Make sure that Content-Length is specified - if we're using an interable value""" - # Make sure that if we're using an iterable object as the request - # body, that we've also specified Content-Length - if req.has_data(): - data = req.get_data() - if hasattr(data, 'read') or hasattr(data, 'next'): - if not req.has_header('Content-length'): - raise ValueError( - "No Content-Length specified for iterable body") - return urllib.request.HTTPHandler.do_request_(self, req) - -if hasattr(httplib, 'HTTPS'): - class StreamingHTTPSConnection(_StreamingHTTPMixin, - http.client.HTTPSConnection): - """Subclass of `httplib.HTTSConnection` that overrides the `send()` - method to support iterable body objects""" - - class StreamingHTTPSHandler(urllib.request.HTTPSHandler): - """Subclass of `urllib2.HTTPSHandler` that uses - StreamingHTTPSConnection as its http connection class.""" - - handler_order = urllib.request.HTTPSHandler.handler_order - 1 - - def https_open(self, req): - return self.do_open(StreamingHTTPSConnection, req) - - def https_request(self, req): - # Make sure that if we're using an iterable object as the request - # body, that we've also specified Content-Length - if req.has_data(): - data = req.get_data() - if hasattr(data, 'read') or hasattr(data, 'next'): - if not req.has_header('Content-length'): - raise ValueError( - "No Content-Length specified for iterable body") - return urllib.request.HTTPSHandler.do_request_(self, req) - - -def get_handlers(): - handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler] - if hasattr(httplib, "HTTPS"): - handlers.append(StreamingHTTPSHandler) - return handlers - -def register_openers(): - """Register the streaming http handlers in the global urllib2 default - opener object. - - Returns the created OpenerDirector object.""" - opener = urllib.request.build_opener(*get_handlers()) - - urllib.request.install_opener(opener) - - return opener diff --git a/waznexserver/tests/hammeruploads/uploads/IMG_20100820_213138 (copy).JPG b/waznexserver/tests/hammeruploads/uploads/IMG_20100820_213138 (copy).JPG deleted file mode 100644 index 0fb6e6b..0000000 Binary files a/waznexserver/tests/hammeruploads/uploads/IMG_20100820_213138 (copy).JPG and /dev/null differ diff --git a/waznexserver/tests/hammeruploads/uploads/IMG_20100820_213138.jpg b/waznexserver/tests/hammeruploads/uploads/IMG_20100820_213138.jpg deleted file mode 100644 index 0fb6e6b..0000000 Binary files a/waznexserver/tests/hammeruploads/uploads/IMG_20100820_213138.jpg and /dev/null differ diff --git a/waznexserver/tests/hammeruploads/uploads/IMG_20100821_154954 copy.jpg b/waznexserver/tests/hammeruploads/uploads/IMG_20100821_154954 copy.jpg deleted file mode 100644 index c03d8f3..0000000 Binary files a/waznexserver/tests/hammeruploads/uploads/IMG_20100821_154954 copy.jpg and /dev/null differ diff --git a/waznexserver/tests/hammeruploads/uploads/IMG_20100821_154954.jpg b/waznexserver/tests/hammeruploads/uploads/IMG_20100821_154954.jpg deleted file mode 100644 index c03d8f3..0000000 Binary files a/waznexserver/tests/hammeruploads/uploads/IMG_20100821_154954.jpg and /dev/null differ diff --git a/waznexserver/tests/hammeruploads/uploads/IMG_20100821_155007.jpg b/waznexserver/tests/hammeruploads/uploads/IMG_20100821_155007.jpg deleted file mode 100644 index 47b802c..0000000 Binary files a/waznexserver/tests/hammeruploads/uploads/IMG_20100821_155007.jpg and /dev/null differ diff --git a/waznexserver/waznexserver.py b/waznexserver/waznexserver.py index 51c70bb..92e93f8 100755 --- a/waznexserver/waznexserver.py +++ b/waznexserver/waznexserver.py @@ -24,6 +24,7 @@ main = Blueprint('main', __name__) + def create_app(): app = Flask(__name__) app.config.from_object(__name__) @@ -36,69 +37,76 @@ def create_app(): return app + # https://stackoverflow.com/a/64076444/ @main.app_template_filter('timeago') def timeago_filter(date): return timeago.format(date, datetime.datetime.now()) - - + + @main.route('/') def index(): # Fetch newest images from DB - grid = db.session.query(models.GridItem).\ - filter_by(status=models.IMAGESTATUS_DONE).\ - order_by(desc('upload_dt')).\ - first() + grid = ( + db.session.query(models.GridItem).filter_by(status=models.IMAGESTATUS_DONE).order_by(desc('upload_dt')).first() + ) app.logger.info(grid) - if grid is not None and \ - grid.level == models.IMAGELEVEL_GRID: - cell_list = db.session.query(models.GridCell).\ - filter_by(fk_grid_item=grid.id).\ - order_by('row').order_by('col').all() + if grid is not None and grid.level == models.IMAGELEVEL_GRID: + cell_list = ( + db.session.query(models.GridCell).filter_by(fk_grid_item=grid.id).order_by('row').order_by('col').all() + ) app.logger.info('Found some cells: ' + str(len(cell_list))) grid.cells = cell_list - return render_template('index.html', - grid=grid, - pretty_dt_format=app.config['PRETTY_DT_FORMAT']) + return render_template('index.html', grid=grid, pretty_dt_format=app.config['PRETTY_DT_FORMAT']) + @main.route('/favicon.ico') def favicon(): filename = request.args.get('filename', 'favicon.ico') - return send_from_directory(os.path.join(app.root_path, 'static', 'favicon'), - filename) + return send_from_directory(os.path.join(app.root_path, 'static', 'favicon'), filename) + @main.route('/thumbnail/') def show_thumbnail(filename): app.logger.info('Serving thumbnail through Flask: ' + filename) return send_from_directory(app.config['THUMBNAIL_FOLDER'], filename) + @main.route('/medium/') def show_downsized(filename): app.logger.info('Serving downsized image through Flask: ' + filename) return send_from_directory(app.config['DOWNSIZED_FOLDER'], filename) + @main.route('/image/') def show_image(filename): app.logger.info('Serving image through Flask: ' + filename) return send_from_directory(app.config['IMAGE_FOLDER'], filename) + @main.route('/sliced//') def show_sliced(dirname, filename): app.logger.info('Serving cell image through Flask: ' + filename) return send_from_directory(app.config['SPLIT_FOLDER'], safe_join(dirname, filename)) + @main.route('/colview//') def show_colview(grid_item_id, col_num): # Get column 0 - Room List # Get column col_num - cells = db.session.query(models.GridCell).\ - filter_by(fk_grid_item=grid_item_id).\ - filter(or_(models.GridCell.col==0, - models.GridCell.col==col_num)).\ - order_by('row').order_by('col').all() - return render_template('colview.html', - cell_list=cells, - ) + cells = ( + db.session.query(models.GridCell) + .filter_by(fk_grid_item=grid_item_id) + .filter(or_(models.GridCell.col == 0, models.GridCell.col == col_num)) + .order_by('row') + .order_by('col') + .all() + ) + return render_template( + 'colview.html', + cell_list=cells, + ) + @main.route('/upload/', methods=['GET', 'POST']) def upload_file(): @@ -109,9 +117,7 @@ def upload_file(): upload_ts = datetime.datetime.utcnow() filename_name, filename_ext = os.path.splitext(f.filename) clean_filename = filename_name.replace('.', '') + filename_ext - filename = ('%sF%s') %\ - (upload_ts.strftime(app.config['FILE_NAME_DT_FORMAT']), - secure_filename(clean_filename)) + filename = upload_ts.strftime(app.config["FILE_NAME_DT_FORMAT"]) + 'F' + secure_filename(clean_filename) f.save(os.path.join(app.config['IMAGE_FOLDER'], filename)) # Initialize GridItem and add it to the list grid_item = models.GridItem(upload_ts, filename) @@ -121,21 +127,21 @@ def upload_file(): app.logger.info('Adding image: ' + filename) flash('Upload successful. Refresh to see it soon', "message-upload-success") else: - flash('Upload failed - invalid file extension.', - "message-upload-fail") + flash('Upload failed - invalid file extension.', "message-upload-fail") return redirect(url_for('main.index')) + def allowed_file(filename): ext = False if '.' in filename: ext = filename.rsplit('.', 1)[1] return ext in app.config['ALLOWED_EXTENSIONS'] + @main.route('/mark_bad/', methods=['GET']) def mark_bad(grid_item_id): try: - bgi = db.session.query(models.GridItem).\ - filter_by(id=grid_item_id).first() + bgi = db.session.query(models.GridItem).filter_by(id=grid_item_id).first() db.session.add(bgi) bgi.status = models.IMAGESTATUS_BAD db.session.commit() @@ -144,7 +150,7 @@ def mark_bad(grid_item_id): # Invalid grid_item_id. Ignore it. pass return redirect(url_for('main.index')) - + if __name__ == '__main__': create_app().run(host='0.0.0.0', port=8080)