Sunday, April 6, 2014

python 3 code to rename files using kid's age

When I had a kid, I wanted to automatically rename all the pictures and videos with his age (in years, months, and days as '##Y##M##D_'), so I wrote a simple matlab script and posted it here. Since matlab is way overkill, I just converted it to python. I don't know a good python script posting site, so I'm just going to stick it here.

Here's what I came up with:

# rename_media_by_age()
# automatically rename pictures and videos to start with age
# from specified date. Written for baby pictures.
# Example: Changes name from 'foo' to '##Y##M##D_foo'
# ie - 'foo.jpg' to '01Y03M10D_foo.jpg' if pic taken 1 year, 3 months, 10
# days after birth
# I tried to keep it simple, so I just copy it into a folder with files I
# want to rename and then run it
# I put all video files in a subfolder called 'videos'
# change variable folder_videos below if you do something different
# my files came from many different sources 
# (iPhone, android phone, multiple cameras, ipcamera)
# and they don't all have the same info, so here's what I came up with
# for *.jpg pictures:
# - try to read exif info 
# - if that doesn't work, I use FileModDate
# for videos:
# mp4 files from android phone
# -named as VID_YYYYMMDD_HHMMSS.mp4
# -I use picasa, which doesn't like mp4 files, so I rename as .mov
# -then just use the date from the original filename
# mov files from iphone & camera
# - use file modification date
# exif code stolen from here:
# v1 - Nathan Tomlin. Feb 12, 2012.
# v2 - Feb 28, 2012. Fixed error in .jpg renaming and added .wmv check
# v3 - Aug 11, 2013. Fixed error in age rename and added .mp4 check
# v4 - Apr 5, 2014. Convert from matlab to python, remove .3gp & .wmv code

import os
import glob
import re
from PIL import Image
from datetime import datetime
from math import floor

dtime0 = datetime(2010,1,1,8,23,00) # birthday - basis of file renames
 # format (YYYY,MM,DD,HH,MM,SS)

regexp_str = '\d\dY\d\dM\d\dD_' # this is the regexp used to match names
 # current looking for '##Y##M##D_*' where # = number

#folder_pics = os.getcwd()
folder_videos = 'videos' # just set as empty string ('') if videos in same directory as pictures
 # this defaults to look for 'videos' folder in start folder

def check_filename(filename):
        # check whether file already has correct name 
        imatch =,filename)
        return imatch

def get_image_creation_datetime(filename):
### exif code stolen from here:
        img =
    except (Exception) as e:
        print(' failed on',filename, "due to", e)
        #mtime = "?"
        # read exif data and set mtime to be earliest datatime found
        exif_data = img._getexif()
        mtime = "?"
        if 306 in exif_data and exif_data[306] < mtime: # 306 = DateTime
            mtime = exif_data[306]
        if 36867 in exif_data and exif_data[36867] < mtime: # 36867 = DateTimeOriginal
            mtime = exif_data[36867]
        if 36868 in exif_data and exif_data[36868] < mtime: # 36868 = DateTimeDigitized
            mtime = exif_data[36868]
        if mtime != "?":    # got a valid datetime from exif data
            dtime = datetime.strptime(mtime, "%Y:%m:%d %H:%M:%S")
        else:    # no exif data - use file modification time instead
            print(filename, "could not find exif creation time!")
            dtime = get_file_mod_datetime(filename)       
    return dtime

def get_file_mod_datetime(filename):
    #t = os.path.getctime(filename)
    t = os.path.getmtime(filename)
    dtime = datetime.fromtimestamp(t)
    return dtime

def date2agestr(dtime):
    # convert from datetime to agestr ##Y##M##D_ format
    d = dtime - dtime0
    days = d.days
    Ydays = 365.25      # average days/year
    Mdays = Ydays/12    # average 30.4375 days/month...
    Y = floor(days/Ydays)
    M = floor((days - Y*Ydays)/Mdays)    
    D = round(days - Y*Ydays - M*Mdays)
    agestr = str(Y).zfill(2) + 'Y' + str(M).zfill(2) + 'M' + str(D).zfill(2) + 'D_'
    return agestr

