Created
January 21, 2014 19:49
-
-
Save mufumbo/8547036 to your computer and use it in GitHub Desktop.
An extension over google's servingUrl "cdn" features: https://developers.google.com/appengine/docs/java/images/ It links to the Media datastore object and also provides rectangular cropping.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* An extension over google's servingUrl "cdn" features: https://developers.google.com/appengine/docs/java/images/ | |
* | |
* It links to the Media datastore object and also provides rectangular cropping. | |
* | |
* Examples: | |
* /c/i/69589306 | |
* /c/i/69589306=s300 | |
* /c/i/69589306=s300-c | |
* /c/i/69589306=s300-c=h20 | |
* | |
* TODO: add watermark functionality. | |
* | |
* TODO: Compare mediaId against PRODUCTION_SERVING_URLS to check if it's possible. | |
* TODO: That way it's safer to detect someone trying to download larges portion | |
* TODO: of the site with a simple sequential script. | |
* | |
* @author mufumbo | |
*/ | |
public class ImagesExtendedApiServlet extends HttpServlet { | |
final static Logger logger = Logger.getLogger(ImagesExtendedApiServlet.class.getName()); | |
final static int MAX_SIZE = 1500; | |
static ImagesService imagesService = ImagesServiceFactory.getImagesService(); | |
static BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService(); | |
@Override | |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
String uri = req.getRequestURI(); | |
logger.warning("processing " + uri + " host " + req.getHeader("Host")); | |
try { | |
String[] spl = uri.split("/"); | |
String type = spl[2]; | |
if ("i".equals(type)) { | |
String content = spl[3]; | |
String[] splContent = content.split("="); | |
long mediaId = Long.parseLong(splContent[0]); | |
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); | |
Key key = KeyFactory.createKey("Media", mediaId); | |
Entity media = null; | |
try { | |
media = datastore.get(key); | |
} | |
catch (EntityNotFoundException enfe) { | |
logger.log(Level.WARNING, "couldn't find media with id " + mediaId); | |
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); | |
} | |
if (media != null) { | |
BlobKey oldBlobKey = (BlobKey) media.getProperty("image"); | |
BlobKey newBlobKey = oldBlobKey; | |
/*BlobKey newBlobKey = BlobMigrationRecord.getNewBlobKey(oldBlobKey); | |
if (!newBlobKey.equals(oldBlobKey)) { | |
// this shouldn't ever be happening as we should migrate everything in Media! | |
logger.warning("blobKey changed from " + oldBlobKey + " to " + newBlobKey); | |
} */ | |
Image newImage = ImagesServiceFactory.makeImageFromBlob(newBlobKey); | |
OutputSettings settings = new OutputSettings(ImagesService.OutputEncoding.JPEG); | |
settings.setQuality(83); | |
if (splContent.length > 1) { | |
int width = 0, height = 0; | |
for (int i = 1; i < splContent.length; i++) { | |
String cur = splContent[i]; | |
if (cur.startsWith("s") && cur.endsWith("-c")) { | |
String w = cur.substring(1, cur.length() - 2); | |
width = height = Integer.parseInt(w); | |
logger.info("Cropping width[" + width + "]"); | |
} else if (cur.startsWith("s")) { | |
String w = cur.substring(1, cur.length()); | |
width = Integer.parseInt(w); | |
logger.info("Resizing image width[" + width + "]"); | |
} else if (cur.startsWith("h")) { | |
String h = cur.substring(1, cur.length()); | |
height = Integer.parseInt(h); | |
logger.info("Cropping height[" + height + "]"); | |
} else { | |
logger.warning("unrecognized transformation " + cur); | |
} | |
} | |
if (width > MAX_SIZE || height > MAX_SIZE) | |
throw new Exception(MAX_SIZE + " is too big"); | |
if (width > 0 && height == 0) { | |
Transform resize = ImagesServiceFactory.makeResize(width, width, false); | |
newImage = imagesService.applyTransform(resize, newImage, settings); | |
//ensureImageFetch(newImage); | |
//float scale = (float) width / (float) newImage.getWidth(); | |
//height = (int) (newImage.getHeight() * scale); | |
//logger.info("Scaling[" + scale + "][" + width + "] height to " + height + " from " + newImage.getHeight()); | |
} | |
else { | |
Transform transform = ImagesServiceFactory.makeResize(width, height, 0.5, 0.5); | |
newImage = imagesService.applyTransform(transform, newImage, settings); | |
} | |
} else { | |
ensureImageFetch(newImage, settings); | |
logger.info("full content download width[" + newImage.getWidth() + "] height[" + newImage.getHeight() + "]"); | |
} | |
/* | |
ImagesServiceFactory.makeImageFromFilename(); | |
InputStream stream = ImagesExtendedApiServlet.class.getResourceAsStream("default_logo_nopadding.png"); | |
Image wm = ImagesServiceFactory.makeImage(asByteArray(stream)); | |
Image wmComposite = ImagesServiceFactory.makeComposite(wm, width - , 30, 1.0f, Composite.Anchor.TOP_LEFT); | |
composites.add(wmComposite); | |
*/ | |
byte[] imageData = newImage.getImageData(); | |
// int beforeStrip = imageData.length; | |
// No need to strip EXIF data off as appengine seems to do this already. | |
// ByteArrayOutputStream baos = new ByteArrayOutputStream(imageData.length); | |
// new ExifRewriter().removeExifMetadata(imageData, baos);//.removeExifMetadata(jpegImageFile, os); | |
// imageData = baos.toByteArray(); | |
// logger.info("streaming nostrip[" + beforeStrip + "] after[" + imageData.length + "]"); | |
resp.getOutputStream().write(imageData); | |
resp.setHeader("Content-Type", "image/jpeg"); | |
} | |
else { | |
resp.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); | |
} | |
} | |
else if ("v".equals(type)) { | |
String content = spl[3]; | |
String[] splContent = content.split("="); | |
long mediaId = Long.parseLong(splContent[0].indexOf('.') < 0 ? splContent[0] : splContent[0].substring(0, splContent[0].indexOf('.'))); | |
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); | |
Key key = KeyFactory.createKey("Media", mediaId); | |
Entity media = null; | |
try { | |
media = datastore.get(key); | |
} | |
catch (EntityNotFoundException enfe) { | |
logger.log(Level.WARNING, "couldn't find media with id " + mediaId); | |
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); | |
} | |
if (media != null) { | |
BlobKey video = (BlobKey) media.getProperty("video"); | |
BlobInfoFactory bif = new BlobInfoFactory(); | |
BlobInfo info = bif.loadBlobInfo(video); | |
resp.setContentType(info.getContentType()); | |
resp.setHeader("Accept-Ranges", "bytes"); | |
if (req.getHeader("Range") != null) { | |
try { | |
ByteRange range = ByteRange.parse(req.getHeader("Range")); | |
blobstoreService.serve(video, range, resp); | |
} | |
catch (Exception ex){ | |
logger.fine("Error parsing byte range: " + String.valueOf(req.getHeader("Range"))); | |
blobstoreService.serve(video, resp); | |
} | |
} | |
else | |
blobstoreService.serve(video, resp); | |
} | |
else { | |
resp.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); | |
} | |
} | |
} catch (Exception e) { | |
logger.log(Level.WARNING, "couldn't provision " + uri, e); | |
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); | |
} | |
} | |
private static Image ensureImageFetch(Image img, OutputSettings settings) { | |
Transform resize = ImagesServiceFactory.makeResize(800, 800, false); | |
Image newImage = imagesService.applyTransform(resize, img, settings); | |
logger.info("Original image[" + newImage.getImageData().length + "] width[" + newImage.getWidth() + "] height[" + newImage.getHeight() + "]"); | |
return newImage; | |
} | |
public static byte[] asByteArray(InputStream stream) throws IOException { | |
final ByteArrayOutputStream buf = new ByteArrayOutputStream(); | |
final byte[] buffer = new byte[32768]; | |
int cbRead; | |
while ((cbRead = stream.read(buffer, 0, buffer.length)) != -1) | |
{ | |
buf.write(buffer, 0, cbRead); | |
} | |
return buf.toByteArray(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment