Created
May 25, 2016 06:58
-
-
Save bhautikj/8d9514421c720aa1f91491e237eb1aa9 to your computer and use it in GitHub Desktop.
Tool to sync timelapse sequences across multiple cameras.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# lapseSync.py | |
# ------------ | |
# | |
# author: Bhautik Joshi, 2016 | |
# license: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Tool to ingest timelapse sequences from multiple cameras and generate a list | |
# of images across the cameras to be synced to a single clock specified by a | |
# given interval. The images are synced to the earliest image in each directory. | |
# | |
# Requirements: | |
# * python 2.7 (ideally) | |
# * exiftool | |
# | |
# on osx, exiftool can be installed using: | |
# /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" | |
# brew install exiftool | |
# | |
# Usage: | |
# python lapseSync.py <interval> <dir1> <dir2> <dir3> .... | |
# | |
# Interval is in seconds, and dir1, dir2 etc are the directory paths with the | |
# images. | |
# | |
# Output is of the format (first line is just a header): | |
# time,sample/1,sample/2,sample/3,sample/4 | |
# 2016-04-12T16:50:09,sample/1/echeng160412_165020.arw,sample/2/echeng160412_165022.arw,sample/3/echeng160412_165009.arw,sample/4/echeng160412_165017.arw | |
# 2016-04-12T16:50:11,sample/1/echeng160412_165022.arw,sample/2/echeng160412_165024.arw,sample/3/echeng160412_165011.arw,sample/4/echeng160412_165019.arw | |
# | |
# requires: exiftool | |
# brew install exiftool | |
import sys, subprocess, os | |
from datetime import datetime | |
import time | |
# run a process | |
def runProcess(cmd): | |
return subprocess.Popen(cmd, | |
shell=True, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
stdin=subprocess.PIPE).communicate() | |
# convert the timesamp from exiftool into a unix timestamp | |
def dateToUnixTime(dt): | |
# input format: | |
# 2016:04:12 16:50:20 | |
time_tuple = time.strptime(dt, "%Y:%m:%d %H:%M:%S") | |
dts = datetime.fromtimestamp(time.mktime(time_tuple)) | |
timestamp = (dts - datetime(1970, 1, 1)).total_seconds() | |
return int(timestamp) | |
# get the timestamps for the images in a given directory | |
def exifCommandForDirectory(loc): | |
ret = [] | |
cmd = "exiftool -p '$filename , $dateTimeOriginal' -q -f " + loc | |
output = runProcess(cmd) | |
for line in output[0].split('\n'): | |
spl = line.split(',') | |
if len(spl) < 2: | |
continue | |
fn = os.path.join(loc, spl[0].strip()) | |
dt = spl[1].strip() | |
ts = dateToUnixTime(dt) | |
ret.append((ts,fn)) | |
return ret | |
def getTimestampBrackets(lst): | |
lst = sorted(lst) | |
return [lst[0][0], lst[-1][0]] | |
def getClosestInTime(ts, lst): | |
diff = sys.maxsize | |
for item in lst: | |
if abs(ts - item[0]) < diff: | |
diff = abs(ts - item[0]) | |
rv = item[1] | |
return rv | |
def unix_to_iso8601(timestamp): | |
return datetime.utcfromtimestamp(timestamp).isoformat() | |
def main(argv): | |
# assume that the earliest image in each directory is the sync frame | |
# if clocks on cameras are sync accurately, set this to False | |
syncToFirst = True | |
interval = int(argv[1]) | |
numdirs = len(argv) - 1 | |
workDict = {} | |
workDict["interval"] = interval | |
workDict["dirs"] = {} | |
for directory in argv[2:]: | |
workDict["dirs"][directory] = exifCommandForDirectory(directory) | |
# get earliest and latest timestamp | |
earliestList = [] | |
latestList = [] | |
for dr in workDict["dirs"].keys(): | |
bracket = getTimestampBrackets(workDict["dirs"][dr]) | |
earliestList.append(bracket[0]) | |
latestList.append(bracket[1]) | |
earliestTimestamp = sorted(earliestList)[0] | |
latestTimestamp = sorted(latestList, reverse=True)[0] | |
if syncToFirst == True: | |
latestList = [] | |
for dr in workDict["dirs"].keys(): | |
bracket = getTimestampBrackets(workDict["dirs"][dr]) | |
earliest = bracket[0] | |
latest = bracket[1] | |
diff = earliestTimestamp - earliest | |
latest += diff | |
latestList.append(latest) | |
newTimes = [] | |
for item in workDict["dirs"][dr]: | |
newTimes.append((item[0] + diff, item[1])) | |
workDict["dirs"][dr] = newTimes | |
latestTimestamp = sorted(latestList, reverse=True)[0] | |
currentTime = earliestTimestamp | |
dirkeys = sorted(workDict["dirs"].keys()) | |
print ','.join(['time'] + dirkeys) | |
while currentTime <= latestTimestamp: | |
outstr = [] | |
outstr.append(str(unix_to_iso8601(currentTime))) | |
for dr in dirkeys: | |
outstr.append(getClosestInTime(currentTime, workDict["dirs"][dr])) | |
print ','.join(outstr) | |
currentTime += interval | |
if __name__ == "__main__": | |
main(sys.argv) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment