Post your fun L scripts here

My kids and I share some camera bodies (7D II, 5D III, rebel t4i) as well as a pile of lenses. The job of sorting through the pictures falls on me and I struggle with it so I wrote this script. I call lens because originally I was trying to sort by which lens we were using but then we swapped bodies on one canoe trip to Elkhorn Slough so I added camera bodies to the script as well.

So what’s it do? You can look at a set of jpgs (or raw files) and say “show me all the ones that were shot by the 7D” or “show me all the ones shot with the 70-200”. Those would be

lens --camera=7D *.jpg
lens --lens=70-200 *.jpg

Here’s the script.

    #!/usr/local/bitkeeper/bk tclsh
    
    #lang L
    /*
     * Use exiftags -c to dig produce a list like so
     * foo.jpg: EF200mm f/2L IS USM
     * bar.jpg: EF70-200mm f/4L IS USM
     * ...
     */
    void
    main(string av[])
    {
            FILE    exif;
            string  buf, c, jpeg;
            string  camera[] = undef;
            string  notcamera[] = undef;
            string  lens[] = undef;
            string  notlens[] = undef;
            string  lopts[] = {
                    "camera:",      // --camera=regex
                    "notcamera:",   // --camera=regex
                    "lens:",        // --lens=regex
                    "not-lens:",    // --lens=regex
            };
            int     i, matchlens, matchcamera, nope;
            string  model, anno;
    
            while (c = getopt(av, "", lopts)) {
                    switch (c) {
                        case "camera": push(&camera, optarg); break;
                        case "not-camera": push(&notcamera, optarg); break;
                        case "lens": push(&lens, optarg); break;
                        case "not-lens": push(&notlens, optarg); break;
                    }
            }
            for (i = optind; buf = av[i]; i++) {
                    exif = popen("exiftags -c ${buf}", "r");
                    jpeg = buf;
                    anno = undef;
                    nope = matchlens = matchcamera = 0;
                    while (buf = <exif>) {
                            if (buf =~ /Lens Name:\s*(.*)/) {
                                    model = $1;
                                    foreach (c in lens) {
                                            if (model =~ /${c}/) {
                                                    matchlens = 1;
                                                    break;
                                            }
                                    }
                                    foreach (c in notlens) {
                                            if (model =~ /${c}/) goto nope;
                                    }
                                    if (anno) {
                                            anno .= ", ${model}";
                                    } else {
                                            anno = model;
                                    }
                            } else if (buf =~ /Camera Model:\s*(.*)/) {
                                    model = $1;
                                    foreach (c in camera) {
                                            if (model =~ /${c}/) {
                                                    matchcamera = 1;
                                                    break;
                                            }
                                    }
                                    foreach (c in notcamera) {
                                            if (model =~ /${c}/) goto nope;
                                    }
                                    model =~ s/Canon EOS //;
                                    if (anno) {
                                            anno .= ", ${model}";
                                    } else {
                                            anno = model;
                                    }
                            } else {
                                    continue;
                            }
                    }
                    if (lens && !matchlens) goto nope;
                    if (camera && !matchcamera) goto nope;
                    printf("%s %s\n", jpeg, anno);
    nope:           pclose(exif);
            }
    }

Here is a script I use to tail -f the access.log produced by apache. It’s a little complicated because I have to timeout reads so I can see if cron has rotated the file out from under me. So this shows how to do I/O in an event loop. Yeah, it’s not obvious at all, it’s where tcl meets L and it’s less than graceful. So an example might help.

The tail -f access.log | arg 1 7 is some more Larry crud, I’ve carried around an awk/perl/L script for years called “arg” that prints the listed arguments.

#!/usr/libexec/bitkeeper/bk tclsh

string  buf;
string  state;
FILE    f;
int     ino;

void
main(void)
{
        string  id, ip, file;
        
reopen:
        tail();
again:
        while (1) {
                buf = undef;
                id = after(500 * 1000, { &timeout });
                vwait("state");
                After_cancel(id);

                // warn("state = %s buf =%s\n", state, buf);
                switch (state) {
                    case "reopen":
                        goto reopen;
                    case "data":
                        break;
                    default:
                        goto again;
                }

                if (buf =~ /^\s*([0-9.]+)\s*$/) {
                        ip = $1;
                        buf = `host ${ip}`;
                        if (buf =~ /not found/ || buf =~ /has no PTR record/) {
                                printf("%s\n", ip);
                        } else {
                                buf =~ /([^ ]+$)/;
                                printf("%s\n", $1);
                        }
                        continue;
                }
                buf =~ /([0-9.]+)\s+(.*)/;
                ip = $1;
                file = $2;
                if (file =~ /assets/) continue;
                if (file =~ /favicon/) continue;
                if (file =~ m|^/icons/|) continue;
                if (file =~ m|^/apple-touch-icon|) continue;
                if (file =~ /wpad.dat/ || file =~ /robots.txt/) continue;
                unless (buf = host(ip, file)) continue;
                if (buf =~ /crawl/ || buf =~ /spider/) continue;
                buf =~ /([^ ]+$)/;
                if ($1 == "pf-lm.mcvoy.com." && file == "/") continue;
                printf("%s %s\n", $1, file);
        }
}

string  hosts{string};

string
host(string ip, string file)
{
        string  host;

        if (hosts{ip}) return (hosts{ip});
        host = `host ${ip}`;
        if (host =~ /not found/ || host =~ /has no PTR record/) {
                  printf("%s %s\n", ip, file);
                  return (undef);
        }
        hosts{ip} = host;
        return (host);
}

void
timeout(void)
{
        struct  stat sb;

        stat("access.log", &sb);
        if (ino == sb.st_ino) {
                state = "timeout";
        } else {
                state = "reopen";
                warn("\n=== REOPEN %d ===\n", sb.st_ino);
        }
}

void
tail(void)
{
        struct  stat sb;

        stat("access.log", &sb);
        ino = sb.st_ino;
        if (f) {
                pclose(f);
        } else {
                warn("INO: %d\n", ino);
        }
        f = popen("tail -f access.log | arg 1 7", "r");
        fconfigure(f, blocking: "off", buffering: "none");
        fileevent(f, "readable", "reader");
        state = undef;
}

void
reader(void)
{
        if (buf = <f>) {
                state = "data";
        } else {
                state = "reopen";
        }
}

Another photo related script. This one has gotten a lot of use, if you look at http://www.mcvoy.com/lm/travis-excavator/ that’s what this script does. It takes a list of jpegs and creates a web interface to the photos. It creates an index page with thumbnails (so it has to generate those), the thumbnails link to a scaled down version of the picture in an html page with navigation (so it has to generate those as well), and then the medium sized ones are linked to the original.

There is some javascript in there to preload the next medium sized one so you can go through things pretty quickly.

I ripped off the idea and the javascript from a program called Igal that a guy at Stanford wrote, so he gets credit, not me.

The reason this is interesting is the automatic parallelism, this script figures out how many cores you have and spawns off the resize processes in background, reaping them as needed, spawning more as the cores become available. It works really well on mcvoy.com which is a 6/12 core machine.

Script in next post, it was too big for one post.

Can’t shrink it enough, it’s at http://mcvoy.com/lm/photos.l