0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com> 0004 ### Copyright (C) 2003-2004 Steven Palm <n9yty@n9yty.com> 0005 ### 0006 ### This program is free software; you can redistribute it and/or modify 0007 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0008 ### 0009 ### $Id: ringers.py 4379 2007-08-28 03:06:58Z djpham $ 0010 0011 from __future__ import with_statement 0012 0013 import os 0014 import time 0015 import wx 0016 from wx.lib import masked 0017 0018 import fileview 0019 import guihelper 0020 import pubsub 0021 import aggregatedisplay 0022 import wallpaper 0023 import common 0024 import fileinfo 0025 import conversions 0026 import helpids 0027 import guihelper 0028 import rangedslider 0029 0030 ### 0031 ### Ringers 0032 ### 0033 0034 class DisplayItem(fileview.FileViewDisplayItem): 0035 0036 datakey='ringtone-index' 0037 datatype='Audio' # used in the tooltip 0038 0039 0040 class RingerView(fileview.FileView): 0041 CURRENTFILEVERSION=2 0042 0043 # this is only used to prevent the pubsub module 0044 # from being GC while any instance of this class exists 0045 __publisher=pubsub.Publisher 0046 media_notification_type=pubsub.ringtone_type 0047 database_key='ringtone-index' 0048 media_key='ringtone' 0049 default_origin="ringers" 0050 0051 def __init__(self, mainwindow, parent, media_root, id=-1): 0052 self.mainwindow=mainwindow 0053 self._data={self.database_key: {}} 0054 fileview.FileView.__init__(self, mainwindow, parent, media_root, "ringtone-watermark") 0055 self.wildcard="Audio files|*.wav;*.mid;*.qcp;*.mp3;*.pmd;*.wma|Midi files|*.mid|Purevoice files|*.qcp|MP3 files|*.mp3|WMA files|*.wma|PMD/CMX files|*.pmd|All files|*.*" 0056 self.thedir=self.mainwindow.ringerpath 0057 self.modified=False 0058 pubsub.subscribe(self.OnListRequest, pubsub.REQUEST_RINGTONES) 0059 pubsub.subscribe(self.OnDictRequest, pubsub.REQUEST_RINGTONE_INDEX) 0060 self._raw_media=self._shift_down=False 0061 0062 def updateprofilevariables(self, profile): 0063 self.maxlen=profile.MAX_RINGTONE_BASENAME_LENGTH 0064 self.filenamechars=profile.RINGTONE_FILENAME_CHARS 0065 self.excluded_origins=profile.excluded_ringtone_origins 0066 for o in profile.ringtoneorigins: 0067 self.media_root.AddMediaNode(o, self) 0068 0069 def OnListRequest(self, msg=None): 0070 l=[self._data[self.database_key][x].name \ 0071 for x in self._data[self.database_key] \ 0072 if self._data[self.database_key][x].origin not in self.excluded_origins ] 0073 l.sort() 0074 pubsub.publish(pubsub.ALL_RINGTONES, l) 0075 0076 def OnDictRequest(self, msg=None): 0077 pubsub.publish(pubsub.ALL_RINGTONE_INDEX, self._data[self.database_key].copy()) 0078 0079 def GetDeleteInfo(self): 0080 return guihelper.ART_DEL_RINGER, "Delete Ringer" 0081 0082 def GetAddInfo(self): 0083 return guihelper.ART_ADD_RINGER, "Add Ringer" 0084 0085 def OnAdd(self, evt=None): 0086 self._raw_media=self._shift_down 0087 super(RingerView, self).OnAdd(evt) 0088 # reset the fla 0089 self._shift_down=False 0090 0091 def GetItemThumbnail(self, item, w, h): 0092 assert w==self.thumbnail.GetWidth() and h==self.thumbnail.GetHeight() 0093 return self.thumbnail 0094 0095 def GetSections(self): 0096 # work out section and item sizes 0097 self.thumbnail=wx.Image(guihelper.getresourcefile('ringer.png')).ConvertToBitmap() 0098 0099 dc=wx.MemoryDC() 0100 dc.SelectObject(wx.EmptyBitmap(100,100)) # unused bitmap needed to keep wxMac happy 0101 h=dc.GetTextExtent("I")[1] 0102 itemsize=self.thumbnail.GetWidth()+160, max(self.thumbnail.GetHeight(), h*4+DisplayItem.PADDING)+DisplayItem.PADDING*2 0103 0104 # get all the items 0105 items=[DisplayItem(self, key) for key in self._data[self.database_key] if self._data[self.database_key][key].mediadata!=None] 0106 0107 self.sections=[] 0108 0109 if len(items)==0: 0110 return self.sections 0111 0112 # get the current sorting type 0113 for sectionlabel, items in self.organizeby_Origin(items): 0114 self.media_root.AddMediaNode(sectionlabel, self) 0115 sh=aggregatedisplay.SectionHeader(sectionlabel) 0116 sh.itemsize=itemsize 0117 for item in items: 0118 item.thumbnailsize=self.thumbnail.GetWidth(), self.thumbnail.GetHeight() 0119 # sort items by name 0120 items.sort(self.CompareItems) 0121 self.sections.append( (sh, items) ) 0122 return [sh for sh,items in self.sections] 0123 0124 def GetItemsFromSection(self, sectionnumber, sectionheader): 0125 return self.sections[sectionnumber][1] 0126 0127 def organizeby_Origin(self, items): 0128 types={} 0129 for item in items: 0130 t=item.origin 0131 if t is None: t="Default" 0132 l=types.get(t, []) 0133 l.append(item) 0134 types[t]=l 0135 0136 keys=types.keys() 0137 keys.sort() 0138 return [ (key, types[key]) for key in types] 0139 0140 def GetItemSize(self, sectionnumber, sectionheader): 0141 return sectionheader.itemsize 0142 0143 def GetFileInfoString(self, string): 0144 return fileinfo.identify_audiostring(string) 0145 0146 def GetFileInfo(self, filename): 0147 return fileinfo.identify_audiofile(filename) 0148 0149 def ReplaceContents(self, name, origin, new_file_name): 0150 """Replace the contents of 'file_name' by the contents of 0151 'new_file_name' by going through the image converter dialog 0152 """ 0153 file_stat=os.stat(new_file_name) 0154 mtime=file_stat.st_mtime 0155 afi=fileinfo.identify_audiofile(new_file_name) 0156 if afi.size<=0: 0157 return # zero length file or other issues 0158 newext,convertinfo=self.mainwindow.phoneprofile.QueryAudio( 0159 None, common.getext(new_file_name), afi) 0160 if convertinfo is not afi: 0161 filedata=None 0162 try: 0163 filedata=self.ConvertFormat(new_file_name, convertinfo) 0164 except: 0165 pass 0166 if filedata is None: 0167 return 0168 else: 0169 filedata=open(new_file_name, "rb").read() 0170 # check for the size limit on the file, if specified 0171 max_size=getattr(convertinfo, 'MAXSIZE', None) 0172 if max_size is not None and len(filedata)>max_size: 0173 # the data is too big 0174 self.log('ringtone %s is too big!'%common.basename(file)) 0175 with guihelper.WXDialogWrapper(wx.MessageDialog(self, 0176 'Ringtone %s may be too big. Do you want to proceed anway?'%common.basename(file), 0177 'Warning', 0178 style=wx.YES_NO|wx.ICON_ERROR), 0179 True) as (dlg, dlg_resp): 0180 if dlg_resp==wx.ID_NO: 0181 return 0182 self.AddToIndex(name, origin, filedata, self._data, mtime) 0183 0184 @guihelper.BusyWrapper 0185 def OnAddFiles(self, filenames): 0186 for file in filenames: 0187 if file is None: continue # failed dragdrop? 0188 file_stat=os.stat(file) 0189 mtime=file_stat.st_mtime 0190 if self._raw_media: 0191 target=self.get_media_name_from_filename(file) 0192 data=open(file, 'rb').read() 0193 self.AddToIndex(target, self.active_section, data, self._data, mtime) 0194 else: 0195 # do we want to convert file? 0196 afi=fileinfo.identify_audiofile(file) 0197 if afi.size<=0: continue # zero length file or other issues 0198 newext,convertinfo=self.mainwindow.phoneprofile.QueryAudio(None, common.getext(file), afi) 0199 if convertinfo is not afi: 0200 filedata=None 0201 wx.EndBusyCursor() 0202 try: 0203 filedata=self.ConvertFormat(file, convertinfo) 0204 finally: 0205 # ensure they match up 0206 wx.BeginBusyCursor() 0207 if filedata is None: 0208 continue 0209 else: 0210 filedata=open(file, "rb").read() 0211 # check for the size limit on the file, if specified 0212 max_size=getattr(convertinfo, 'MAXSIZE', None) 0213 if max_size is not None and len(filedata)>max_size: 0214 # the data is too big 0215 self.log('ringtone %s is too big!'%common.basename(file)) 0216 with guihelper.WXDialogWrapper(wx.MessageDialog(self, 0217 'Ringtone %s may be too big. Do you want to proceed anway?'%common.basename(file), 0218 'Warning', 0219 style=wx.YES_NO|wx.ICON_ERROR), 0220 True) as (dlg, dlg_resp): 0221 if dlg_resp==wx.ID_NO: 0222 continue 0223 target=self.get_media_name_from_filename(file, newext) 0224 self.AddToIndex(target, self.active_section, filedata, self._data, mtime) 0225 self.OnRefresh() 0226 0227 def ConvertFormat(self, file, convertinfo): 0228 with guihelper.WXDialogWrapper(ConvertDialog(self, file, convertinfo), 0229 True) as (dlg, retcode): 0230 return dlg.newfiledata if retcode==wx.ID_OK else None 0231 0232 def versionupgrade(self, dict, version): 0233 """Upgrade old data format read from disk 0234 0235 @param dict: The dict that was read in 0236 @param version: version number of the data on disk 0237 """ 0238 0239 # version 0 to 1 upgrade 0240 if version==0: 0241 version=1 # the are the same 0242 0243 # 1 to 2 etc 0244 if version==1: 0245 print "converting to version 2" 0246 version=2 0247 d={} 0248 input=dict.get(self.database_key, {}) 0249 for i in input: 0250 d[i]={'name': input[i]} 0251 dict[self.database_key]=d 0252 return dict 0253 0254 class ConvertDialog(wx.Dialog): 0255 0256 ID_CONVERT=wx.NewId() 0257 ID_PLAY=wx.NewId() 0258 ID_PLAY_CLIP=wx.NewId() 0259 ID_STOP=wx.NewId() 0260 ID_TIMER=wx.NewId() 0261 ID_SLIDER=wx.NewId() 0262 0263 # we need to offer all types here rather than using derived classes as the phone may support several 0264 # alternatives 0265 0266 PARAMETERS={ 0267 'MP3': { 0268 'formats': ["MP3"], 0269 'samplerates': ["16000", "22050", "24000", "32000", "44100", "48000"], 0270 'channels': ["1", "2"], 0271 'bitrates': ["8", "16", "24", "32", "40", "48", "56", "64", "80", "96", "112", "128", "144", "160", "192", "224", "256", "320"], 0272 'setup': 'mp3setup', 0273 'convert': 'mp3convert', 0274 'filelength': 'mp3filelength', 0275 'final': 'mp3final', 0276 }, 0277 0278 'QCP': { 0279 'formats': ["QCP"], 0280 'samplerates': ["8000"], 0281 'channels': ["1"], 0282 'bitrates': ["13000"], 0283 'optimization': ['0-Best Sound Quality', '1', '2', '3-Smallest File Size'], 0284 'setup': 'qcpsetup', 0285 'convert': 'qcpconvert', 0286 'filelength': 'qcpfilelength', 0287 'final': 'qcpfinal', 0288 } 0289 0290 } 0291 0292 0293 0294 def __init__(self, parent, file, convertinfo): 0295 wx.Dialog.__init__(self, parent, title="Convert Audio File", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.SYSTEM_MENU|wx.MAXIMIZE_BOX) 0296 self.file=file 0297 self.convertinfo=convertinfo 0298 self.afi=None 0299 self.temporaryfiles=[] 0300 self.wavfile=common.gettempfilename("wav") # full length wav equivalent 0301 self.clipwavfile=common.gettempfilename("wav") # used for clips from full length wav 0302 self.temporaryfiles.extend([self.wavfile, self.clipwavfile]) 0303 0304 getattr(self, self.PARAMETERS[convertinfo.format]['setup'])() 0305 0306 vbs=wx.BoxSizer(wx.VERTICAL) 0307 # create the covert controls 0308 self.create_convert_pane(vbs, file, convertinfo) 0309 # and the crop controls 0310 self.create_crop_panel(vbs) 0311 0312 vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALL|wx.ALIGN_RIGHT, 5) 0313 0314 self.SetSizer(vbs) 0315 vbs.Fit(self) 0316 0317 # diable various things 0318 self.FindWindowById(wx.ID_OK).Enable(False) 0319 for i in self.cropids: 0320 self.FindWindowById(i).Enable(False) 0321 0322 0323 # Events 0324 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk) 0325 wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnCancel) 0326 wx.EVT_TIMER(self, self.ID_TIMER, self.OnTimer) 0327 wx.EVT_BUTTON(self, wx.ID_HELP, lambda _: wx.GetApp().displayhelpid(helpids.ID_DLG_AUDIOCONVERT)) 0328 0329 # timers and sounds 0330 self.sound=None 0331 self.timer=wx.Timer(self, self.ID_TIMER) 0332 0333 # wxPython wrapper on Mac raises NotImplemented for wx.Sound.Stop() so 0334 # we hack around that by supplying a zero length wav to play instead 0335 if guihelper.IsMac(): 0336 self.zerolenwav=guihelper.getresourcefile("zerolen.wav") 0337 0338 0339 def create_convert_pane(self, vbs, file, convertinfo): 0340 params=self.PARAMETERS[convertinfo.format] 0341 # convert bit 0342 bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Convert"), wx.VERTICAL) 0343 bs.Add(wx.StaticText(self, -1, "Input File: "+file), 0, wx.ALL, 5) 0344 gs=wx.FlexGridSizer(2, 4, 5, 5) 0345 gs.Add(wx.StaticText(self, -1, "New Type"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 0346 self.type=wx.ComboBox(self, style=wx.CB_DROPDOWN|wx.CB_READONLY, choices=params['formats']) 0347 gs.Add(self.type, 0, wx.ALL|wx.EXPAND, 5) 0348 gs.Add(wx.StaticText(self, -1, "Sample Rate (per second)"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 0349 self.samplerate=wx.ComboBox(self, style=wx.CB_DROPDOWN|wx.CB_READONLY, choices=params['samplerates']) 0350 gs.Add(self.samplerate, 0, wx.ALL|wx.EXPAND, 5) 0351 gs.Add(wx.StaticText(self, -1, "Channels (Mono/Stereo)"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 0352 self.channels=wx.ComboBox(self, style=wx.CB_DROPDOWN|wx.CB_READONLY, choices=params['channels']) 0353 gs.Add(self.channels, 0, wx.ALL|wx.EXPAND, 5) 0354 gs.Add(wx.StaticText(self, -1, "Bitrate (kbits per second)"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 0355 self.bitrate=wx.ComboBox(self, style=wx.CB_DROPDOWN|wx.CB_READONLY, choices=params['bitrates']) 0356 gs.Add(self.bitrate, 0, wx.ALL|wx.EXPAND, 5) 0357 if params.has_key('optimization'): 0358 gs.Add(wx.StaticText(self, -1, 'Optimization'), 0, 0359 wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 0360 self.optimization=wx.ComboBox(self, 0361 style=wx.CB_DROPDOWN|wx.CB_READONLY, 0362 choices=params['optimization']) 0363 self.optimization.SetSelection(1) 0364 gs.Add(self.optimization, 0, wx.ALL|wx.EXPAND, 5) 0365 gs.AddGrowableCol(1, 1) 0366 gs.AddGrowableCol(3, 1) 0367 bs.Add(gs, 0, wx.EXPAND) 0368 0369 bs.Add(wx.Button(self, self.ID_CONVERT, "Convert"), 0, wx.ALIGN_RIGHT|wx.ALL, 5) 0370 0371 vbs.Add(bs, 0, wx.EXPAND|wx.ALL, 5) 0372 # Fill out fields - we explicitly set even when not necessary due to bugs on wxMac 0373 if self.type.GetCount()==1: 0374 self.type.SetSelection(0) 0375 else: 0376 self.type.SetStringSelection(convertinfo.format) 0377 0378 if self.channels.GetCount()==1: 0379 self.channels.SetSelection(0) 0380 else: 0381 self.channels.SetStringSelection(`convertinfo.channels`) 0382 0383 if self.bitrate.GetCount()==1: 0384 self.bitrate.SetSelection(0) 0385 else: 0386 self.bitrate.SetStringSelection(`convertinfo.bitrate`) 0387 0388 if self.samplerate.GetCount()==1: 0389 self.samplerate.SetSelection(0) 0390 else: 0391 self.samplerate.SetStringSelection(`convertinfo.samplerate`) 0392 0393 # Events 0394 wx.EVT_BUTTON(self, self.ID_CONVERT, self.OnConvert) 0395 0396 def create_crop_panel(self, vbs): 0397 # crop bit 0398 bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Crop"), wx.VERTICAL) 0399 hbs=wx.BoxSizer(wx.HORIZONTAL) 0400 hbs.Add(wx.StaticText(self, -1, "Current Position"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 0401 self.positionlabel=wx.StaticText(self, -1, "0 ") 0402 hbs.Add(self.positionlabel, 0, wx.ALL, 5) 0403 hbs.Add(wx.StaticText(self, -1, "Est. Clip File length"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 0404 self.lengthlabel=wx.StaticText(self, -1, "0 ") 0405 hbs.Add(self.lengthlabel, 0, wx.ALL, 5) 0406 bs.Add(hbs, 0, wx.ALL, 5) 0407 # the start & end manual entry items 0408 hbs=wx.GridSizer(-1, 2, 0, 0) 0409 hbs.Add(wx.StaticText(self, -1, 'Clip Start (sec):'), 0, wx.EXPAND|wx.ALL, 5) 0410 self.clip_start=masked.NumCtrl(self, wx.NewId(), fractionWidth=2) 0411 hbs.Add(self.clip_start, 1, wx.EXPAND|wx.ALL, 5) 0412 hbs.Add(wx.StaticText(self, -1, 'Clip Duration (sec):'), 0, wx.EXPAND|wx.ALL, 5) 0413 self.clip_duration=masked.NumCtrl(self, wx.NewId(), fractionWidth=2) 0414 hbs.Add(self.clip_duration, 1, wx.EXPAND|wx.ALL, 5) 0415 hbs.Add(wx.StaticText(self, -1, 'Volume Adjustment (dB):'), 0, 0416 wx.EXPAND|wx.ALL, 5) 0417 self.clip_volume=masked.NumCtrl(self, wx.NewId(), fractionWidth=1) 0418 hbs.Add(self.clip_volume, 1, wx.EXPAND|wx.ALL, 5) 0419 clip_set_btn=wx.Button(self, wx.NewId(), 'Set') 0420 hbs.Add(clip_set_btn, 0, wx.EXPAND|wx.ALL, 5) 0421 bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5) 0422 hbs=wx.BoxSizer(wx.HORIZONTAL) 0423 self.slider=rangedslider.RangedSlider(self, id=self.ID_SLIDER, size=(-1, 30)) 0424 hbs.Add(self.slider, 1, wx.EXPAND|wx.ALL, 5) 0425 bs.Add(hbs, 1, wx.EXPAND|wx.ALL, 5) 0426 hbs=wx.BoxSizer(wx.HORIZONTAL) 0427 hbs.Add(wx.Button(self, self.ID_STOP, "Stop"), 0, wx.ALL, 5) 0428 hbs.Add(wx.Button(self, self.ID_PLAY, "Play Position"), 0, wx.ALL, 5) 0429 hbs.Add(wx.Button(self, self.ID_PLAY_CLIP, "Play Clip"), 0, wx.ALL, 5) 0430 bs.Add(hbs, 0, wx.ALL|wx.ALIGN_RIGHT, 5) 0431 vbs.Add(bs, 0, wx.EXPAND|wx.ALL, 5) 0432 wx.EVT_BUTTON(self, self.ID_PLAY, self.OnPlayPosition) 0433 wx.EVT_BUTTON(self, self.ID_PLAY_CLIP, self.OnPlayClip) 0434 wx.EVT_BUTTON(self, self.ID_STOP, self.OnStop) 0435 wx.EVT_BUTTON(self, clip_set_btn.GetId(), self.OnSetClip) 0436 0437 rangedslider.EVT_POS_CHANGED(self, self.ID_SLIDER, self.OnSliderCurrentChanged) 0438 rangedslider.EVT_CHANGING(self, self.ID_SLIDER, self.OnSliderChanging) 0439 0440 self.cropids=[self.ID_SLIDER, self.ID_STOP, self.ID_PLAY, 0441 self.ID_PLAY_CLIP, self.clip_start.GetId(), 0442 self.clip_duration.GetId(), self.clip_volume.GetId(), 0443 clip_set_btn.GetId()] 0444 0445 @guihelper.BusyWrapper 0446 def OnConvert(self, _): 0447 self.OnStop() 0448 for i in self.cropids: 0449 self.FindWindowById(i).Enable(False) 0450 self.FindWindowById(wx.ID_OK).Enable(False) 0451 getattr(self, self.PARAMETERS[self.convertinfo.format]['convert'])() 0452 self.wfi=fileinfo.getpcmfileinfo(self.wavfile) 0453 max_duration=round(self.wfi.duration, 2)+0.01 0454 self.clip_start.SetParameters(min=0.0, max=max_duration, limited=True) 0455 self.clip_duration.SetParameters(min=0.0, max=max_duration, limited=True) 0456 self.UpdateCrop() 0457 for i in self.cropids: 0458 self.FindWindowById(i).Enable(True) 0459 self.FindWindowById(wx.ID_OK).Enable(True) 0460 0461 def UpdateCrop(self): 0462 self.positionlabel.SetLabel("%.1f secs" % (self.slider.GetCurrent()*self.wfi.duration),) 0463 duration=(self.slider.GetEnd()-self.slider.GetStart())*self.wfi.duration 0464 self.clip_start.SetValue(self.slider.GetStart()*self.wfi.duration) 0465 self.clip_duration.SetValue(duration) 0466 v=getattr(self, self.PARAMETERS[self.convertinfo.format]['filelength'])(duration) 0467 self.lengthlabel.SetLabel("%s" % (v,)) 0468 0469 0470 0471 def OnPlayClip(self,_): 0472 self._Play(self.slider.GetStart(), self.slider.GetEnd(), 0473 self.clip_volume.GetValue()) 0474 0475 def OnPlayPosition(self, _): 0476 self._Play(self.slider.GetCurrent(), 1.0) 0477 0478 def _Play(self, start, end, volume=None): 0479 self.OnStop() 0480 assert start<=end 0481 self.playstart=start 0482 self.playend=end 0483 0484 self.playduration=(self.playend-self.playstart)*self.wfi.duration 0485 0486 conversions.trimwavfile(self.wavfile, self.clipwavfile, 0487 self.playstart*self.wfi.duration, 0488 self.playduration, volume) 0489 self.sound=wx.Sound(self.clipwavfile) 0490 assert self.sound.IsOk() 0491 res=self.sound.Play(wx.SOUND_ASYNC) 0492 assert res 0493 self.starttime=time.time() 0494 self.endtime=self.starttime+self.playduration 0495 self.timer.Start(100, wx.TIMER_CONTINUOUS) 0496 0497 def OnTimer(self,_): 0498 now=time.time() 0499 if now>self.endtime: 0500 self.timer.Stop() 0501 # assert wx.Sound.IsPlaying()==False 0502 self.slider.SetCurrent(self.playend) 0503 self.UpdateCrop() 0504 return 0505 # work out where the slider should go 0506 newval=self.playstart+((now-self.starttime)/(self.endtime-self.starttime))*(self.playend-self.playstart) 0507 self.slider.SetCurrent(newval) 0508 self.UpdateCrop() 0509 0510 0511 def OnStop(self, _=None): 0512 self.timer.Stop() 0513 if self.sound is not None: 0514 if guihelper.IsMac(): 0515 # There is no stop method for Mac so we play a one centisecond wav 0516 # which cancels the existing playing sound 0517 self.sound=None 0518 sound=wx.Sound(self.zerolenwav) 0519 sound.Play(wx.SOUND_ASYNC) 0520 else: 0521 self.sound.Stop() 0522 self.sound=None 0523 0524 def OnSliderCurrentChanged(self, evt): 0525 self.OnStop() 0526 wx.CallAfter(self.UpdateCrop) 0527 0528 def OnSliderChanging(self, _): 0529 wx.CallAfter(self.UpdateCrop) 0530 0531 def _removetempfiles(self): 0532 for file in self.temporaryfiles: 0533 if os.path.exists(file): 0534 os.remove(file) 0535 0536 @guihelper.BusyWrapper 0537 def OnOk(self, evt): 0538 self.OnStop() 0539 # make new data 0540 start=self.slider.GetStart()*self.wfi.duration 0541 duration=(self.slider.GetEnd()-self.slider.GetStart())*self.wfi.duration 0542 self.newfiledata=getattr(self, self.PARAMETERS[self.convertinfo.format]['final'])( 0543 start, duration, self.clip_volume.GetValue()) 0544 # now remove files 0545 self._removetempfiles() 0546 # use normal handler to quit dialog 0547 evt.Skip() 0548 0549 def OnCancel(self, evt): 0550 self.OnStop() 0551 self._removetempfiles() 0552 evt.Skip() 0553 0554 def OnSetClip(self, _=None): 0555 s=self.clip_start.GetValue() 0556 d=self.clip_duration.GetValue() 0557 e=s+d 0558 if e<=self.wfi.duration: 0559 self.slider.SetStart(s/self.wfi.duration) 0560 self.slider.SetEnd(e/self.wfi.duration) 0561 self.UpdateCrop() 0562 0563 ### 0564 ### MP3 functions 0565 ### 0566 0567 def mp3setup(self): 0568 self.mp3file=common.gettempfilename("mp3") 0569 self.tmp_mp3file=common.gettempfilename('mp3') 0570 self.temporaryfiles.append(self.mp3file) 0571 self.temporaryfiles.append(self.tmp_mp3file) 0572 0573 def mp3convert(self): 0574 # make mp3 to work with 0575 open(self.mp3file, "wb").write(conversions.converttomp3(self.file, int(self.bitrate.GetStringSelection()), int(self.samplerate.GetStringSelection()), int(self.channels.GetStringSelection()))) 0576 self.afi=fileinfo.getmp3fileinfo(self.mp3file) 0577 print "result is",len(self.afi.frames),"frames" 0578 # and corresponding wav to play 0579 conversions.converttowav(self.mp3file, self.wavfile) 0580 0581 def mp3filelength(self, duration): 0582 # mp3 specific file length calculation 0583 frames=self.afi.frames 0584 self.beginframe=int(self.slider.GetStart()*len(frames)) 0585 self.endframe=int(self.slider.GetEnd()*len(frames)) 0586 length=sum([frames[frame].nextoffset-frames[frame].offset for frame in range(self.beginframe, self.endframe)]) 0587 return length 0588 0589 def _trim_mp3(self, start, duration): 0590 # mp3 writing out 0591 frames=self.afi.frames 0592 offset=frames[self.beginframe].offset 0593 length=frames[self.endframe-1].nextoffset-offset 0594 with file(self.mp3file, 'rb', 0) as f: 0595 f.seek(offset) 0596 return f.read(length) 0597 0598 def _trim_and_adjust_vol_mp3(self, start, duration, volume): 0599 # trim, adjust volume, and write mp3 out 0600 # use the original to make a new wav, not the one that went through foo -> mp3 -> wav 0601 conversions.converttowav(self.file, self.wavfile, 0602 start=start, duration=duration) 0603 # adjust the volume 0604 conversions.adjustwavfilevolume(self.wavfile, volume) 0605 # convert to mp3 0606 return conversions.converttomp3(self.wavfile, 0607 int(self.bitrate.GetStringSelection()), 0608 int(self.samplerate.GetStringSelection()), 0609 int(self.channels.GetStringSelection())) 0610 0611 def mp3final(self, start, duration, volume=None): 0612 if volume: 0613 # need to adjust volume 0614 return self._trim_and_adjust_vol_mp3(start, duration, volume) 0615 else: 0616 return self._trim_mp3(start, duration) 0617 0618 ### 0619 ### QCP/PureVoice functions 0620 ### 0621 0622 def qcpsetup(self): 0623 self.qcpfile=common.gettempfilename("qcp") 0624 self.temporaryfiles.append(self.qcpfile) 0625 0626 def qcpconvert(self): 0627 # we verify the pvconv binary exists first 0628 conversions.getpvconvbinary() 0629 # convert to wav first 0630 conversions.converttowav(self.file, self.wavfile, samplerate=8000, channels=1) 0631 # then to qcp 0632 conversions.convertwavtoqcp(self.wavfile, self.qcpfile, 0633 self.optimization.GetSelection()) 0634 # and finally the wav from the qcp so we can accurately hear what it sounds like 0635 conversions.convertqcptowav(self.qcpfile, self.wavfile) 0636 0637 def qcpfilelength(self, duration): 0638 # we don't actually know unless we do a conversion as QCP is 0639 # variable bitrate, so just assume the worst case of 13000 0640 # bits per second (1625 bytes per second) with an additional 0641 # 5% overhead due to the file format (I often see closer to 7%) 0642 return int(duration*1625*1.05) 0643 0644 def qcpfinal(self, start, duration, volume=None): 0645 # use the original to make a new wav, not the one that went through foo -> qcp -> wav 0646 conversions.converttowav(self.file, self.wavfile, samplerate=8000, channels=1, start=start, duration=duration) 0647 if volume is not None: 0648 conversions.adjustwavfilevolume(self.wavfile, volume) 0649 conversions.convertwavtoqcp(self.wavfile, self.qcpfile, 0650 self.optimization.GetSelection()) 0651 return file(self.qcpfile, "rb").read() 0652
Generated by PyXR 0.9.4