def rename(name_old,agestr,ext=''):
    # rename file with new name: append agestr to old name
    # change file extension if input ext
    name_new = agestr + name_old
    name_base = name_new[:-4]
    if ext == '':
        name_ext = name_new[-4:]
        name_ext = ext
        name_new = name_base + name_ext
    # check whether name already exists
    if os.path.isfile(name_new):
        while os.path.isfile(name_new):
            name_new = name_base + '_' + str(k) + name_ext
    except (Exception) as e:
        print('os.rename failed for',name_old,'to',name_new,'due to',e)
    return name_new

if __name__=="__main__":
    # jpg files
    ext = '.jpg'
    files = glob.glob('*'+ext)
    k = 0;
    for file in files:
        # check whether file already has correct name 
        imatch = check_filename(file)
        #filename = files[m]
        if imatch == None: # file did not match, need to fix name           
            dtime = get_image_creation_datetime(file)   # datetime from exif
            agestr = date2agestr(dtime) # generate age string ('##Y##M##D_')
            rename(file,agestr) # rename

    # change to video directory

    # .mov videos from iphone & camera in form or
    #   use file modification date for datetime
    ext = '.mov'
    files = glob.glob('*'+ext)
    k = 0;
    for file in files:
        # check whether file already has correct name 
        imatch = check_filename(file)
        if imatch == None: # file did not match, need to fix name           
            dtime = get_file_mod_datetime(file) # datetime from file mod
            agestr = date2agestr(dtime) # generate age string ('##Y##M##D_')
            filenew = rename(file,agestr)   # rename

    # .mp4 videos from android phone in form VID_yyyymmdd_HHMMSS
    #   just read name to get datetime
    # picasa seems to have problem with .mp4, so rename to .mov
    ext = '.mp4'
    ext2 = '.mov'
    files = glob.glob('*'+ext)
    k = 0;
    for file in files:
        # check whether file already has correct name 
        imatch = check_filename(file)
        if imatch == None: # file did not match, need to fix name           
            dtime = datetime.strptime(file, 'VID_%Y%m%d_%H%M%S'+ext) # datetime from filename
            agestr = date2agestr(dtime) # generate age string ('##Y##M##D_')
            filenew = rename(file,agestr,ext2)  # rename & change extension
    print('renamed',str(k),ext,'files to',ext2,'files')

    # change back to pics directory


Friday, August 30, 2013

How to import openscad model in autodesk inventor

I drew something in openscad and wanted to send it to my collaborators who use autodesk inventor (2013). I naively thought it would be easy, but it turned out to be a little weird. Inventor 2013 opens STL files, but it always adds or subtracts random things to the files I tried, so first you have to fix it up (using Inventor Fusion 2013).

Here's how I got it to work (I didn't figure any of this out, google told me):

  1. In openscad, export as .STL
  2. Open the .STL file in Inventor Fusion 2013. It should look correct.
  3. In Fusion, save as a .STL file
  4. Install the Inventor addin Mesh Enabler
  5. Open the .STL file (the new one from Fusion) in Inventor. It should look correct. Instead of drag and drop, I open from Inventor so I can set my units under 'options'.
  6. Right click on the part and select 'Convert to Base Feature'. Select output 'Solid/Surface' (cube) and check 'Delete Original'. Wait a bit.
  7. Now you can save as a .IPT file and you're done!

Sunday, August 25, 2013

fixing a toy cement truck with a 3D printer

I've been looking for an excuse to get familiar with openscad and a 3D printer I have access to. So I was happy when I thought of fixing my kid's broken toy truck. We bought a bag of vehicles from a second hand store and the cement mixer was missing the mixer barrel.
So I figured it would be the perfect project to make a new mixer barrel. I designed the barrel in openscad, and made it hollow to save on time and material. I uploaded the CAD files to thingiverse in case anyone ever wants to do something similar, and because their CAD pictures look great:
I printed the barrel in two pieces and added some marbles inside to jiggle around.

That's not the best picture, but it turned out great and actually spins!

I learned a lot about 3D printing, especially from many failed prints. Here are some of my tips:
  • slic3r
    • Occasionally I got errors when I imported an .stl file. Usually they were fixed by netfabb's online tool (or fixing my openscad source)
    • I originally assumed slic3r would pick the best print orientation, but it just prints from z=0 and up. So you need to design your .stl file accordingly.
    • Lots of helpful info here.
  • adhesion
    • I had lots of trouble with adhesion to the kapton when I first started. Sounds like the melted ABS in acetone slurry is the way to go, but I didn't try that.
    • I had good results when I flipped the plate over and printed on the bare glass, using hair spray to help stick. I removed the glass to clean with acetone (nail polish remover), then sprayed hair spray on it, clipped it back down and heated to print. 
  • spool
    • The spool kept getting bound up on almost every print. Usually because the plastic would get trapped on the inside and tighten up instead of unwinding. I didn't figure out how to fix this.
    • I did successfully restart a print that failed halfway through (instructions here).
  • leveling
    • I couldn't really tell anything from the bed calibration test print. It looked fine to me, but when I checked the level with the single piece of paper method, it seemed way off
  • air quality
    • I was surprised at how smelly the printer is. After a couple hours of sitting next to it, my lungs were not happy. If I ever need to use one again, I'll have to set up better ventilation.
I also had fun printing out different models from thingiverse:

Monday, February 4, 2013

Keurig disassembly pics

Based on this excellent step-by-step guide to disassembling a Keurig, I have added pictures of most steps. All text is copied straight from the forum post by 'switch998', just to make the pictures more clear. This post is just so I can link to the pics.
  • Remove the bottom plate. There are a few Philips screws holding the metal plate on. The grounding wires are riveted onto the metal plate, preventing complete removal of the plate.
  • You are now ready to remove the plastic frame. Locate the screws for this frame around the bottom edge of the unit. There is another screw by the drip tray, and another by the basin pump. Do not pull the frame yet.
  • The basin pump and basin LEDs must be removed now. Locate the 1 screw for the LED and the 2 screws for the mouth of the basin pump. These are located directly below the basin.
  • Pull the frame out and de-route the power cable (optional). Be careful of the basin pump, you will need to bend the tube connecting it to the pump. You may want to unscrew the pump.

  • Remove the 2 screws from the underside of the K-CUP arm. Open the K-CUP arm. Remove the holder.
  • Remove the 2 black screws located in front of the holder (inside bottom). The body piece will fall off, hold it up with your hand when unscrewing then pull it off.
  • Look directly above of those 2 black screws, you should see two more screws on the top of the K-CUP arm. Remove those screws and slide the body piece out from underneath the handle.
  • Remove the 2 screws securing the top cover to the frame of the unit. These are higher than the K-CUP arm's two screws. You only need to remove the top cover's two screws. Pull the top cover up from the gap, this will not remove it but it will loosen the clips for removal.
  • Flip the unit on its side again.
  • Get a flash light and look in the cavity where the water basin was, locate the clip securing the top cover of the unit to the case. It is located directly back of where you are looking, it is not on the side wall of the unit. Pry it back towards the back of the unit and push up. This requires a lot of force, you may want to use a long screwdriver or tiny crowbar.
  • Stand the unit up again.
  • While the unit is still on its side, pull up the top cover a bit more. Shine a flash light into the gap and locate another clip on the back side of the unit. Use a screwdriver to push and pry upwards on the clip. It should come loose. Do this for the next few clips as the come loose. Pull off the top cover.
  • Unscrew the two screws above the LCD. Unscrew the screw securing the back cover to the front panel on the other side of the unit. Slide the back cover down and pull it back to remove.
  • Your brewer is now fully disassembled. If i missed a step, please correct me, but i think everything is correct.

Wednesday, August 1, 2012

Attaching an umbrella to a bakfiets

Update: This worked great until my wife took a ride on a really windy day and the umbrella blew inside out with a strong cross-wind. Still working on an alternative.

I bought a rain cover for my bakfiets when I ordered it, and it's great in the winter because it keeps all the wind out so my son stays warm. But in the summer, it's like a greenhouse and just cooks in there. Unfortunately, I have to use it a lot in the summer because there's a good chance of thunderstorms everyday in Colorado.

My first thought was to add zippers on both sides of the canopy like a tent and just leave the sides rolled down unless it's raining (see picture below with cude red line where the zipper would go). I went to a local gear sewing/modification shop and they quoted me $100 per zipper, so I scrapped that idea.

I also thought of building a frame and somehow attaching a cover over just the top, but then I remembered a cool umbrella video I saw awhile ago. So I ordered a Senz smart stick umbrella off amazon and figured I could find a way to attach it once it showed up. After brainstorming in my garage for a bit, I came up with a pretty simple PVC piping holder that uses the bobike mini for attachment. Here's the end result:

