# This file is part of the source code of # gradint v0.9936 (c) 2002-2009 Silas S. Brown. GPL v3+. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # Start of recording.py - GUI-based management of recorded words try: import tkSnack except: tkSnack = 0 class InputSource(object): def startRec(self,outFile,lastStopRecVal=None): pass # start recording to outFile def stopRec(self): pass # stop recording def close(self): pass # stop everything def currentTime(self): return 0 # (makes sense only for PlayerInput, but here just in case) def __del__(self): self.close() class MicInput(InputSource): def __init__(self): rates = tkSnack.audio.rates() for rate in [22050, 16000, 24000, 44100]: if rate in rates: self.rate = rate ; break if rates: self.rate = max(rates) else: self.rate = None def startRec(self,outFile,lastStopRecVal=None): if not self.rate: return # TODO tell the user we can't record anything on this system (currently we just let them find out) self.sound = tkSnack.Sound(file=outFile, rate=self.rate, channels=1, encoding="Lin16") self.sound.record() def stopRec(self): self.sound.stop() class PlayerInput(InputSource): # play to speakers while recording to various destinations def __init__(self,fileToPlay,startNow=True,startTime=0): # (if startNow=False, starts when you start recording) global paranoid_file_management if use_unicode_filenames: fileToPlay=ensure_unicode(fileToPlay) else: assert not type(fileToPlay)==type(u"") if not paranoid_file_management and filter(lambda x:ord(x)>=128,list(fileToPlay)): paranoid_file_management = True # hack to try to work around a Tkinter fault on Linux with utf-8 filenames if paranoid_file_management: # try to ensure it's ready for reading if filelen(fileToPlay)<1048576: # only small - copy to temp 1st self.fileToDel = os.tempnam()+fileToPlay[fileToPlay.rfind(extsep):] open(self.fileToDel,"wb").write(open(fileToPlay,"rb").read()) fileToPlay=self.fileToDel else: open(fileToPlay) if fileToPlay.lower().endswith(dotwav) and filelen(fileToPlay)<1048576: self.sound=tkSnack.Sound(load=fileToPlay) # in-memory if <1M (saves problems with Windows keeping them open even after object deleted), TODO is this still needed now that .destroy() is called properly? (but might be a good idea to keep it in anyway) else: self.sound = tkSnack.Sound(file=fileToPlay) self.startSample = 0 self.sampleRate = self.sound.info()[1] self.length = self.sound.length()*1.0/self.sampleRate if not self.length: self.length=lengthOfSound(fileToPlay) # tkSnack bug workaround. NB don't just set it to 3 because it may be less than that, and user may press Record before the 3secs are up, expecting to record from mic. self.inCtor = 1 if startNow: self.startPlaying(max(0,int(startTime*self.sampleRate))) self.inCtor = 0 def startPlaying(self,curSample=0): theISM.nowPlaying = self tkSnack.audio.stop() # as we might be still in c'tor and just about to be assigned to replace the previously-playing sound (i.e. it might not have stopped yet), and we don't want to confuse elapsedTime self.sound.play(start=curSample) self.startSample = curSample self.autostop() def autostop(self): if not theISM or not theISM.nowPlaying==self or not tkSnack or not tkSnack.audio: return elapsedTime = self.elapsedTime() if elapsedTime>=self.length-self.startSample*1.0/self.sampleRate: self.close() else: import thread def stopMe(self): time.sleep(max(0.5,self.length-self.startSample*1.0/self.sampleRate-elapsedTime)) self.autostop() thread.start_new_thread(stopMe,(self,)) def elapsedTime(self): try: t=tkSnack.audio.elapsedTime() except: t=0.0 if t==0.0: t=self.length # if elapsedTime raises exception or returns an exact float 0.0, probably finished return t def currentSample(self): return int(self.elapsedTime()*self.sampleRate)+self.startSample def currentTime(self): return self.currentSample()*1.0/self.sampleRate def startRec(self,outFile,lastStopRecVal=None): if lastStopRecVal: self.recordingStartSample = lastStopRecVal elif theISM.nowPlaying==self: self.recordingStartSample = self.currentSample() else: self.startPlaying() self.recordingStartSample = 0 self.fileToWrite = outFile def stopRec(self,closing = False): if not hasattr(self,"fileToWrite"): return curSample = self.currentSample() self.sound.stop() self.sound.write(self.fileToWrite,start=self.recordingStartSample,end=curSample) if not closing: self.startPlaying(curSample) del self.fileToWrite return curSample def close(self): if not hasattr(self,"sound"): return # called twice? self.stopRec(True) if theISM.nowPlaying == self: theISM.nowPlaying = None self.sound.stop() self.sound.destroy() del self.sound if hasattr(self,"fileToDel"): os.unlink(self.fileToDel) theISM.finished(self) global theRecorderControls try: theRecorderControls except: theRecorderControls=0 if theRecorderControls: if self.inCtor: # tried to skip off end - DO ensure the GUI resets its controls when that happens, even if it has "protected" itself due to restarting the sample at different position theRecorderControls.current_recordFrom_button = theRecorderControls.old_recordFrom_button theRecorderControls.undoRecordFrom() if not tkSnack and macsound: # might still be able to use Audio Recorder if fileExists("AudioRecorder.zip"): unzip_and_delete("AudioRecorder.zip") if fileExists("Audio Recorder.app/plist"): # Audio Recorder with our special preferences list runAudioRecorderYet = 0 def MacStartRecording(): global runAudioRecorderYet if not runAudioRecorderYet: os.system("mv ~/Library/Preferences/com.benshan.AudioRecorder31.plist ~/Library/Preferences/com.benshan.AudioRecorder31.plist-OLD 2>/dev/null ; cp Audio\\ Recorder.app/plist ~/Library/Preferences/com.benshan.AudioRecorder31.plist; open Audio\\ Recorder.app") os.system("osascript -e 'Tell application \"Audio Recorder\" to Record'") runAudioRecorderYet = 1 def MacStopRecording(): os.system("osascript -e 'Tell application \"Audio Recorder\" to Stop'") MacRecordingFile = "/tmp/audiorec-output-for-gradint.wav" # specified in the plist def quitAudioRecorder(): if runAudioRecorderYet: os.system("osascript -e 'Tell application \"Audio Recorder\" to quit' ; rm ~/Library/Preferences/com.benshan.AudioRecorder31.plist ; mv ~/Library/Preferences/com.benshan.AudioRecorder31.plist-OLD ~/Library/Preferences/com.benshan.AudioRecorder31.plist 2>/dev/null") import atexit ; atexit.register(quitAudioRecorder) del MicInput class MicInput(InputSource): # Mac Audio Recorder version def startRec(self,outFile,lastStopRecVal=None): self.fileToWrite = outFile MacStartRecording() def stopRec(self): MacStopRecording() os.rename(MacRecordingFile,self.fileToWrite) tkSnack = "MicOnly" class InputSourceManager(object): def __init__(self): self.currentInputSource = None self.currentOutfile = self.nowPlaying = None # (nowPlaying is for PlayerInputSource to manage) def setInputSource(self,inputSource): self.stopRecording() if self.currentInputSource: self.currentInputSource.close() self.currentInputSource = inputSource def finished(self,inputSource): # called by the inputSource itself; we don't need to call its close() if not self.currentInputSource==inputSource: return # irrelevant self.stopRecording() self.currentInputSource = None def startRecording(self, newOutfile): if not self.currentInputSource: self.currentInputSource = MicInput() if self.currentOutfile: self.currentInputSource.startRec(newOutfile,self.currentInputSource.stopRec()) else: self.currentInputSource.startRec(newOutfile) self.currentOutfile = newOutfile def stopRecording(self): if self.currentOutfile: self.currentInputSource.stopRec() self.currentOutfile = None theISM = InputSourceManager() ; del InputSourceManager # singleton def wavToMp3(directory): # Compress all WAVs in directory to MP3 (CBR for gradint i/p) # don't worry about progress.txt - mergeProgress will recognise WAVs replaced with mp3 for l in os.listdir(directory): if l.lower().endswith(dotwav): needRetry = 1 ; tries = 0 while needRetry: tries += 1 needRetry = system("lame \"%s\" --cbr -b 48 -m m -o \"%s\"" % (directory+os.sep+l, directory+os.sep+l[:-len(dotwav)]+dotmp3)) if not needRetry: os.remove(directory+os.sep+l) elif paranoid_file_management and tries<10: time.sleep(0.5) else: show_warning("lame failed on "+directory+os.sep+l) ; break elif isDirectory(directory+os.sep+l): wavToMp3(directory+os.sep+l) def makeMp3Zips(baseDir,outDir,zipNo=0,direc=None): zipSplitThreshold = 5*1048576 # to be safe (as will split when it goes OVER that) if baseDir==outDir: return zipNo # omit elif not direc: for f in os.listdir(baseDir): zipNo = makeMp3Zips(baseDir,outDir,zipNo,f) elif isDirectory(baseDir+os.sep+direc): zipNo = makeMp3Zips(baseDir+os.sep+direc,outDir,zipNo) else: if zipNo: zipNo -= 1 zipfile = None while not zipfile or (fileExists(zipfile) and filelen(zipfile) >= zipSplitThreshold): zipNo += 1 zipfile = outDir+os.sep+"zipfile"+str(zipNo)+extsep+"zip" system("zip -9 \"%s\" \"%s\"" % (zipfile,baseDir+os.sep+direc)) return zipNo class RecorderControls: def __init__(self): self.snack_initialized = 0 self.currentDir = samplesDirectory setup_samplesDir_ifNec() self.coords2buttons = {} self.syncFlag = False self.always_enable_rerecord = False self.old_recordFrom_button = None def changeDir(self,newDir): self.undraw() self.currentDir = newDir self.draw() def global_rerecord(self): self.undraw() self.always_enable_rerecord = True self.draw() def finished(self): app.master.title(appTitle) self.undraw() del app.scanrow if recorderMode: app.cancel() else: app.todo.set_main_menu=1 def undraw(self): if hasattr(self,"renameToCancel"): del self.renameToCancel self.coords2buttons = {} del self.ourCanvas self.frame.pack_forget() theISM.setInputSource(None) def addButton(self,row,col,text,command,colspan=None): if (row,col) in self.coords2buttons: self.coords2buttons[(row,col)].grid_forget() self.coords2buttons[(row,col)] = Tkinter.Button(self.grid,text=text,command=command) if not colspan: if not col: colspan=1+3*len(self.languagesToDraw) else: colspan = 1 if col: self.coords2buttons[(row,col)].grid(row=row,column=col,columnspan=colspan) else: self.coords2buttons[(row,col)].grid(row=row,column=0,columnspan=colspan,sticky="w") def addLabel(self,row,col,utext): if (row,col) in self.coords2buttons: self.coords2buttons[(row,col)].grid_forget() self.coords2buttons[(row,col)] = Tkinter.Label(self.grid,text=utext,wraplength=int(self.ourCanvas.winfo_screenwidth()/(1+len(self.languagesToDraw)))) self.coords2buttons[(row,col)].grid(row=row,column=col,sticky="w") if col==0: self.coords2buttons[(row,col)].bind('<Button-1>',lambda *args:self.startRename(row,col,utext)) def addSynthLabel(self,filename,row,col): try: ftext = ensure_unicode(u8strip(open(filename).read().strip(wsp))) except IOError: return False l = Tkinter.Label(self.grid,text=ftext,wraplength=int(self.ourCanvas.winfo_screenwidth()/(1+len(self.languagesToDraw)))) l.grid(row=row,column=col,columnspan=3) l.bind('<Button-1>',lambda *args:self.startSynthEdit(l,row,col,filename)) return True def startSynthEdit(self,l,row,col,filename): if hasattr(self,"renameToCancel"): rr,cc = self.renameToCancel self.cancelRename(rr,cc) if l: l.grid_forget() editText,editEntry = addTextBox(self.grid,"nopack") try: editText.set(ensure_unicode(u8strip(open(filename).read().strip(wsp)))) except IOError: pass editEntry.grid(row=row,column=col,sticky='we',columnspan=3) editEntry.bind('<Return>',lambda *args:self.doEdit(editText,editEntry,row,col,filename)) editEntry.bind('<Escape>',lambda *args:self.cancelEdit(editEntry,row,col,filename)) editEntry.focus() def doEdit(self,editText,editEntry,row,col,filename): text = editText.get().encode("utf-8").strip(wsp) if text: open(filename,"w").write(text+"\n") else: try: os.remove(filename) except: pass self.cancelEdit(editEntry,row,col,filename) def cancelEdit(self,editEntry,row,col,filename): editEntry.grid_forget() labelAdded = self.addSynthLabel(filename,row,col) if hasattr(self.coords2buttons.get((row-1,col+1),""),"is_synth_label"): if labelAdded: self.addLabel(row-1,col+1,localise("(synth'd)")) else: self.addButton(row-1,col+1,text=localise("Synthesize"),command=(lambda *args:self.startSynthEdit(None,row,col,filename))) self.coords2buttons[(row-1,col+1)].is_synth_label = True def all2mp3_or_zip(self): self.CompressButton["text"] = localise("Compressing, please wait") if got_program("lame"): wavToMp3(self.currentDir) # TODO not in the GUI thread !! (but lock our other buttons while it's doing it) if got_program("zip") and (explorerCommand or winCEsound) and (not got_program("lame") or tkMessageBox.askyesno(app.master.title(),localise("All recordings have been compressed to MP3. Do you also want to make a ZIP file for sending as email?"))): try: os.mkdir(self.currentDir+os.sep+"zips") except: pass # already exists? numZips = makeMp3Zips(self.currentDir,self.currentDir+os.sep+"zips") if numZips: openDirectory(self.currentDir+os.sep+"zips") if numZips>1: app.todo.alert=localise("Please send the %d zip files as %d separate messages, in case one very large message doesn't get through.") % (zipNo,zipNo) else: app.todo.alert=localise("You may now send the zip file by email.") else: app.todo.alert=localise("No recordings found") self.undraw() ; self.draw() def startRename(self,row,col,filename): if hasattr(self,"renameToCancel"): rr,cc = self.renameToCancel self.cancelRename(rr,cc) self.renameToCancel = (row,col) if (row,col) in self.coords2buttons: self.coords2buttons[(row,col)].grid_forget() renameText,renameEntry = addTextBox(self.grid,"nopack") renameEntry['width']=min(8,len(filename)+2) renameEntry.theText = renameText renameEntry.origName = filename self.coords2buttons[(row,col)] = renameEntry renameEntry.grid(row=row,column=col,sticky='we') number=filename if number.startswith("word"): number=number[4:] if number and "0"<=number[0]<="9": renameText.set(number) selectAllFunc = selectAllButNumber else: renameText.set(filename) selectAllFunc = selectAll class E: pass e=E() ; e.widget = renameEntry self.ourCanvas.after(10,lambda *args:(self.scrollIntoView(e.widget),selectAllFunc(e))) renameEntry.bind('<Return>',lambda *args:self.doRename(row,col)) renameEntry.bind('<Escape>',lambda *args:self.cancelRename(row,col)) def doRename(self,row,col): if hasattr(self,"renameToCancel"): del self.renameToCancel origName = self.coords2buttons[(row,col)].origName newNames = filter(lambda x:x,self.coords2buttons[(row,col)].theText.get().split("\n")) # multiline paste, ignore blank lines for newName in newNames: if not origName: # extra lines - need to get their origNames if row==self.addMoreRow: self.addMore() elif not (row,col) in self.coords2buttons: row += 1 # skip extra row if there are notes origName=self.coords2buttons[(row,col)]["text"] if len(newNames)>1 and not '0'<=newName[0]<='9': # multiline paste and not numbered - we'd better keep the original number o2 = origName if o2.startswith("word"): o2=o2[4:] if intor0(o2): newName=o2+"-"+newName if isDirectory(unicode2filename(self.currentDir+os.sep+origName)): try: os.rename(unicode2filename(self.currentDir+os.sep+origName),unicode2filename(self.currentDir+os.sep+newName)) except OSError: tkMessageBox.showinfo(app.master.title(),localise("Could not rename %s to %s") % (origName,newName)) return self.addButton(row,col,text=newName,command=(lambda f=self.currentDir+os.sep+newName:self.changeDir(f))) else: # not a directory - rename individual files self.doStop() # just in case for lang in self.languagesToDraw: self.updateFile(unicode2filename(newName+"_"+lang+dotwav),row,self.languagesToDraw.index(lang),0) for ext in [dottxt, dotwav, dotmp3]: if fileExists_stat(unicode2filename(self.currentDir+os.sep+origName+"_"+lang+ext)): try: os.rename(unicode2filename(self.currentDir+os.sep+origName+"_"+lang+ext),unicode2filename(self.currentDir+os.sep+newName+"_"+lang+ext)) except OSError: tkMessageBox.showinfo(app.master.title(),localise("Could not rename %s to %s") % (origName+"_"+lang+ext,newName+"_"+lang+ext)) # TODO undo any that did succeed first! + check for destination-already-exists (OS may not catch it) return self.updateFile(unicode2filename(newName+"_"+lang+ext),row,self.languagesToDraw.index(lang),2) # TODO the 2 should be 1 if and only if we didn't just record it self.addLabel(row,col,newName) # TODO what about updating progress.txt with wildcard changes (cld be going too far - we have the move script in utilities) origName = None # get any others from the form row += 1 if len(newNames)==1 and row<self.addMoreRow: # put cursor on the next one if not (row,col) in self.coords2buttons: row += 1 # skip extra row if there are notes origName=self.coords2buttons[(row,col)]["text"] if not isDirectory(unicode2filename(self.currentDir+os.sep+origName)): self.startRename(row,0,origName) def cancelRename(self,row,col): if hasattr(self,"renameToCancel"): del self.renameToCancel origName = self.coords2buttons[(row,col)].origName if isDirectory(unicode2filename(self.currentDir+os.sep+origName)): self.addButton(row,col,text=origName,command=(lambda f=ensure_unicode(self.currentDir+os.sep+origName).encode('utf-8'):self.changeDir(f))) else: self.addLabel(row,col,origName) def updateFile(self,filename,row,languageNo,state,txtExists="unknown"): # state: 0 not exist, 1 already existed, 2 we just created it if not os.sep in filename: filename = self.currentDir+os.sep+filename recFilename = filename if recFilename.lower().endswith(dotmp3): recFilename=recFilename[:-len(dotmp3)]+dotwav # always record in WAV; can compress to MP3 after if state: # exists if not tkSnack or tkSnack=="MicOnly": self.addButton(row,2+3*languageNo,text="Play",command=(lambda f=filename:SampleEvent(f).play())) # but if got full tkSnack, might as well use setInputSource instead to be consistent with the non-_ version: else: self.addButton(row,2+3*languageNo,text=localise("Play"),command=(lambda f=filename:(self.doStop(),theISM.setInputSource(PlayerInput(f,not self.syncFlag))))) if tkSnack and (state==2 or self.always_enable_rerecord): self.addButton(row,3+3*languageNo,text=localise("Re-record"),command=(lambda f=recFilename,r=row,l=languageNo:self.doRecord(f,r,l,needToUpdatePlayButton=(not filename==recFilename)))) else: self.addLabel(row,3+3*languageNo,"") self.need_reRecord_enabler = not (not tkSnack) else: synthFilename = filename[:filename.rfind(extsep)]+dottxt if txtExists=="unknown": txtExists=fileExists(synthFilename) if txtExists: self.addLabel(row,2+3*languageNo,localise("(synth'd)")) elif get_synth_if_possible(self.languagesToDraw[languageNo],0): self.addButton(row,2+3*languageNo,text=localise("Synthesize"),command=(lambda *args:self.startSynthEdit(None,row+1,1+3*languageNo,synthFilename))) else: self.addLabel(row,2+3*languageNo,localise("(empty)")) self.coords2buttons[(row,2+3*languageNo)].is_synth_label = True self.addButton(row,3+3*languageNo,text=localise("Record"),command=(lambda f=recFilename,r=row,l=languageNo:self.doRecord(f,r,l))) def add_addMore_button(self): self.addButton(self.addMoreRow,0,text=localise("Add more words"),command=(lambda *args:self.addMore()),colspan=cond(self.need_reRecord_enabler,2,4)) if self.need_reRecord_enabler: self.addButton(self.addMoreRow,2,text=localise("Re-record"),command=(lambda *args:self.global_rerecord()),colspan=2) self.addButton(self.addMoreRow,4,text=localise("New folder"),command=(lambda *args:self.newFolder()),colspan=3) def del_addMore_button(self): self.coords2buttons[(self.addMoreRow,0)].grid_forget() # old 'add more' button if (self.addMoreRow,3) in self.coords2buttons: self.coords2buttons[(self.addMoreRow,1)].grid_forget() # old 're-record' button self.coords2buttons[(self.addMoreRow,4)].grid_forget() # old 'new folder' button def addMore(self): self.del_addMore_button() for r in range(5): if self.maxPrefix<=99: prefix = "word%02d" % self.maxPrefix else: prefix = "word_%04d" % self.maxPrefix self.addLabel(self.addMoreRow,0,utext=prefix) for lang in self.languagesToDraw: self.updateFile(unicode2filename(prefix+"_"+lang+dotwav),self.addMoreRow,self.languagesToDraw.index(lang),state=0) Tkinter.Label(self.grid,text=" "+localise(lang)+": ").grid(row=self.addMoreRow,column=1+3*self.languagesToDraw.index(lang)) self.addMoreRow += 2 ; self.maxPrefix += 1 self.add_addMore_button() def doRecord(self,filename,row,languageNo,needToUpdatePlayButton=False): if not tkSnack: if winCEsound: return tkMessageBox.showinfo(app.master.title(),localise("Sorry not implemented on PocketPC, but you can use PocketPC's Notes app (record %s to 1st file, %s to 2nd file, %s to 3rd etc) and Gradint can import this.") % (secondLanguage,firstLanguage,secondLanguage)) else: return tkMessageBox.showinfo(app.master.title(),localise("Sorry, cannot record on this computer because the tkSnack library (python-tksnack) is not installed.")) theISM.startRecording(filename) if needToUpdatePlayButton: self.updateFile(filename,row,languageNo,2) self.coords2buttons[(row,3+3*languageNo)]["text"]=localise("Stop") self.updateForStopOrChange() self.currentRecording = (filename,row,languageNo) self.coords2buttons[(row,3+3*languageNo)]["command"]=(lambda *args:self.doStop()) if app.scanrow.get()=="2": # "stop" self.coords2buttons[(row,3+3*languageNo)].focus() else: moved = 0 if app.scanrow.get()=="1": # move along 1st while languageNo+1<len(self.languagesToDraw): languageNo += 1 if (row,3+3*languageNo) in self.coords2buttons: self.coords2buttons[(row,3+3*languageNo)].focus() return languageNo = 0 # start of the row # fall-through - vertical movement for r in [row+1,row+2]: if r==self.addMoreRow: self.addMore() if (r,3+3*languageNo) in self.coords2buttons: return self.scrollIntoView(self.coords2buttons[(r,3+3*languageNo)]) def scrollIntoView(self,button): button.focus() self.continueScrollIntoView(button) def continueScrollIntoView(self,button): if not button.winfo_rooty() or not button.winfo_height(): return app.after(10,lambda *args:self.continueScrollIntoView(button)) if button.winfo_rooty()+button.winfo_height() >= self.ourCanvas.winfo_rooty()+self.ourCanvas.winfo_height(): # can't specify pixels, so have to keep advancing until we get it self.ourCanvas.yview("scroll","1","units") app.after(10,lambda *args:self.continueScrollIntoView(button)) def doStop(self): theISM.stopRecording() self.updateForStopOrChange() def updateForStopOrChange(self): if hasattr(self,"currentRecording"): filename,row,languageNo = self.currentRecording del self.currentRecording self.updateFile(filename,row,languageNo,2) def reconfigure_scrollbar(self): if not hasattr(app,"scanrow"): return # closing down if hasattr(self,"ourCanvas"): c = self.ourCanvas bbox = c.bbox(Tkinter.ALL) if hasattr(self,"oldCanvasBbox") and bbox==self.oldCanvasBbox: pass else: self.oldCanvasBbox = bbox c.config(scrollregion=bbox,width=bbox[2],height=min(c["height"],c.winfo_screenheight()/2,bbox[3])) if hasattr(self,"currentRecording") and not theISM.currentOutfile: self.doStop() # ensure GUI updates the recording button after player auto-stop (for want of a better place to put it) app.after(cond(winCEsound,3000,600),lambda *args:self.reconfigure_scrollbar()) def setSync(self,syncFlag): self.syncFlag = syncFlag def newFolder(self): count=0 while True: fname = "folder%d" % count try: os.mkdir(unicode2filename(self.currentDir+os.sep+fname)) except OSError: count += 1 ; continue break self.del_addMore_button() row=self.addMoreRow ; col=0 self.addLabel(row,col,fname) self.addMoreRow += 1 self.add_addMore_button() self.startRename(row,col,fname) def doRecordFrom(self,filename,row): self.doStop() theISM.setInputSource(PlayerInput(filename,not self.syncFlag)) self.current_recordFrom_button = (row, self.coords2buttons[(row,0)]) self.addButton(row,0,text=localise("Stop"),command=(lambda *args:(self.doStop(),theISM.setInputSource(MicInput()))),colspan=1) col = 1 for inc in [-30, -5, 5, 30]: if inc<0: text="<"+str(-inc) else: text=str(inc)+">" self.addButton(row,col,text=text,command=(lambda i=inc:(self.doStop(),self.protect_currentRecordFrom(),theISM.setInputSource(PlayerInput(filename,True,theISM.currentInputSource.currentTime()+i)),self.restore_currentRecordFrom()))) col += 1 def protect_currentRecordFrom(self): self.old_recordFrom_button, self.current_recordFrom_button = self.current_recordFrom_button, None def restore_currentRecordFrom(self): self.current_recordFrom_button, self.old_recordFrom_button = self.old_recordFrom_button, None def undoRecordFrom(self): if hasattr(self,"current_recordFrom_button") and self.current_recordFrom_button: row, button = self.current_recordFrom_button for col in range(1+3*len(self.languagesToDraw)): if (row,col) in self.coords2buttons: self.coords2buttons[(row,col)].grid_forget() del self.coords2buttons[(row,col)] button.grid(row=row,column=0,columnspan=1+3*len(self.languagesToDraw),sticky="w") self.coords2buttons[(row,0)] = button del self.current_recordFrom_button def do_openInExplorer(self,*args): openDirectory(self.currentDir) tkMessageBox.showinfo(app.master.title(),localise("Gradint has opened the current folder for you to work on. When you press OK, Gradint will re-scan the folder for new files.")) self.undraw() self.draw() def do_recordFromFile(self,*args): if not tkSnack or tkSnack=="MicOnly": return tkMessageBox.showinfo(app.master.title(),localise("Sorry, cannot record from file on this computer because the tkSnack library (python-tksnack) is not installed")) msg1 = localise("You can record from an existing recording (i.e. copy parts from it) if you first put the existing recording into the samples folder and then press its Play button.")+"\n\n" if not self.has_recordFrom_buttons: openDirectory(self.currentDir) tkMessageBox.showinfo(app.master.title(),msg1+localise("Gradint has opened the current folder for you to do this. When you press OK, Gradint will re-scan the folder for new files.")) self.undraw() self.draw() msg1 = "" self.setSync(tkMessageBox.askyesno(app.master.title(),localise(msg1+"Do you want your next Play operation to be delayed until you also press a Record button?"))) def draw(self): self.languagesToDraw = [secondLanguage,firstLanguage] # each lang cn take 3 columns, starting at column 1 (DO need to regenerate this every draw - languages may have changed!) app.master.title(localise("Recordings manager")) if not self.snack_initialized: if tkSnack and not tkSnack=="MicOnly": tkSnack.initializeSnack(app) if paranoid_file_management and tkSnack.audio.playLatency()<500: tkSnack.audio.playLatency(500) # at least 500ms latency if we're paranoid_file_management, just in case (since tkSnack.audio.elapsedTime does not account for hold-ups) self.snack_initialized = True if not hasattr(app,"scanrow"): app.scanrow = Tkinter.StringVar(app) # (keep StringVars in app not self to avoid d'tor confusion when close box pressed) app.scanrow.set("0") self.reconfigure_scrollbar() if tkSnack: theISM.setInputSource(MicInput()) self.frame=Tkinter.Frame(app.leftPanel) ; self.frame.pack() self.need_reRecord_enabler = 0 # no previously-existing words yet (when we get existing words we 'lock' them and have to unlock by pressing a global 'rerecord' button 1st, just in case) r = Tkinter.Frame(self.frame) r.grid(row=1,sticky="e",columnspan=2) Tkinter.Label(r,text=localise("Action of spacebar during recording")).pack() r=Tkinter.Frame(r) ; r.pack() Tkinter.Radiobutton(r, text=localise("move down"), variable=app.scanrow, value="0", indicatoron=1).pack({"side":"left"}) Tkinter.Radiobutton(r, text=localise("move along"), variable=app.scanrow, value="1", indicatoron=1).pack({"side":"left"}) Tkinter.Radiobutton(r, text=localise("stop"), variable=app.scanrow, value="2", indicatoron=1).pack({"side":"left"}) self.grid,self.ourCanvas = setupScrollbar(self.frame,2) if hasattr(self,"oldCanvasBbox"): del self.oldCanvasBbox # unconditionally reconfigure scrollbar even if bounds are unchanged curRow = 0 ; prefix2row = {} maxPrefix = 0 ; self.has_recordFrom_buttons = False if not self.currentDir==samplesDirectory: self.addButton(curRow,0,text=localise("(Up)"),command=(lambda f=self.currentDir[:self.currentDir.rindex(os.sep)]:self.changeDir(f))) curRow += 1 l = os.listdir(self.currentDir) ; l.sort() allLangs = list2set([firstLanguage,secondLanguage]+possible_otherLanguages+otherLanguages) hadDirectories = False for fname in l: flwr = fname.lower() if isDirectory(self.currentDir+os.sep+fname): if not flwr in ["zips","utils","advanced utilities"]: # NOT "prompts", that can be browsed newDir = self.currentDir+os.sep+fname self.addButton(curRow,0,text=filename2unicode(fname),command=(lambda f=newDir:self.changeDir(f))) curRow += 1 hadDirectories = True elif "_" in fname and languageof(fname) in allLangs: # something_lang where lang is a recognised language (don't just take "any _" because some podcasts etc will have _ in them) # TODO what if there are variants? (currently languageof won't recognise so will drop to the next case!) # TODO and what about letting them record _explain_ files etc from the GUI, + toggling !poetry prefix=fname[:fname.rindex("_")] wprefix = prefix if wprefix.startswith("word"): wprefix=wprefix[4:] # ditch any "word" before the integer try: iprefix = int(wprefix) except: iprefix = -1 if iprefix>maxPrefix: maxPrefix=iprefix # max existing numerical prefix if (flwr.endswith(dotwav) or flwr.endswith(dotmp3) or flwr.endswith(dottxt)): # even if not languageof(fname) in self.languagesToDraw e.g. for prompts - helps setting up gradint in a language it doesn't have prompts for (creates blank rows for the prefixes that other languages use). TODO do we want to add 'and languageof(fname) in self.languagesToDraw' if NOT in prompts? if not prefix in prefix2row: self.addLabel(curRow,0,utext=filename2unicode(prefix)) foundTxt = {} for lang in self.languagesToDraw: if prefix+"_"+lang+dottxt in l: foundTxt[lang]=(self.currentDir+os.sep+prefix+"_"+lang+dottxt,1+3*self.languagesToDraw.index(lang)) prefix2row[prefix] = curRow for lang in self.languagesToDraw: self.updateFile(prefix+"_"+lang+dotwav,curRow,self.languagesToDraw.index(lang),state=0,txtExists=(lang in foundTxt)) Tkinter.Label(self.grid,text=" "+localise(lang)+": ").grid(row=curRow,column=1+3*self.languagesToDraw.index(lang)) for filename,col in foundTxt.values(): self.addSynthLabel(filename,curRow+1,col) curRow += 2 if languageof(fname) in self.languagesToDraw and not flwr.endswith(dottxt): self.updateFile(fname,prefix2row[prefix],self.languagesToDraw.index(languageof(fname)),state=1) elif flwr.endswith(dotwav) or flwr.endswith(dotmp3) and tkSnack and not tkSnack=="MicOnly": # no _ in it but we can still play it for splitting self.addButton(curRow,0,text=(localise("Record from %s") % (filename2unicode(fname),)),command=(lambda r=curRow,f=self.currentDir+os.sep+fname:self.doRecordFrom(f,r))) self.has_recordFrom_buttons = True curRow += 1 self.addMoreRow = curRow ; self.maxPrefix = maxPrefix+1 self.add_addMore_button() if curRow<3 and not hadDirectories: self.addMore() # anyway r=Tkinter.Frame(self.frame) r.grid(row=3,columnspan=2) r2 = Tkinter.Frame(r) ; r2.pack(side="left") Tkinter.Button(r2,text=localise("Advanced"),command=self.do_openInExplorer).pack() Tkinter.Button(r2,text=localise("Record from file"),command=self.do_recordFromFile).pack() if got_program("lame"): self.CompressButton = Tkinter.Button(r,text=localise("Compress all"),command=(lambda *args:self.all2mp3_or_zip())) # was "Compress all recordings" but it takes too much width # TODO else can we see if it's possible to get the encoder on the fly, like in the main screen? (would need some restructuring) elif got_program("zip") and (explorerCommand or winCEsound): self.CompressButton = Tkinter.Button(r,text=localise("Zip for email"),command=(lambda *args:self.all2mp3_or_zip())) if hasattr(self,"CompressButton"): self.CompressButton.pack(side="left") Tkinter.Button(r,text=localise(cond(recorderMode,"Quit","Back to main menu")),command=self.finished).pack(side="left") Tkinter.Label(self.frame,text="Choose a word and start recording. Then press space to advance (see control at top). You can also browse and manage previous recordings. Click on filenames at left to rename (multi-line pastes are allowed); click on synthesized text to edit it.",wraplength=cond(olpc or winCEsound,self.ourCanvas.winfo_screenwidth(),min(int(self.ourCanvas.winfo_screenwidth()*.7),512))).grid(row=4,columnspan=2) # (512-pixel max. so the column isn't too wide to read on wide screens, TODO increase if the font is large) # (Don't worry about making the text files editable - editable filenames should be enough + easier to browse the result outside Gradint; can include both languages in the filename if you like - hope the users figure this out as we don't want to make the instructions too complex) def doRecWords(): # called from GUI thread if hasattr(app,"LessonRow"): app.thin_down_for_lesson() # else recorderMode app.Label.pack_forget() ; app.CancelRow.pack_forget() global theRecorderControls try: theRecorderControls except: theRecorderControls=RecorderControls() theRecorderControls.draw()