Skip to content

Commit 199ddae

Browse files
committed
Initial commit.
0 parents  commit 199ddae

File tree

16 files changed

+663
-0
lines changed

16 files changed

+663
-0
lines changed

.hgignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# use glob syntax.
2+
syntax: glob
3+
4+
.DS_Store
5+
.svn
6+
*.svn-base
7+
.cvs
8+
*.elc
9+
*.pyc
10+
*~
11+
.*.swp
12+
nbproject
13+
build/
14+
*.mode1v3
15+
*.pbxuser
16+
*.db
17+
*.orig
18+
19+
# switch to regexp syntax.
20+
syntax: regexp
21+
^.pc/
22+
^sizr/uploads
23+
^sizr/bucket
24+
^sizr/db.sql
25+
^sizr/out.html
26+
^sizr/sizr/settings_local.py
27+
^sizr/.idea/
28+
^sizr/out.png
29+
^.idea/
30+
^sizr/static/

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# sizr - Fast scaling of images for web and mobile with caching
2+
3+
sizr is a django app that can resize images on the fly with just a GET request. sizr then fetches the image, resizes it caches the image in a "bucket" and redirects you to the local copy. The next request then gets redirected directly.
4+
5+
At the moment sizr has the status "prove of concept", so don't expect it to work with every image format or think of it as unbreakable.
6+
7+
# Dependencies
8+
9+
* Django 1.4.10
10+
* Grapelli 2.4.8
11+
* Python Requests (>= 0.8.2-1 | shipped with Ubuntu 12.04)
12+
13+
14+
# Some notes on base64
15+
16+
sizr uses base64 to encode URLs. But it has to be URL-safe, which standard base64 isn't.
17+
18+
iOS: http://stackoverflow.com/questions/11106393/url-safe-base64-in-objective-c
19+
Java: http://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Base64.html#Base64%28boolean%29
20+
Perl: http://search.cpan.org/~kazuho/MIME-Base64-URLSafe-0.01/lib/MIME/Base64/URLSafe.pm

examples/UrlsafeBase64.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
public static byte[] encodeUrlSafe(byte[] data) {
2+
byte[] encode = Base64.encode(data);
3+
for (int i = 0; i < encode.length; i++) {
4+
if (encode[i] == '+') {
5+
encode[i] = '-';
6+
} else if (encode[i] == '/') {
7+
encode[i] = '_';
8+
}
9+
}
10+
return encode;
11+
}
12+
13+
public static byte[] decodeUrlSafe(byte[] data) {
14+
byte[] encode = Arrays.copyOf(data, data.length);
15+
for (int i = 0; i < encode.length; i++) {
16+
if (encode[i] == '-') {
17+
encode[i] = '+';
18+
} else if (encode[i] == '_') {
19+
encode[i] = '/';
20+
}
21+
}
22+
return Base64.decode(encode);
23+
}

examples/get_image.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
5+
import requests
6+
from urllib2 import quote
7+
from base64 import urlsafe_b64encode
8+
9+
10+
#url = "http://localhost:8000/resize/?url=%s&x=50&y=50" % (quote("http://dajool.com/public/images/dajool_badge.png"), )
11+
url = "http://localhost:8000/thumbnail/%s/50/50/" % (urlsafe_b64encode("http://dajool.com/public/images/dajool_badge.png"), )
12+
13+
request = requests.get(url)
14+
print request.content

examples/test.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<html>
2+
<body>
3+
4+
<img src="http://sizr.pelicanbay.de/thumbnail/aHR0cDovL2Rham9vbC5jb20vcHVibGljL2ltYWdlcy9kYWpvb2xfYmFkZ2UucG5n/500/500/" />
5+
<img src="http://sizr.pelicanbay.de/thumbnail/aHR0cHM6Ly93d3cuZ29vZ2xlLmRlL2ltYWdlcy9zcnByL2xvZ280dy5wbmc=/90/90/" />
6+
<img src="http://sizr.pelicanbay.de/thumbnail/aHR0cDovL2Rham9vbC5jb20vcHVibGljL2ltYWdlcy9wb3N0cy9tb2JpbGVfc29sdXRpb25fZW5lcmd5LnBuZw==/80/80/" />
7+
8+
</body
9+
</html>