For parts, I found that 1 1/4" PVC tubing fits the umbrella handle well, so that's what I went with.
  • 1 1/4" PVC tubing
  • 1" PVC tubing (to fit inside the 1 1/4" tubing)
  • 1 1/4"  tee (Home Depot was out of tees, so I got a cross and hacksawed off one port)
  • two 1 1/4" 90 degree angles
  • 1/4-20 all thread
  • 1/4" nuts
  • umbrella
  • set screw, thumb screw, or wing nut screw
  • hacksaw, wrenches, measuring tape, marker, PVC glue, drill, drill bit

I assembled the PVC parts in this shape, selecting the lengths to set the right angle and get a tight fit in the bucket. The separate length of 1" PVC goes inside the main vertical tube.

 Here the PVC is sitting in the bucket, ready for the umbrella. The main tube is held upright by the allthread inserted in holes in the bobike mini leg bars. This won't work if you have the foot rests all the way up, so in that case you might have to use cable or zip ties.

It works pretty well without gluing the PVC, so I could have left it just press fit together, but I ended up gluing all the joints since I had the PVC glue lying around. You can't see it in any pictures, but I dropped a length of the 1" PVC pipe down the main umbrella holder tube to act as a spacer and set the height of the umbrella.

Here it is with the umbrella inserted. I added a 1/4-20 wing nut screw that presses into the umbrella handle to keep the umbrella pointing forward since unlike most umbrellas, senz umbrellas have a front and back.

During the week when I'm riding to and from daycare in the morning and evening, I just leave the umbrella out of the tube and lying in the bucket as my backup rain cover. On the weekends I tend to use it as a sun shade because we usually end up riding in the middle of the day.

It's easiest to remove the umbrella to get my son in and out. Since I'm always using the wing nut screw, I'll probably eventually replace it with a large thumb screw that's easier to turn. And probably choosing a thread that is more coarse than 1/4-20 would be better. I'm also going to be on the lookout for good padding to add over the PVC tube.

Sunday, May 20, 2012

trendnet TV-IP422W: how to take time lapse videos using a NAS

I wanted to setup my trendnet TV-IP422W to record images or video snippets at a regular interval to my network attached storage (NAS) so I could then convert them into a time lapse video. I just figured it out, but the setup wasn't obvious to me, so I thought I'd post what works in case it's helpful to anyone else. From the setup page, go to Event Server, then Network Storage. It should look like this:

  • Samba Server Address: enter IP address or name, nothing extra
    • or nasname
    • if you use the name, do not enter slashes like this \\nasname
  • Share: top directory, no slashes
    • something like Volume_1
  • Path: 
    • something like /ipcamfolder/ or just ipcamfolder
  • When you click on the Test button, it should write a text file to the folder and popup a message saying it was successful.

Now go to Event Config and Schedule Profile and add a new profile for the days and times you want to record pictures.

Go to Event Config and General to set how long to record for each time - I left it at 1 second, the minimum.

Go to Event Config and Schedule Trigger to enable recording, select the schedule, and pick the recording interval.

Now I have a ton of 1 second .avi video files that I need to combine to make 1 time lapse video. I'm using windows 7, so the simplest thing for me to use is Windows Live Movie Maker (WLMM).

WLMM can only load so many videos at a time, which got annoying, so I ended up using Lifehacker's AVI-joiner to combine all the avi files. Then I open the long avi file and speed it up with WLMM.

Sunday, May 13, 2012

python v3 answers to MIT python course

I went through the MIT (gentle intro to programming) python course with my cousin. We decided to use python 3 to get some extra practice since the course material is all python 2. A few times, I found myself wishing I had the answers to check my work, or when I felt I was doing something wrong. Also, it was occasionally hard to get the code provided to work with v3.

So I decided to post my answers just in case it's useful to anyone else. Obviously I'm no expert since I'm going through an intro class, so keep that in mind if there's something wrong or if I did something in a weird way.

Individual zip files for each homework set:
  • HW1 -,,,,,,,
  • HW2 -,,,,
  • Project 1 -,,
  • HW3 -,,
  • HW4, (v3 is here) -,,,,
  • Project 2 -,
  • Final Project -,

One zip file for all homework: