Commits

Mark Sundstrom committed 80f1c3a

reorganized so that app and project are combined

Comments (0)

Files changed (31)

+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == '__main__':
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'timelapse.settings')
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)

pics/__init__.py

Empty file removed.

pics/admin.py

-from pics.models import Picture
-from django.contrib import admin
-
-class PictureAdmin(admin.ModelAdmin):
-    date_hierarchy = 'timestamp'
-    ordering = ('-timestamp',)
-    list_display = ('filename', 'timestamp', 'valid', 'size')
-    list_filter = ('valid',)
-
-admin.site.register(Picture, PictureAdmin)

pics/image_sequence.py

-#!/usr/bin/env python
-# -*- coding: utf8
-"""
-    use ffmpeg for creating timelapse movies from a sequence of images
-"""
-from __future__ import print_function
-import os
-import tempfile
-import shutil
-import subprocess
-
-
-TEMPDIR_BASE = '/var/tmp/timelapse'  # keep all the temp stuff in one spot
-if not os.path.exists(TEMPDIR_BASE):
-    os.mkdir(TEMPDIR_BASE)
-
-
-class ImageSequence(object):
-    """
-        Collect a sequence of images (assumed to be added in the
-        correct sequence) so that a timelapse movie can be created
-        from them.
-
-        TODO – we may eventually allow for adding images that are
-        not from the Picture table
-    """
-    def __init__(self):
-        self.images = []
-
-    def add_picture(self, picture):
-        self.images.append(picture)
-
-    def make_timelapse(self, name, frames_per_second=10):
-        # get a temporary place to write all the images
-        tempdir = tempfile.mkdtemp(dir=TEMPDIR_BASE)
-        for sequence, image in enumerate(self.images, start=1):
-            dst = '%s/%04d.jpg' % (tempdir, sequence)
-            shutil.copyfile(image.filepath, dst)
-
-        # I am clueless about all the ffmpeg options,
-        # but the following is working okay so far.
-        imagedir = '{}/%*.jpg'.format(tempdir)
-        args = ['/usr/local/bin/ffmpeg',
-            '-r', '12',
-            '-i', imagedir,
-            '-an',
-            '-vcodec', 'libx264',
-            '-b:v', '1200k',
-            '-s', '640x480',
-            name]
-        print(' '.join(args))
-        subprocess.call(args)
-
-if __name__ == '__main__':
-    # Clean up the temp directory
-    shutil.rmtree(TEMPDIR_BASE)

pics/management/__init__.py

Empty file removed.

pics/management/commands/__init__.py

Empty file removed.

pics/management/commands/addpics.py

-"""
-    Add new pictures to the database. I've modeled this after
-    Eric Floehr's daystrips project, i.e., the addpics management command.
-    I don't know yet if I want all the same data gathered from the image,
-    but this is a good place to start.
-
-    Now that I've switched to the D-Link camera, the DCS-930L, I don't
-    expect to go back to the images generated by the iSight, as the D-Link
-    is so much better.
-"""
-import sys
-import os
-from django.core.management.base import NoArgsCommand, CommandError
-import datetime
-from collections import defaultdict, namedtuple
-from pics.models import Picture
-from PIL import Image, ImageStat
-from django.utils.timezone import utc, get_default_timezone, make_aware
-from pics.util import rgb_to_int
-import logging
-
-logger = logging.getLogger(__name__)
-
-# Set up defaults
-image_src = os.environ['TIMELAPSE_SOURCE']
-camera_width = 640
-camera_height = 480
-camera_center = (camera_width / 2, camera_height / 2)
-Item = namedtuple('Item', 'dirpath filename date')
-
-
-def generate_all_image_files():
-    """
-        filenames from the D-Link camera look like:
-            dlinkYYYYMMDDHHMMSSnn.jpg
-        with the time being in UTC.
-    """
-    FORMAT = 'dlink%Y%m%d%H%M%S'
-    for dirpath, dirnames, filenames in os.walk(image_src):
-        for name in filenames:
-            if name.startswith('dlink'):
-                date = make_aware(
-                    datetime.datetime.strptime(name[:19], FORMAT), utc)
-                yield Item(dirpath, name, date)
-
-class Command(NoArgsCommand):
-    def handle_noargs(self, **options):
-        logger.debug("addpics start: {} pictures".format(Picture.objects.count()))
-        latest = Picture.objects.latest('timestamp')
-        logger.info("Adding images created after {}".format(latest.timestamp))
-        for item in generate_all_image_files():
-            if item.date <= latest.timestamp:
-                continue
-            pic, created = Picture.objects.get_or_create(timestamp=item.date)
-            assert created
-
-            pic.filepath = os.path.join(item.dirpath, item.filename)
-            pic.filename = item.filename
-            filestat = os.stat(pic.filepath)
-            pic.file_timestamp = datetime.datetime.fromtimestamp(
-                int(filestat.st_mtime), utc)
-            pic.size = filestat.st_size
-
-            try:
-                im = Image.open(pic.filepath)
-                stats = ImageStat.Stat(im)
-                pic.center_color = rgb_to_int(im.getpixel(camera_center))
-                pic.mean_color = rgb_to_int(stats.mean)
-                pic.median_color = rgb_to_int(stats.median)
-                pic.stddev_red, pic.stddev_green, pic.stddev_blue = [
-                    int(round(i))
-                    for i in stats.stddev[0:3]
-                    ]
-
-                exc = stats.extrema
-
-                pic.min_color = rgb_to_int((exc[0][0], exc[1][0], exc[2][0]))
-                pic.max_color = rgb_to_int((exc[0][1], exc[1][1], exc[2][1]))
-
-                pic.valid = True
-
-                if im.size != (640, 480):
-                    pic.valid = False
-
-            except:
-                pic.valid = False
-            pic.save()
-            logger.info('{} {}'.format(
-                item.date.astimezone(get_default_timezone()), item.filename))
-        logger.debug("addpics finish: {} pictures".format(Picture.objects.count()))
-

