PyXR

c:\projects\bitpim\src \ ringers.py



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