Recent new feature: As well as tweeting user requested images NixieBot will send out a daily movie tweet about "how my day went". This movie is composed of one frame taken every 15 minutes throughout the day so you can see how lighting changes and weather affect the images the camera produces. The word to display is either picked from the last user requested word from the previous 7.5 minutes or else, if there was no request made during that time period, the most popular word (of four or more letters in length) used in the random tweet feed is displayed. Certain very common words:
boringWords=["this","that","with","from","have","what","your","like","when","just"]are filtered out to make it more interesting. The movie attempts to summarize the twitter 'ZeitGeist' for the day.
How it works:
The time interval between frames is kept in the timeLapseInterval variable, every time round the loop in the main runClock() function this happens:
if int(t.minute) % timeLapseInterval == 0 : doTimeLapse() #either choose a frame from recent first frames or, if none available, take one from random stats #if it's the appointed hour, generate and tweet the time lapse movie. else : lapseDone = FalseThe minutes value of the time variable t (set at the top of the loop) is checked to see if it's a multiple of the required interval, if so the doTimelapse() function is called. The lapseDone variable acts as a flag to make sure that doTimeLapse only gets called once per interval. Without this, if the timelapse process takes less than a minute to run, it would be called multiple times.
So what does doTimelapse do then? here it is:
def doTimeLapse() : global cam global lapseDone global makeMovie global effx global effxspeed if lapseDone : return print("doTimeLapse called") #delete all lapse*.jpg older than (lapseTime / 2) #pick youngest lapse*.jpg file and copy to lapseFrames directory youngestName = "" youngestTime = time.time() youngestFile= "" timeLimit = time.time() - ((timeLapseInterval/2) * 60) files = glob(basePath+"lapse*.jpg") for f in files : fileTime = os.path.getatime(f) if fileTime < timeLimit : print("deleting ", f, " age =", (time.time() - fileTime)/60) os.remove(f) elif fileTime < youngestTime : youngtestTime = fileTime youngestFile = f if youngestFile != "" : print("moving file", youngestFile , "into frame store") move(youngestFile, basePath+"lapseFrames/") else : #take frame of most popular word in random tweet sample of four or more letters words=randstream.allWords()['wordList'] bigEnough= for w in words : if len(w) >= 4 and w not in boringWords and "&" not in w: bigEnough.append(w) c = collections.Counter(bigEnough) topWords=c.most_common(20) theWord=topWords print(topWords, theWord) makeMovie = False stashfx = effx stashspeed = fxspeed setEffex(0,0) lockCamExposure(cam) displayString(theWord) cam.capture(basePath+"/lapseFrames/lapse-"+time.strftime("%Y%m%d-%H%M%S")+".jpg",resize=(320,200)) unlockCamExposure(cam) setEffex(stashfx,stashspeed) lapseFrames = glob(basePath+"lapseFrames/*.jpg") #if there are now 96 files in the frames folder, make a movie and tweet it out #NixieLapse print(len(lapseFrames),"lapse frames found") if len(lapseFrames) >=96 : print("making daily time lapse") delay=20 mresult = call(["gm","convert","-delay",str(delay),"-loop", "0", basePath+"/lapseFrames/*.jpg","Tlapse.gif"]) print("Make movie command result code = ",mresult) if mresult == 0 : uploadRetries = 0 while uploadRetries < 3 : try: pic =open("Tlapse.gif","rb") print(">>>>>>>>>>>>> Uploading Timelapse Movie ", datetime.datetime.now().strftime('%H:%M:%S.%f')) response = twitter.upload_media(media=pic ) print(">>>>>>>>>>>>> Updating status ", datetime.datetime.now().strftime('%H:%M:%S.%f')) twitter.update_status( status="This is how my day went: #NixieBotTimelapse", media_ids=[response['media_id']] ) print(">>>>>>>>>>>>> Done ", datetime.datetime.now().strftime('%H:%M:%S.%f')) uploadRetries = 200 except BaseException as e: print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Tweeting movie exception!" + str(e)) uploadRetries += 1 move(basePath+"Tlapse.gif", basePath+"lapseFrames/Tlapse"+time.strftime("%Y%m%d-%H%M%S")+ ".gif") for f in lapseFrames : os.remove(f) lapseDone = True return()In between calls to doTimeLapse the word displaying and picture taking routines save the image as a file with name composed of the string "lapse" then a timestamp.
doTimeLapse() first iterates through all files named lapse*.jpg, it discards any that were created in the first half of the current lapse period and keeps track of the youngest file it finds that was created in the second half of the lapse period.
If this process finds a youngest file it will move it into a subdirectory where all frames for the day's movie are kept.
If no file is found then it retrieves a list of all words used in current buffer of random tweets by invoking the allWords() method of the randstream object (this TwythonStreamer object is in charge of receiving random tweets and keeps a circular buffer of the last 1000 tweets received, this buffer is a deque ).
This word list is first iterated through to remove words that are too small or in the boringWords list.
A counter accepts values and compiles a dictionary of unique values against a count of how many times that value occurs.
Counters also have a handy most_common() method which is used in this case, to extract the most used word (actually for reasons lost in the mist of debugging time it extracts the top twenty words then picks the number one from those ... that should probably get neatened up one day).
Having found the most popular word it then displays it, takes a photo, then stores the image in the directory where the other timelapse frames are kept.
Next job is to see if there are a full day's worth of frames yet (and, in explaining all this to you I have found a potential bug, there's a hard coded value for number of frames per day when it should be calculated from the timeLapseInterval variable ... explaining your code to someone is a great technique for optimising and debugging! )
If a full day's worth of frames have been recorded then they are assembled into an animated gif with the "gm convert " command and that is posted to twitter (here there is substantial code duplication as there are other places where movies are posted to twitter... one day it might get refactored out into a separate function but this was a quick and dirty feature addition )
Finally the lapseDone flag is set so that the routine doesn't get called again next time round the main clock loop.
So there you go ... an insight into what happens when I start coding and keep adding things without a refactor ... lots of global variables serving mysterious purposes and code duplication, it still works though!