pics/models.py

-from django.db import models
-from django.db.models.signals import pre_save
-from django.dispatch import receiver
-import datetime
-
-
-class PictureManager(models.Manager):
-    def dates(self):
-        "Return a DateQuerySet of all dates"
-        return self.get_query_set().dates('timestamp', 'day')
-
-
-class Picture(models.Model):
-    """Mostly copied from Eric Floehr's daystrips project.
-       It stores everything we might want to conveniently
-       know about an image.
-    """
-    filepath = models.CharField(max_length=1024)
-    filename = models.CharField(max_length=1024)
-
-    timestamp = models.DateTimeField(db_index=True)
-    file_timestamp = models.DateTimeField(null=True)
-
-    # Auto-populated, don't manually enter
-    hour = models.SmallIntegerField(null=True, db_index=True)
-    minute = models.SmallIntegerField(null=True, db_index=True)
-    file_hour = models.SmallIntegerField(null=True)
-    file_minute = models.SmallIntegerField(null=True)
-    # End auto-populated fields
-
-    size = models.IntegerField(default=0)
-
-    center_color = models.IntegerField(null=True)
-    mean_color = models.IntegerField(null=True)
-    median_color = models.IntegerField(null=True)
-
-    stddev_red = models.IntegerField(null=True)
-    stddev_green = models.IntegerField(null=True)
-    stddev_blue = models.IntegerField(null=True)
-
-    min_color = models.IntegerField(null=True)
-    max_color = models.IntegerField(null=True)
-
-    valid = models.BooleanField(default=False)
-
-    class Meta:
-        ordering = ['timestamp']
-
-    objects = PictureManager()
-
-
-@receiver(pre_save, sender=Picture)
-def pre_save_picture(sender, **kwargs):
-    pic = kwargs['instance']
-    pic.hour = pic.timestamp.hour
-    pic.minute = pic.timestamp.minute
-    if pic.file_timestamp is not None:
-        pic.file_hour = pic.file_timestamp.hour
-        pic.file_minute = pic.file_timestamp.minute

pics/util.py

-#!/usr/bin/env python
-# -*- coding: utf8
-"""
-    We store color values as integers in the database.
-    These functions do the conversions back and forth.
-"""
-from __future__ import print_function
-from collections import namedtuple
-
-
-def rgb_to_int(rgb):
-    "(red, green, blue) tuple --> integer"
-    return int(round(rgb[0] * 256 * 256 + rgb[1] * 256 + rgb[2]))
-
-
-def int_to_rgb(i):
-    "integer --> (red, green, blue)"
-    r = (i >> 16) & 255
-    g = (i >> 8) & 255
-    b = i & 255
-    return (r, g, b)

pics/views.py

-# Create your views here.

rsync_from_avocet.sh

+#!/bin/sh
+# Use rsync to get time lapse images from the laptop with the images
+rsync -av mark@avocet.local:Desktop/timelapse/ $TIMELAPSE_SOURCE

scripts/daily.py

-#!/usr/bin/env python
-# -*- coding: utf8
-"""
-    Create movies with all the images from one day.
-    This is so we can easily find things that might be interesting.
-    For now we limit to images where the Sun is 7° below the horizon
-    or higher.
-
-    We use TIMELAPSE_DAILY to find where to put these movies
-"""
-from __future__ import print_function
-import sys
-import os
-import datetime
-from pics.models import Picture
-from django.utils.timezone import utc, get_default_timezone, make_aware
-from pics.image_sequence import ImageSequence
-import sky
-
-# Choose defaults from the environment.
-# I set these defaults in the postactivate script of the virtualenv
-tzinfo = get_default_timezone()
-observer = sky.observer_from_location()
-overwrite = False  # always write the output files?
-include_night = False
-
-
-def generate_dates():
-    "Generate all dates"
-    for date in Picture.objects.dates():
-        yield date.date()
-
-
-for date in generate_dates():
-    print("Checking", date)
-    outpath = os.path.join(os.environ['TIMELAPSE_DAILY_MOVIES'],
-        '{:%Y-%m-%d}.mp4'.format(date))
-    if os.path.exists(outpath) and (not overwrite):
-        continue
-    dirpath = os.path.dirname(outpath)
-    if not os.path.exists(dirpath):
-        os.makedirs(dirpath)
-    if include_night:
-        start = make_aware(datetime.datetime.combine(date,
-            datetime.time(0, 0, 0)), tzinfo)
-        end = start + datetime.timedelta(days=1)
-    else:
-        suntimes = sky.suntimes_for_date(observer, date, '-8')
-        # seconds are floating point, so just ignore them
-        start = make_aware(datetime.datetime(*suntimes.dawn.tuple()[:5]), utc)
-        end = make_aware(datetime.datetime(*suntimes.dusk.tuple()[:5]), utc)
-    qs = Picture.objects.filter(timestamp__gte=start,
-        timestamp__lt=end).order_by('timestamp')
-    if qs.count() > 0:
-        print('{} {:4} images'.format(date, qs.count()))
-        movie = ImageSequence()
-        [movie.add_picture(obj) for obj in qs]
-        movie.make_timelapse(outpath)

scripts/rsync_from_avocet.sh

-#!/bin/sh
-# Use rsync to get time lapse images from the laptop with the images
-rsync -av mark@avocet.local:Desktop/timelapse/ $TIMELAPSE_SOURCE

scripts/sandbox.py

-#!/usr/bin/env python
-from __future__ import print_function
-from collections import defaultdict
-from pics.management.commands.addpics import generate_all_image_files
-
-days = defaultdict(list)
-
-for item in generate_all_image_files():
-    day = item.date.date()
-    days[day].append(item)
-
-for key in sorted(days.keys()):
-    items = days[key]
-    items.sort(key=lambda x:x.date)
-    print('{} {:4} {:%H:%M:%S} {:%H:%M:%S}'.format(
-        key, len(items), items[0].date, items[-1].date))

scripts/sky.py

-#!/usr/bin/env python
-# -*- coding: utf8
-"""
-    Calculate the approximate times of full darkness for the
-    next day, so we can set the schedule for the webcam. For the
-    current location, pictures taken at night are not that
-    interesting and don't vary from one night to the next, so
-    I'll turn the camera off when it gets dark and turn it on
-    in the morning when it starts getting light.
-
-    Civil twilight is defined as the Sun being 6° below the
-    horizon. From examining images, it looks like choosing
-    8° is a good choice for me.
-"""
-from __future__ import print_function
-import sys
-import os
-import datetime
-from collections import namedtuple
-import ephem
-
-HORIZON = '-8'
-
-sun = ephem.Sun()
-
-
-def observer_from_location():
-    "Create an ephem.Observer object from our location"
-    observer = ephem.Observer()
-    observer.lat = str(os.environ['LATITUDE'])
-    observer.lon = str(os.environ['LONGITUDE'])
-    observer.elevation = os.environ.get('ELEVATION', 750)
-    observer.date = ephem.now()  # useful default?
-    return observer
-
-SunTimes = namedtuple('SunTimes', 'dawn sunrise sunset dusk')
-
-
-def suntimes_for_date(observer, date, horizon=HORIZON):
-    """
-    Calculate rise and set times for a given date and observer,
-    along with the times of twilight using our default horizon.
-    Civil twilight is usually calculated for 6°.
-    """
-    observer.date = ephem.Date((date.year, date.month, date.day, 12, 0, 0))
-    sunrise = observer.previous_rising(sun)
-    sunset = observer.next_setting(sun)
-    observer.horizon = horizon
-    dawn = observer.previous_rising(sun, use_center=True)
-    dusk = observer.next_setting(sun, use_center=True)
-    return SunTimes(dawn, sunrise, sunset, dusk)
-
-if __name__ == '__main__':
-    print("Sun times for today, horizon of {}°".format(HORIZON))
-    observer = observer_from_location()
-    times = suntimes_for_date(observer, datetime.date.today())
-    for field in times._fields:
-        value = getattr(times, field)
-        print('{:7} {} UTC or {:%H:%M:%S} local'.format(
-            field, value, ephem.localtime(value)))

scripts/update.sh

-#!/bin/bash
-source ~/.bashrc
-workon timelapse
-bin/rsync_from_avocet.sh
-django-admin.py addpics
-/usr/local/bin/growlnotify -m "timelapse update complete"

setup.py

-from distutils.core import setup
-
-setup(
-    name='timelapse',
-    version='0.2',
-    description='My time lapse project',
-    author='Mark Sundstrom',
-    url='https://bitbucket.org/mhsundstrom/timelapse',
-    packages = ['pics', 'timelapse'],
-    scripts = [
-        'scripts/rsync_from_avocet.sh',
-        'scripts/sky.py',
-        'scripts/daily.py',
-        'scripts/update.sh',
-    ],
-)
-

timelapse/admin.py

+from .models import Picture
+from django.contrib import admin
+
+class PictureAdmin(admin.ModelAdmin):
+    date_hierarchy = 'timestamp'
+    ordering = ('-timestamp',)
+    list_display = ('filename', 'timestamp', 'valid', 'size')
+    list_filter = ('valid',)
+
+admin.site.register(Picture, PictureAdmin)

timelapse/daily.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    python -m timelapse.daily
+
+    Create movies with all the images from one day.
+    This is so we can easily find things that might be interesting.
+    For now we limit to images where the Sun is 7° below the horizon
+    or higher.
+
+    We use TIMELAPSE_DAILY to find where to put these movies
+"""
+from __future__ import print_function
+import sys
+import os
+import datetime
+from .models import Picture
+from django.utils.timezone import utc, get_default_timezone, make_aware
+from .image_sequence import ImageSequence
+from .sky import observer_from_location, suntimes_for_date
+
+# Choose defaults from the environment.
+# I set these defaults in the postactivate script of the virtualenv
+tzinfo = get_default_timezone()
+observer = observer_from_location()
+overwrite = False  # always write the output files?
+include_night = False
+
+
+def generate_dates():
+    "Generate all dates"
+    for date in Picture.objects.dates():
+        yield date.date()
+
+
+for date in generate_dates():
+    print("Checking", date)
+    outpath = os.path.join(os.environ['TIMELAPSE_DAILY_MOVIES'],
+        '{:%Y-%m-%d}.mp4'.format(date))
+    if os.path.exists(outpath) and (not overwrite):
+        continue
+    dirpath = os.path.dirname(outpath)
+    if not os.path.exists(dirpath):
+        os.makedirs(dirpath)
+    if include_night:
+        start = make_aware(datetime.datetime.combine(date,
+            datetime.time(0, 0, 0)), tzinfo)
+        end = start + datetime.timedelta(days=1)
+    else:
+        suntimes = suntimes_for_date(observer, date, '-8')
+        # seconds are floating point, so just ignore them
+        start = make_aware(datetime.datetime(*suntimes.dawn.tuple()[:5]), utc)
+        end = make_aware(datetime.datetime(*suntimes.dusk.tuple()[:5]), utc)
+    qs = Picture.objects.filter(timestamp__gte=start,
+        timestamp__lt=end).order_by('timestamp')
+    if qs.count() > 0:
+        print('{} {:4} images'.format(date, qs.count()))
+        movie = ImageSequence()
+        [movie.add_picture(obj) for obj in qs]
+        movie.make_timelapse(outpath)

timelapse/image_sequence.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    use ffmpeg for creating timelapse movies from a sequence of images
+"""
+from __future__ import print_function
+import os
+import tempfile
+import shutil
+import subprocess
+
+
+TEMPDIR_BASE = '/var/tmp/timelapse'  # keep all the temp stuff in one spot
+if not os.path.exists(TEMPDIR_BASE):
+    os.mkdir(TEMPDIR_BASE)
+
+
+class ImageSequence(object):
+    """
+        Collect a sequence of images (assumed to be added in the
+        correct sequence) so that a timelapse movie can be created
+        from them.
+
+        TODO – we may eventually allow for adding images that are
+        not from the Picture table
+    """
+    def __init__(self):
+        self.images = []
+
+    def add_picture(self, picture):
+        self.images.append(picture)
+
+    def make_timelapse(self, name, frames_per_second=12):
+        # get a temporary place to write all the images
+        tempdir = tempfile.mkdtemp(dir=TEMPDIR_BASE)
+        for sequence, image in enumerate(self.images, start=1):
+            dst = '%s/%04d.jpg' % (tempdir, sequence)
+            shutil.copyfile(image.filepath, dst)
+
+        # I am clueless about all the ffmpeg options,
+        # but the following is working okay so far.
+        imagedir = '{}/%*.jpg'.format(tempdir)
+        args = ['/usr/local/bin/ffmpeg',
+            '-r', str(frames_per_second),
+            '-i', imagedir,
+            '-an',
+            '-vcodec', 'libx264',
+            '-b:v', '1200k',
+            '-s', '640x480',
+            name]
+        print(' '.join(args))
+        subprocess.call(args)
+
+if __name__ == '__main__':
+    # Clean up the temp directory
+    shutil.rmtree(TEMPDIR_BASE)

