Tinkers and Scriveners, 2015

Tinkering is, historically, the repair of tinware, usually by itinerant craftsmen.  Modern usage extends the term to any kind of more or less informal modification or adaptation of something.  If the object is intended to be useful or repurposed, we say it has been “tinkered with.”  If the purpose is to make an art statement, we resort to French, and call it “bricolage.”

A scrivener is traditionally a transcriptionist, someone who writes down what an illiterate person needs to have in print for legal or financial purposes.  Scriveners are still fairly common in Third-World countries, and in the First World, we use professionals to help us with legal and other formal paperwork.   In the modern age, reading and writing is more or less a universal skill, but computer coding is not, so we commonly also employ a scrivener to write down what we want a computer to do.   I think the term “scrivener” is more descriptive than “programmer.” As someone who revises more code than writing original code, we often use the combination, “Tinkers and Scriveners” as our informal motto.

In our last episode, we described a “just for fun” practice, describing (a word derived from “scribe”)  to our computers how to take pictures of our driveway, turn them into a timelapse video, and make them available on the Web.  To extend and refine this concept, we tinkered with the scripts this week, to automate the process and add an audio track, plus fix a few issues.

Try it out:  http://www.parkins.org/webcam

As part of the allure of having a video surveillance system, we had included a weather report along with the “real time” view.  The process is simple:  “Go get the weather report, take a picture, and display the picture, time, and current weather report in a web page.”   That worked fine when it was just me running it.  But, as soon as I posted the URL (Uniform Resource Locater, or web page name) to Facebook, that World-wide Eater of Time, I immediately got a “nastygram” from the weather service informing me that I had abused my (free) automated query privilege by asking for many weather reports all at once.   I don’t have all that many “fans” eagerly awaiting my next post, so I presume it was the WordPress.org  and Facebook robots burrowing into the link to see where it went–many, many times.  The search engines do this to score your site/post and the spammers also are looking for paths that lead to posts with open comment access.

So, the first order of business was to rethink the weather report.  The service only updates their reports a few times an hour, so there is no need to ask for a fresh report every time the page is accessed.  So, we told the computer, “Remember the weather report, and only get a new one if the one you have is more than 10 minutes old.”  We don’t expect a lot of traffic, but this ensures at least 10 minutes between weather updates, no matter how many visitors we get at once.

Here’s what that revision looks like, in patch form:

9a10
> import os.path
37,39c38,46
< 
< f = urllib2.urlopen('http://api.wunderground.com/api/...key.../geolookup/conditions/q/WA/Shelton.json')
< json_string = f.read()
---
> stale = time.time() - 600
> if ( os.path.getmtime('weather.dat') < stale ):
> f = urllib2.urlopen('http://api.wunderground.com/api/...key.../geolookup/conditions/q/WA/Shelton.json')
> json_string = f.read()
> w = open('weather.dat','w')
> w.write(json_string)
> w.close()
> w = open('weather.dat','r')
> json_string = w.read()

A minor change, but one that prevents breaking the application programming interface (API) agreement with the weather site. We can’t control how many hits we get on our web site, but we can control how often we hit theirs.

Since we are now taking a picture every 15 seconds, and the process runs all day, taking exclusive control over the camera hardware, we no longer can take snapshots in “real time” like we did originally. However, 15 seconds isn’t a long time, so we simply copy the picture into the “current” view every time we take one, and skip the “snap the shutter” part of the weather report page.  That part we had in the last post.  What we did this time, though, was add some code to update the time displayed on the screen and refresh the picture every 15 seconds.  This requires some browser code, in Javascript,  We also added some code to show when the weather report was last updated by the weather service.  The patch file looks something like this (we also cleaned up some other code that is no longer necessary):

28c30
< <h1>The view from Chaos Central %s</h1>
---
> <h1>The view from Chaos Central <span id="nowTime">%s</span></h1>

47a48
> updated = parsed_json['current_observation']['observation_time']
49,50c50,72
< print "<img src=\"/images/webcam.jpg\" />"
< print "<p>The current temperature in %s is: %s F / %s C<br />" % (location, temp_f, temp_c)
---
> print """
> <table><tr>
> <td>
> <img src="/images/current.png" id="currentImage"/>
> <script language="Javascript">
> setInterval(function() {
> var currentImageElement = document.getElementById('currentImage');
> currentImageElement.src = '/images/current.png?rand=' + Math.random();
> var now = new Date();
> var h = now.getHours();
> var m = now.getMinutes();
> var s = now.getSeconds();
> formatnum = function( num ) {
> if ( num < 10 ) { return '0' + num; }
> else { return '' + num; }
> }
> var time = formatnum(h) + ':' + formatnum(m) + ':' + formatnum(s);
> var nowTimeElement = document.getElementById("nowTime");
> nowTimeElement.innerHTML = time;
> }, 15000);
> </script>
> """
> print "<p>The temperature in %s is: %s F / %s C<br />" % (location, temp_f, temp_c)
52a75,76
> print "Weather Data %s<br />" % updated 
> print "Reload page to update weather: image updates automatically every 15 seconds<br />"
54a79,91
> print """
> </td><td>
> <ul>
> <li><a href="/timelapse0.html">Timelapse video of Monday</a></li>
> <li><a href="/timelapse1.html">Timelapse video of Tuesday</a></li>
> <li><a href="/timelapse2.html">Timelapse video of Wednesday</a></li>
> <li><a href="/timelapse3.html">Timelapse video of Thursday</a></li>
> <li><a href="/timelapse4.html">Timelapse video of Friday</a></li>
> <li><a href="/timelapse5.html">Timelapse video of Saturday</a></li>
> <li><a href="/timelapse6.html">Timelapse video of Sunday</a></li>
> </ul>
> </td></tr></table>
> """

Those of you familiar with HTML and web page layout will also notice that we converted the page into a table and added a column with a list of links to the timelapse videos, which weren’t really visible before, since we were testing them.

Now, to make all this useful and automated, we told the computers (plural, remember–the video production takes place in another, bigger machine) to make a new video every hour on the hour and add background music to the video.  For the first part, making a video of the images captured so far, we simply add the makevideo.sh script to a cron job.  “cron” is a daemon in the Unix/Linux system that runs programs at specified times, periodically. (A daemon is not to be confused with ‘demon.’  A demon is an evil force, a daemon is a helper or servant –the term comes from the Greek, adopted by the geek culture in the 1970s.)  Because our little Raspberry Pi runs on Universal Time (UTC, what used to be called Greenwich Mean Time), and daylight on the North American West Coast, eight time zones later, spans two days under UTC,  we have to put in two jobs for each time lapse image set, one for the morning and early afternoon, and one for late afternoon and evening:

0 16,17,18,19,20,21,22,23 * * 0 /home/larye/TIMELAPSE/makevideo.sh 6
0 0,1,2,3 * * 1 /home/larye/TIMELAPSE/makevideo.sh 6
0 16,17,18,19,20,21,22,23 * * 1 /home/larye/TIMELAPSE/makevideo.sh 0
0 0,1,2,3 * * 2 /home/larye/TIMELAPSE/makevideo.sh 0
0 16,17,18,19,20,21,22,23 * * 2 /home/larye/TIMELAPSE/makevideo.sh 1
0 0,1,2,3 * * 3 /home/larye/TIMELAPSE/makevideo.sh 1
0 16,17,18,19,20,21,22,23 * * 3 /home/larye/TIMELAPSE/makevideo.sh 2
0 0,1,2,3 * * 4 /home/larye/TIMELAPSE/makevideo.sh 2
0 16,17,18,19,20,21,22,23 * * 4 /home/larye/TIMELAPSE/makevideo.sh 3
0 0,1,2,3 * * 5 /home/larye/TIMELAPSE/makevideo.sh 3
0 16,17,18,19,20,21,22,23 * * 5 /home/larye/TIMELAPSE/makevideo.sh 4
0 0,1,2,3 * * 6 /home/larye/TIMELAPSE/makevideo.sh 4
0 16,17,18,19,20,21,22,23 * * 6 /home/larye/TIMELAPSE/makevideo.sh 5
0 0,1,2,3 * * 0 /home/larye/TIMELAPSE/makevideo.sh 5

Now, the web server owns the scripts that generate the web page and take the photos, but we share the folder on the external disk drive that holds the videos and I own the video building scripts on the CentOS server, so I run this set of  cron jobs under my own user account.  The timelapse show for the current day starts at 8:00am PST (1600 UTC), and runs until well after dark ( i.e., 0300 UTC, or 7:00pm PST).  As the days grow longer, if I am still running this experiment, I will need to extend the times, or use the astronomical data to control whether or not the video needs to be generated.  At the current display rate, the timelapse video covers an hour every 30 seconds.  In mid-winter, daylight lasts less than 8 hours at this latitude, but nearly 18 hours at the summer solstice, so the video will stretch out from the current 4-minute running time to nearly 10 minutes.  The camera program starts at 4:00am and waits for daylight to start recording, shutting off automatically at dark.  The programs are written so that it would be trivially easy to change the frame rate to keep the length of the video shorter by advancing the time scale faster later in the day, as the days get longer.

The last bit of code added was for an audio track.  For this, I told the computer, “Find a song in the music library that is about the same length as the video, and add a sound track using it.”  I have enough tunes of varying length in the library so that there is a different one every hour, and hopefully enough in between so we get different ones each week as the days get longer.

Part of this exercise was to get some practice writing in the Python language.  However, the song-picker, I wrote in my old standby, Perl, just to get it done quickly.

#!/usr/bin/perl -w

%list = (
 108 => "Sintel/Jan_Morgenstern_-_01_-_Snow_Fight.mp3",
.
.  # lots more songs here, deleted for clarity...
.
 225 => "Kai_Engel/Kai_Engel_-_09_-_Embracing_The_Sunrise.mp3");

$frames = $ARGV[0];
$rate = $ARGV[1];
$runtime = $frames / $rate;

