git » galala.git » master » tree

[master] / galala

#!/usr/bin/env python
# encoding: utf8

import sys
import os
import re
import readline
import shutil

import common
from bottle import SimpleTemplate

from PIL import Image as pil_image
from PIL import ExifTags as pil_exif

def our_data_path():
    "Returns the path to our data."
    # XXX: ugly assumption for now
    return os.path.dirname(os.path.realpath(sys.argv[0]))

# How many degrees counter-clockwise to rotate, according to the exif
# orientation. See http://sylvana.net/jpegcrop/exif_orientation.html.
exif_rotations = {
    3: -180,
    6: -90,
    8: -270,
}

def resize_img(img, x, y, dst):
    "Resizes the given image, and saves it to dst."
    img = pil_image.open(img)
    exif_items = []
    if img._getexif() is not None:
        exif_items = img._getexif().items()

    for k, v in exif_items:
        if k in pil_exif.TAGS and pil_exif.TAGS[k] == 'Orientation':
            if v in exif_rotations:
                img = img.rotate(exif_rotations[v],
                        expand = True)

    img.thumbnail((x, y))
    img.save(dst)

def generate(page_path, dst_path):
    # write the index
    page = common.page_load(page_path)
    index = SimpleTemplate(name = our_data_path() + "/templates/index.html")
    out = index.render(title = page.title,
            introduction = page.introduction,
            albums = page.albums)

    if not os.path.exists(dst_path):
        os.makedirs(dst_path)
    dst = open(dst_path + "/index.html", 'w')
    dst.write(out.encode('utf8'))

    # write the albums
    album_page = SimpleTemplate(name = our_data_path() + "/templates/album.html")
    for a in page.albums:
        out = album_page.render(title = page.title, album = a)
        dst = open(dst_path + "/album-%s.html" % a.shortname, 'w')
        dst.write(out.encode('utf8'))

    # TODO: do we really want to remove it?
    if os.path.exists(dst_path + "/static/"):
        shutil.rmtree(dst_path + "/static/")
    shutil.copytree(our_data_path() + "/static/", dst_path + "/static/")

    # create the needed directories (same as the ones in common.Image)
    if not os.path.exists(dst_path + "/normal/"):
        os.mkdir(dst_path + "/normal/")
    if not os.path.exists(dst_path + "/thumbs/"):
        os.mkdir(dst_path + "/thumbs/")

    dst_dev = os.stat(dst_path).st_dev
    for a in page.albums:
        # the image lives under its album shortname, see
        # Image.albumpath
        if not os.path.exists(dst_path + "/normal/" + a.shortname):
            os.mkdir(dst_path + "/normal/" + a.shortname)
        if not os.path.exists(dst_path + "/thumbs/" + a.shortname):
            os.mkdir(dst_path + "/thumbs/" + a.shortname)

        for i in a.images:
            # hardlink the images if possible, copy otherwise
            #dst_fname = dst_path + "/" + i.imagepath
            #if not os.path.exists(dst_fname):
            #   if os.stat(i.realpath).st_dev == dst_dev:
            #       os.link(i.realpath, dst_fname)
            #   else:
            #       shutil.copy2(i.realpath, dst_fname)

            # create thumbs using PIL
            if not os.path.exists(dst_path + '/' + i.thumbpath):
                resize_img(i.realpath, 256, 256,
                        dst_path + '/' + i.thumbpath)

            # create normal-sized images using PIL
            if not os.path.exists(dst_path + '/' + i.normalpath):
                resize_img(i.realpath, page.resize_to, page.resize_to,
                        dst_path + '/' + i.normalpath)


def new_page(path):
    p = common.Page()
    p.title = raw_input("Title: ")
    p.introduction = raw_input("Introduction: ")
    add_albums(p)
    common.page_save(p, path)

def _add_album(page, shortname, title, text):
    a = common.Album(shortname)

    a.title = title
    if not a.title:
        a.title = a.shortname.decode('utf8')
    a.text = text

    while True:
        img_path = raw_input("Album images path: ")
        if os.path.isdir(img_path):
            fnames = (img_path + '/' + f
                    for f in os.listdir(img_path))
        else:
            fnames = open(img_path).read().split('\n')

        c = 0
        for fname in sorted(set(fnames)):
            fname = os.path.realpath(fname)
            if fname.lower().endswith(".jpg"):
                i = common.Image(a)
                i.realpath = fname
                a.images.append(i)
                c += 1

        if c > 0:
            break
        else:
            print 'ERROR: invalid path'

    page.albums.append(a)

def find_album(page, shortname):
    """Find the album "shortname" on page "page" (the shortname is unique)"""
    for a in page.albums:
        if a.shortname == shortname:
            return a

    return None

def add_albums(page):
    while True:
        shortname = raw_input("Album shortname (empty to skip): ")
        if not shortname:
            break
        if not re.match("^[a-zA-Z0-9_-]+$", shortname):
            print "ERROR: shortname has invalid characters"
            continue
        if find_album(page, shortname):
            print "ERROR: shortname already in use (must be unique)"
            continue

        title = raw_input("Album title: ").decode('utf8')
        text = raw_input("Album text: ").decode('utf8')

        _add_album(page, shortname, title, text)

        print

def delete_album(page, shortname):
    a = find_album(page, shortname)
    if not a:
        print "ERROR: invalid album shortname"
        return False

    page.albums.remove(a)
    return True

def rescan_album(page, shortname):
    # Get the album "data" (title, etc.) to create one with the same data later
    old_a = find_album(page, shortname)
    if not old_a:
        print "ERROR: invalid album shortname"
        return False

    delete_album(page, shortname)

    # This adds the album again re-scanning the images used
    _add_album(page, old_a.shortname, old_a.title,old_a.text)
    return True


HELP = '''
Use one of:

    galala new_page FILENAME
        Creates a new page and will ask to optionally add albums to it. The
        filename given will store the page information in .ini format.

    galala add_albums FILENAME
        Adds albums to the given page. It will ask for the information
        interactively, and update the page file.

    galala delete_album FILENAME ALBUM_SHORTNAME
        This command deletes the album from FILENAME so it will not be included
        the next time you generate. But, keep in mind that the files already
        created for this album (if any) will still be there.

    galala rescan_album FILENAME ALBUM_SHORTNAME
        The same album is preserved, except that it asks you for the image path
        and adds those images. This command is equivalent to delete the album and
        create it again with the same shortname, title and text.

    galala generate FILENAME OUT_PATH
        Generates static HTML for the page given in FILENAME to OUT_PATH.
'''


if __name__ == '__main__':
    if len(sys.argv) <= 2:
        print HELP
        sys.exit(1)

    cmd = sys.argv[1]

    if cmd == 'generate':
        if len(sys.argv) != 4:
            print HELP
            sys.exit(1)
        page_path = sys.argv[2]
        dst_path = sys.argv[3]
        generate(page_path, dst_path)
    elif cmd == 'new_page':
        new_page(sys.argv[2])
    elif cmd == 'add_albums':
        page = common.page_load(sys.argv[2])
        add_albums(page)
        common.page_save(page, sys.argv[2])
    elif cmd in ['delete_album', 'rescan_album']:
        if len(sys.argv) != 4:
            print HELP
            sys.exit(1)
        page = common.page_load(sys.argv[2])
        shortname = sys.argv[3]
        if cmd == 'delete_album':
            success = delete_album(page, shortname)
        elif cmd == 'rescan_album':
            success = rescan_album(page, shortname)

        if success:
            common.page_save(page, sys.argv[2])
    else:
        print "ERROR: unknown command"
        sys.exit(1)