timelapse/management/__init__.py

Empty file added.

timelapse/management/commands/__init__.py

Empty file added.

timelapse/management/commands/addpics.py

+"""
+    Add new pictures to the database. I've modeled this after
+    Eric Floehr's daystrips project, i.e., the addpics management command.
+    I don't know yet if I want all the same data gathered from the image,
+    but this is a good place to start.
+
+    Now that I've switched to the D-Link camera, the DCS-930L, I don't
+    expect to go back to the images generated by the iSight, as the D-Link
+    is so much better.
+"""
+import sys
+import os
+from django.core.management.base import NoArgsCommand, CommandError
+import datetime
+from collections import defaultdict, namedtuple
+from timelapse.models import Picture
+from PIL import Image, ImageStat
+from django.utils.timezone import utc, get_default_timezone, make_aware
+from timelapse.util import rgb_to_int
+import logging
+
+logger = logging.getLogger(__name__)
+
+# Set up defaults
+image_src = os.environ['TIMELAPSE_SOURCE']
+camera_width = 640
+camera_height = 480
+camera_center = (camera_width / 2, camera_height / 2)
+Item = namedtuple('Item', 'dirpath filename date')
+
+
+def generate_all_image_files():
+    """
+        filenames from the D-Link camera look like:
+            dlinkYYYYMMDDHHMMSSnn.jpg
+        with the time being in UTC.
+    """
+    FORMAT = 'dlink%Y%m%d%H%M%S'
+    for dirpath, dirnames, filenames in os.walk(image_src):
+        for name in filenames:
+            if name.startswith('dlink'):
+                date = make_aware(
+                    datetime.datetime.strptime(name[:19], FORMAT), utc)
+                yield Item(dirpath, name, date)
+
+class Command(NoArgsCommand):
+    def handle_noargs(self, **options):
+        logger.debug("addpics start: {} pictures".format(Picture.objects.count()))
+        latest = Picture.objects.latest('timestamp')
+        logger.info("Adding images created after {}".format(latest.timestamp))
+        for item in generate_all_image_files():
+            if item.date <= latest.timestamp:
+                continue
+            pic, created = Picture.objects.get_or_create(timestamp=item.date)
+            assert created
+
+            pic.filepath = os.path.join(item.dirpath, item.filename)
+            pic.filename = item.filename
+            filestat = os.stat(pic.filepath)
+            pic.file_timestamp = datetime.datetime.fromtimestamp(
+                int(filestat.st_mtime), utc)
+            pic.size = filestat.st_size
+
+            try:
+                im = Image.open(pic.filepath)
+                stats = ImageStat.Stat(im)
+                pic.center_color = rgb_to_int(im.getpixel(camera_center))
+                pic.mean_color = rgb_to_int(stats.mean)
+                pic.median_color = rgb_to_int(stats.median)
+                pic.stddev_red, pic.stddev_green, pic.stddev_blue = [
+                    int(round(i))
+                    for i in stats.stddev[0:3]
+                    ]
+
+                exc = stats.extrema
+
+                pic.min_color = rgb_to_int((exc[0][0], exc[1][0], exc[2][0]))
+                pic.max_color = rgb_to_int((exc[0][1], exc[1][1], exc[2][1]))
+
+                pic.valid = True
+
+                if im.size != (640, 480):
+                    pic.valid = False
+
+            except:
+                pic.valid = False
+            pic.save()
+            logger.info('{} {}'.format(
+                item.date.astimezone(get_default_timezone()), item.filename))
+        logger.debug("addpics finish: {} pictures".format(Picture.objects.count()))
+