examples/urlsafe_b64encode.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function urlsafe_b64encode(url){
2+
var b64url = btoa(url);
3+
return b64url.replace(/\+/g, '-').replace(/\//g, '_');
4+
}

sizr/image_sizr/__init__.py

Whitespace-only changes.

sizr/image_sizr/admin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from django.contrib import admin
4+
from image_sizr.models import Image
5+
6+
class ImageAdmin(admin.ModelAdmin):
7+
pass
8+
9+
admin.site.register(Image, ImageAdmin)

sizr/image_sizr/models.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# -*- coding: utf-8 -*-
2+
3+
__author__ = 'Jochen Breuer <[email protected]>'
4+
__copyright__ = 'Jochen Breuer <[email protected]>'
5+
__license__ = 'BSD 3-Clause License'
6+
7+
import os
8+
import random
9+
import string
10+
import logging
11+
12+
from shutil import rmtree
13+
14+
from django.db import models
15+
from django.conf import settings
16+
from django.dispatch import receiver
17+
from django.db.models.signals import pre_delete
18+
from django.utils.translation import ugettext_lazy as _
19+
20+
21+
# returns the absolute path to this file extended by the parameter
22+
abs_path = lambda parameter: os.path.join(
23+
os.path.dirname(os.path.abspath(__file__)),
24+
parameter)
25+
26+
# Get an instance of a logger
27+
logger = logging.getLogger(__name__)
28+
29+
class Image(models.Model):
30+
"""\
31+
32+
"""
33+
34+
url_hash = models.CharField(max_length=32, primary_key=True)
35+
url = models.URLField()
36+
bucket = models.CharField(max_length=2, blank=True, null=True)
37+
suffix = models.CharField(max_length=8)
38+
# regarding the length of the mime type field:
39+
# http://tools.ietf.org/html/rfc4288#section-4.2 | 127 + 1 + 127 = 255
40+
mime_type = models.CharField(max_length=255)
41+
img_hash = models.CharField(max_length=255,
42+
help_text=_(u"""This is either the etag or a md5sum
43+
generated from last-modified and content-length"""))
44+
45+
class Meta:
46+
verbose_name = _(u"Image")
47+
verbose_name_plural = _(u"Images")
48+
49+
def __unicode__(self):
50+
return self.url_hash
51+
52+
def save(self, *args, **kwargs):
53+
"""\
54+
Randomly choose bucket and save.
55+
"""
56+
model = self.__class__
57+
if self.bucket is None or self.bucket == "":
58+
self.bucket = ''.join(random.choice(string.digits) for x in range(2))
59+
mkdir_p(self.path_to_images())
60+
return super(Image, self).save(*args, **kwargs)
61+
62+
def path_to_images(self):
63+
return os.path.join(settings.BUCKET_ROOT, self.bucket, self.url_hash, self.img_hash)
64+
65+
def path_to_original_image(self):
66+
return os.path.join(self.path_to_images(), self.img_hash + '.' + self.suffix)
67+
68+
69+
@receiver(pre_delete)
70+
def del_images(sender, instance, **kwargs):
71+
"""\
72+
Also delete images from Bucket when dataset is deleted.
73+
"""
74+
if sender==Image:
75+
try:
76+
rmtree(instance.path_to_images())
77+
except:
78+
logger.error("Couldn't remove bucket: %s" % instance.path_to_images())
79+
80+
81+
def mkdir_p(path):
82+
if not os.path.isdir(path):
83+
os.makedirs(path)

sizr/image_sizr/tests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
This file demonstrates writing tests using the unittest module. These will pass
3+
when you run "manage.py test".
4+
5+
Replace this with more appropriate tests for your application.
6+
"""
7+
8+
from django.test import TestCase
9+
10+
11+
class SimpleTest(TestCase):
12+
def test_basic_addition(self):
13+
"""
14+
Tests that 1 + 1 always equals 2.
15+
"""
16+
self.assertEqual(1 + 1, 2)

0 commit comments

Comments
 (0)