foreach $key (sort { $a <=> $b } keys %list) {
 print STDERR "check: " . $key . " against " . $runtime . "\n";
 if ( $key >= $runtime ) {
 print $list{$key};
 exit();
 }
}
print $list{$key};

This program is called by the shell script that runs the video:

#!/bin/bash

cd ~/TIMELAPSE
rate=8
frames=`ls ./images/${1}/img*png | wc -l`
# find a music track that is about the same length as the video
song=`./songs.pl $frames $rate`
rm surveil${1}.mp4
~/bin/ffmpeg -framerate $rate -i ./images/${1}/img%04d.png -i ./music/${song}
-c:a aac -strict experimental -strict -2 -r 30 -pix_fmt yuv420p surveil${1}.mp4
echo $song > surveil${1}.txt

This also records the name of the artist and the song (for the credits) in a file, the contents of which are displayed by the Javascript code on the web page that plays the video clip.  This is still crude, using a frame.  The other part of this learning exercise is to hone my Javascript skills and learn to use AJAX (Asynchronous Javascript And XML–actually more often JSON rather than XML)  to pull data from the server and format it so the page is well-formatted and updates seamlessly.  The time updates on the “real-time” page also needs work to format it the same as the original time statement.  One oddity in that is that the initial timestamp is the server time, but the updates are for the timezone of the browser, so the hour will change if you are in a different timezone.  This violates the “least surprise” principle of good web and program design.

Finally, here is one of the video display pages, showing the mechanism to update the current video file and song title.  I should rewrite this as a Python script, so it accepts the index of the video and creates the proper page, instead of repeating the HTML code seven times and having to edit all of them for each code change, which leads to errors and inconsistency.  The random number function added on to the file URL in the Javascript code is a hack to make the browser reload the file–if the URL doesn’t change, the browser will use the cached (old) version of the file, so that the video and song title (and photo in the ‘real-time’ page)  wouldn’t update until the cache expired (usually a couple of weeks–not very effective for an hourly or 15-second update).  That’s always been an issue when fixing customer web sites–they don’t always see the change unless they clear their browser cache.  If they are working through a proxy server, they don’t have control over the proxy caching.  A cache is  a neat way to speed up the Internet for sites you visit a lot, but it can be annoying if the browser or proxy doesn’t check the file modification time against your cached copy.  Inaccurate time-keeping on the user end can also mess up cache coherence and browser cookie management.   Fortunately, most computers now get their time updates from the Internet automatically, so this is less of a problem than it was in the old modem/dial-up days when we couldn’t afford the overhead of a lot of background processing over the link.

<html>
<head><title>Webcam Timelapse</title></head>
<body>
<h2>Sunday</h2>
<table width="500px">
<tr><td align="left">
<a href="timelapse5.html">Saturday</a>
</td><td align="center">
<a href="/cgi-bin/webcam.cgi">Home</a>
</td><td align=right>
<a href="timelapse0.html">Monday</a>
</td></tr></table>
<video width="320" height="240" controls>
<source src="/TIMELAPSE/videos/surveil6.mp4" type="video/mp4" id="vidFile6">
</video>
<p>Music track: <iframe id="audFile6" src="/TIMELAPSE/videos/surveil6.txt" height="55px" width="300px" onload="upDate()"></iframe></p>
<script language="Javascript">
function upDate() {
 var currentVidFile6 = document.getElementById('vidFile6');
 currentVidFile6.src = '/TIMELAPSE/videos/surveil6.mp4?rand=' + Math.random();
}
</script>
</body>
</html>

So, there we have it: a project that is realized in a variety of programming languages and data formats: Python, Bash (Bourne-Again SHell), Perl (Practical Extraction and Report Language or Perniciously Eclectic Rubbish Lister or just short for ‘Pearl,’ depending who you ask), HTML5 (HyperText Markup Language version 5), and JSON (JavaScript Object Notation), MPEG 4 video and MPEG 3 audio, H264 video codec and AAC audio coding, and runs on several different Linux distributions, Raspian (Debian 7 on Atom CPU) and CentOS7 (Red Hat unlicensed version on 64-bit Intel CPU, as a virtual machine running on a CentOS6 host), using the Apache2 web server software configured to use CGI (Computer Gateway Interface, not Computer Generated Images, as used in the movies) and directories aliased on a shared disk drive. We use Secure Shell (SSH) with a password-caching agent to transfer files between the web server and the video processor, and a API (Application Programming Interface) to retrieve weather and astronomical data from a remote web server. We also use a script that queries an external “whats-my-address” server to track the variable IP address of the web server and link to it by a redirection statement on our public web server, routing the request through a firewall rule and port mapping to avoid a lot of hacker traffic on the public HTTP (HyperText Transport Protocol) port (80). All of the software used is freely available, shared for the benefit of all who use it and contribute to the software community: no proprietary, hidden code, no expensive training classes, just relatively inexpensive books or free on-line documentation and how-to-do-it forums. This is literacy in the 21st century–the ability to express yourself through ubiquitous and free technology, with the freedom to tinker–if you don’t like the way the software works, change it, or write something completely different. Expand your mind. Share your code and write about your experience.