For my dad's birthday, I decided to make a system with a raspberry pi and camera that would take a time-lapse of the sun rising at our house in NH and send it in an email to my dad every day.

Once a year on his birthday it compiles a special email with a seasonal time-lapse. Read more below.

<aside> <img src="/icons/list_green.svg" alt="/icons/list_green.svg" width="40px" /> There are several steps that make this possible.

  1. I am using an old Raspberry Pi 3 to run everything and the raspberry pi camera
  2. Crontab starts the Python program at 4:00 AM every morning (well before sunrise)
  3. The Python program
    1. Looks up the time of dawn with the astral library
    2. Waits until dawn
    3. Then runs the rapstill time-lapse terminal command to take a photo every ten seconds for two hours
    4. Uses FFMPEG to combine the photos into a single .mp4 movie
    5. Uploads the video to a google drive folder using the pydrive library and Google Drive API
    6. Sends an email to everyone on a list with a link to the video and the date
  4. Extra: there is another Python script that runs at noon every day which takes a single picture which I can later compile into a time-lapse spanning multiple years </aside>

Here are some additional cool time-lapses that the Raspberry Pi captured

Look for the ducks!

Look for the ducks!

Check out that mist!

Check out that mist!

2022: For my dad’s birthday this year, I added some new features to the program. Now, every year on his birthday it sends a special and completely automated email

<aside> ✉️ Happy Birthday Dad!

If my calculations are correct you are now 53 years old!

As an extra special birthday gift (which is completely automated) here is the seasonal time-lapse created from pictures taken from when I first gifted this to you until today

Check out the leaves the changing and falling!

Check out the leaves the changing and falling!


2023: For my dad’s birthday this year, I added a feature that sends an automated email whenever a power outage is detected and the Raspberry Pi reboots. Not super important, but a fun little thing

<aside> ✉️ POWER ANOMALY DETECTED. Power recovered. Downtime 0:00:06.939611 (hh:mm:ss:...). All systems nominal.


Over the past couple of years an additional 15 of my friends and family have requested and been added to the sunrise email sender. If you too would like a scenic 30 second NH sunrise time-lapse emailed to your inbox each morning feel free to reach out, and I’ll be happy to add you.

<aside> 👏 What people are saying

Good morning! I just want to thank you again for including me on the SH sunrise. It’s my daily meditation. I take deep breath and center while it’s downloading and playing. Namaste 🧘‍♂️- My AuntClick to see the code below

</aside> (1).gif

import smtplib
import ssl
from email.mime.text import MIMEText
from email.utils import formataddr
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase 
from email import encoders
from pydrive.auth import GoogleAuth
from import GoogleDrive
import subprocess
import shutil
import os
import time
from astral import LocationInfo
from datetime import datetime, timedelta, date
from astral.sun import sun
import time
import sys

def uploadFile(upload_file):
    gauth = GoogleAuth()
    drive = GoogleDrive(gauth)
    newFile = timelapseMode + "_timelapse_" + str( + ".mp4"
    print("uploading " + newFile + " ...", end="")
    gfile = drive.CreateFile({'parents': [{'id': 'FOLDER_ID_GOES_HERE'}]})  # folder ID (from URL)

    gfile['title'] = newFile
    permission = gfile.InsertPermission({
        'type': 'anyone',
        'value': 'anyone',
        'role': 'reader'})
    link = gfile['alternateLink']
    return link

def remove(file):

# User configuration
sender_email = "EMAIL_GOES_HERE"
sender_name = "NAME_GOES_HERE"

receiver_emails = [] # list of emails
receiver_names = [] # list of reciever names

timelapseFilename = "timelapse.mp4"
timelapseFolder = "dailyTimelapse"
timelapseMode = sys.argv[1]

# actual lat and long go here (I have changed them)
latitude = 20
longitude = -60
# loc = LocationInfo(name='name', region='NH, USA', timezone="US/Eastern",
                   # latitude=latitude, longitude=longitude)
s = sun(,, tzinfo=loc.timezone)
dawn = s["dawn"].replace(tzinfo=None)
dusk = s["dusk"].replace(tzinfo=None)
if dawn - < timedelta():
    dawn += timedelta(days=1)
    dusk += timedelta(days=1)

if timelapseMode == "sunrise":
    targetTime = dawn
    durationSeconds = round(timedelta(hours=2).total_seconds(), 2)
elif timelapseMode == "allday":
    targetTime = dawn
    durationSeconds = round((dusk-dawn).total_seconds(), 2)
else: # if timelapseMode == "now" # or anything else
    targetTime = datetime(1990 , 2, 3, 2, 30, 0)
    inputHours = float(sys.argv[2]) if len(sys.argv) == 3 else .5
    durationSeconds = timedelta(hours=inputHours).total_seconds()
    timelaspeMode = "now"

targetDiff = timedelta(seconds=1)
endVidLengthSeconds = 30
framerate = 24
secondsPerFrame = max(round(durationSeconds / framerate / endVidLengthSeconds, 2), 1.5)
totalPhotos = round(durationSeconds / secondsPerFrame)
print("timelapse mode         " + timelapseMode)
print("now                    " + str(
print("target start time      " + str(targetTime))
print("time duration (hours)  " + str(durationSeconds / 3600))
print("seconds per frame      " + str(secondsPerFrame))
print("total photos           " + str(totalPhotos))
print("time till done (hours) " + str(round(durationSeconds / 3600 + totalPhotos/3600, 2)))
print("emailing to            ", end="")
print(*receiver_names, sep=", ")

if targetTime - > targetDiff:
    print("waiting for %s" % str(targetTime -
    while targetTime - > targetDiff:
    print("target time reached")


print("taking timelapse photos")
command = "raspistill -t {} -tl {} -awb auto -o {}/image%04d.jpg".format(durationSeconds*1000, secondsPerFrame*1000, timelapseFolder)
print(command), shell=True)

# from picamera import PiCamera
# camera = PiCamera()
# camera.start_preview()
# for i in range(totalPhotos):
    # camera.capture(timelapseFolder + '/image{0:04d}.jpg'.format(i))
    # time.sleep(secondsPerFrame)

print("stitching photos with ffmpeg")
command = "ffmpeg -r %i -f image2 -pattern_type glob -i '%s/image*.jpg'\\
 -s 1280x720 -vcodec libx264 %s" % (framerate, timelapseFolder, timelapseFilename)
print(command), shell=True)

videoLink = uploadFile(timelapseFilename)

birthdate = date(1970 , 3, 10) # not my dad's real birthday
# if ( - birthdate == timedelta(years=1)):
if"%m%d") == birthdate.strftime("%m%d"):
    email_body = '''
    <h2 style="color: green">Happy Birthday Dad!</h2>
    <p>%s timelapse on %s<p>
    ''' % (,, videoLink)
    email_body = '''
    <h2 style="color: green">%s timelapse %s </h2>
    ''' % (,, videoLink)

# send emails(s)
for receiver_email, receiver_name in zip(receiver_emails, receiver_names):
    print("Sending the email...")
    # Configurating user's info
    msg = MIMEMultipart()
    msg['To'] = formataddr((receiver_name, receiver_email))
    msg['From'] = formataddr((sender_name, sender_email))
    msg['Subject'] = "%s timelapse %s"% (,
    msg.attach(MIMEText(email_body, 'html'))

        # Creating a SMTP session | use 587 with TLS, 465 SSL and 25
        server = smtplib.SMTP('', 587)
        # Encrypts the email
        context = ssl.create_default_context()
        # We log in into our Google account
        server.login(sender_email, password)
        # Sending email from sender, to receiver with the email body
        server.sendmail(sender_email, receiver_email, msg.as_string())
        print('Email sent!')
    except Exception as e:
        print(f'Oh no! Something bad happened!n{e}')
        print('Closing the server...')

<aside> <img src="/icons/home_blue.svg" alt="/icons/home_blue.svg" width="40px" /> Home


<aside> <img src="/icons/document_green.svg" alt="/icons/document_green.svg" width="40px" /> Resume


© Jesse Gilbert 2024

Powered by Fruition