timelapse/models.py

+from django.db import models
+from django.db.models.signals import pre_save
+from django.dispatch import receiver
+import datetime
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class PictureManager(models.Manager):
+    def dates(self):
+        "Return a DateQuerySet of all dates"
+        return self.get_query_set().dates('timestamp', 'day')
+
+
+class Picture(models.Model):
+    """Mostly copied from Eric Floehr's daystrips project.
+       It stores everything we might want to conveniently
+       know about an image.
+    """
+    filepath = models.CharField(max_length=1024)
+    filename = models.CharField(max_length=1024)
+
+    timestamp = models.DateTimeField(db_index=True)
+    file_timestamp = models.DateTimeField(null=True)
+
+    # Auto-populated, don't manually enter
+    hour = models.SmallIntegerField(null=True, db_index=True)
+    minute = models.SmallIntegerField(null=True, db_index=True)
+    file_hour = models.SmallIntegerField(null=True)
+    file_minute = models.SmallIntegerField(null=True)
+    # End auto-populated fields
+
+    size = models.IntegerField(default=0)
+
+    center_color = models.IntegerField(null=True)
+    mean_color = models.IntegerField(null=True)
+    median_color = models.IntegerField(null=True)
+
+    stddev_red = models.IntegerField(null=True)
+    stddev_green = models.IntegerField(null=True)
+    stddev_blue = models.IntegerField(null=True)
+
+    min_color = models.IntegerField(null=True)
+    max_color = models.IntegerField(null=True)
+
+    valid = models.BooleanField(default=False)
+
+    class Meta:
+        ordering = ['timestamp']
+
+    objects = PictureManager()
+
+    def __unicode__(self):
+        return unicode(self.timestamp)
+
+
+@receiver(pre_save, sender=Picture)
+def pre_save_picture(sender, **kwargs):
+    pic = kwargs['instance']
+    pic.hour = pic.timestamp.hour
+    pic.minute = pic.timestamp.minute
+    if pic.file_timestamp is not None:
+        pic.file_hour = pic.file_timestamp.hour
+        pic.file_minute = pic.file_timestamp.minute

timelapse/sandbox.py

+#!/usr/bin/env python
+"""python -m timelapse.sandbox"""
+from __future__ import print_function
+from collections import defaultdict
+from .management.commands.addpics import generate_all_image_files
+
+days = defaultdict(list)
+
+for item in generate_all_image_files():
+    day = item.date.date()
+    days[day].append(item)
+
+for key in sorted(days.keys()):
+    items = days[key]
+    items.sort(key=lambda x:x.date)
+    print('{} {:4} {:%H:%M:%S} {:%H:%M:%S}'.format(
+        key, len(items), items[0].date, items[-1].date))

timelapse/settings.py

 
 DEBUG = True
 TEMPLATE_DEBUG = DEBUG
+INTERNAL_IPS = ('127.0.0.1',)
 
 ADMINS = (
     # ('Your Name', 'your_email@example.com'),
 
 ROOT_URLCONF = 'timelapse.urls'
 
-
 TEMPLATE_DIRS = (make_path('templates'),)
 
 INSTALLED_APPS = (
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'django.contrib.admin',
-    'pics',
+    'timelapse',
+    #'debug_toolbar',
 )
 
 # A sample logging configuration. The only tangible logging
             'level': 'ERROR',
             'propagate': True,
         },
-        'pics': {'handlers': ['console'], 'level': 'DEBUG'},
         'timelapse': {'handlers': ['console'], 'level': 'DEBUG'},
