Skip to content

Instantly share code, notes, and snippets.

@mufumbo
Created January 21, 2014 19:49
Show Gist options
  • Save mufumbo/8547036 to your computer and use it in GitHub Desktop.
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.
/**
* 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