+        'django': {'handlers': ['console']},
+        'django.db.backends': {
+            'handlers': ['console'],
+            'level': 'INFO',
+        },
     }
 }
+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    python -m timelapse.sky
+
+    Calculate the approximate times of full darkness for the
+    next day, so we can set the schedule for the webcam. For the
+    current location, pictures taken at night are not that
+    interesting and don't vary from one night to the next, so
+    I'll turn the camera off when it gets dark and turn it on
+    in the morning when it starts getting light.
+
+    Civil twilight is defined as the Sun being 6° below the
+    horizon. From examining images, it looks like choosing
+    8° is a good choice for me.
+"""
+from __future__ import print_function
+import sys
+import os
+import datetime
+from collections import namedtuple
+import ephem
+
+HORIZON = '-8'
+
+sun = ephem.Sun()
+
+
+def observer_from_location():
+    "Create an ephem.Observer object from our location"
+    observer = ephem.Observer()
+    observer.lat = str(os.environ['LATITUDE'])
+    observer.lon = str(os.environ['LONGITUDE'])
+    observer.elevation = os.environ.get('ELEVATION', 750)
+    observer.date = ephem.now()  # useful default?
+    return observer
+
+SunTimes = namedtuple('SunTimes', 'dawn sunrise sunset dusk')
+
+
+def suntimes_for_date(observer, date, horizon=HORIZON):
+    """
+    Calculate rise and set times for a given date and observer,
+    along with the times of twilight using our default horizon.
+    Civil twilight is usually calculated for 6°.
+    """
+    observer.date = ephem.Date((date.year, date.month, date.day, 12, 0, 0))
+    sunrise = observer.previous_rising(sun)
+    sunset = observer.next_setting(sun)
+    observer.horizon = horizon
+    dawn = observer.previous_rising(sun, use_center=True)
+    dusk = observer.next_setting(sun, use_center=True)
+    return SunTimes(dawn, sunrise, sunset, dusk)
+
+if __name__ == '__main__':
+    print("Sun times for today, horizon of {}°".format(HORIZON))
+    observer = observer_from_location()
+    times = suntimes_for_date(observer, datetime.date.today())
+    for field in times._fields:
+        value = getattr(times, field)
+        print('{:7} {} UTC or {:%H:%M:%S} local'.format(
+            field, value, ephem.localtime(value)))

timelapse/time_of_day.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    Create a movie from a particular time of day
+    (Not sure this is going to be all that useful)
+"""
+from __future__ import print_function
+import sys
+import os
+import datetime
+from .models import Picture
+from django.utils.timezone import utc, get_default_timezone, make_aware
+from .image_sequence import ImageSequence
+from .sky import observer_from_location
+
+# Choose defaults from the environment.
+# I set these defaults in the postactivate script of the virtualenv
+tzinfo = get_default_timezone()
+observer = observer_from_location()
+
+def date_range():
+    first = Picture.objects.all()[0]
+    last = Picture.objects.latest('timestamp')
+    return (first, last)
+
+# first, last = date_range()
+# print(first.timestamp.astimezone(tzinfo))
+# print(last.timestamp.astimezone(tzinfo))
+movie = ImageSequence()
+hour = 16
+minute = 0
+qs = Picture.objects.filter(hour=hour, minute=minute)
+print(qs.count())
+[movie.add_picture(obj) for obj in qs]
+outpath = os.path.join(os.environ['VIRTUAL_ENV'],
+    'hour-minute-{}-{}.mp4'.format(hour, minute))
+movie.make_timelapse(outpath, frames_per_second=5)

timelapse/util.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    We store color values as integers in the database.
+    These functions do the conversions back and forth.
+"""
+from __future__ import print_function
+from collections import namedtuple
+
+
+def rgb_to_int(rgb):
+    "(red, green, blue) tuple --> integer"
+    return int(round(rgb[0] * 256 * 256 + rgb[1] * 256 + rgb[2]))
+
+
+def int_to_rgb(i):
+    "integer --> (red, green, blue)"
+    r = (i >> 16) & 255
+    g = (i >> 8) & 255
+    b = i & 255
+    return (r, g, b)

timelapse/views.py

+# Create your views here.
+#!/bin/bash
+source ~/.bashrc
+workon timelapse
+cd $HOME/hg/timelapse
+./rsync_from_avocet.sh
+./manage.py addpics
+/usr/local/bin/growlnotify -m "timelapse update complete"