PyXR

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



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2003-2004 Stephen Wood <sawecw@users.sf.net>
0004 ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.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: conversions.py 4381 2007-08-29 00:19:51Z djpham $
0010 
0011 "Routines to do various file format conversions"
0012 from __future__ import with_statement
0013 import contextlib
0014 import os
0015 import tempfile
0016 import struct
0017 import subprocess
0018 import sys
0019 import wx
0020 
0021 
0022 import common
0023 
0024 class ConversionFailed(Exception): pass
0025 
0026 helperdir=os.path.join(common.get_main_dir(), "helpers")
0027 
0028 osext={'win32': '.exe',
0029        'darwin': '.mbin',
0030        'linux2': '.lbin'} \
0031        [sys.platform]
0032 
0033 # This shortname crap is needed because Windows programs (including ffmpeg)
0034 # don't correctly parse command line arguments.
0035 if sys.platform=='win32':
0036     import win32api
0037     def shortfilename(x):
0038         # the name may already be short (eg from tempfile which always returns short names)
0039         # and may not exist, so we are careful to only call GetShortPathName if necessary
0040         if " " in x:
0041             return win32api.GetShortPathName(x)
0042         return x
0043 else:
0044     def shortfilename(x): return x
0045 
0046 def gethelperbinary(basename):
0047     "Returns the full pathname to the specified helper binary"
0048     if basename=="pvconv":
0049         return getpvconvbinary()
0050     f=os.path.join(helperdir, basename)+osext
0051     try:
0052         f=shortfilename(f)
0053     except:
0054         # this craps out if the helper does not exist!
0055         raise common.HelperBinaryNotFound(basename, basename+osext, [helperdir])
0056     if not os.path.isfile(f):
0057         raise common.HelperBinaryNotFound(basename, basename+osext, [helperdir])
0058     return f
0059 
0060 
0061 _foundpvconv=None
0062 
0063 def getpvconvbinary():
0064     "PVConv can't be distributed with BitPim so the user has to install it and we have to find it"
0065     global _foundpvconv
0066     # check each time as user could delete or more binary
0067     if _foundpvconv is not None and os.path.isfile(_foundpvconv):
0068         return _foundpvconv
0069     _foundpvconv=None
0070     lookin=[]
0071     if sys.platform=='win32':
0072         binary="pvconv.exe"
0073         lookin.append("c:\\bin")
0074         from win32com.shell import shell, shellcon
0075         path=shell.SHGetFolderPath(0, shellcon.CSIDL_PROGRAM_FILES, None, 0)
0076         if path:
0077             lookin.append(os.path.join(path, "Qualcomm"))
0078             lookin.append(os.path.join(path, "pvconv"))
0079         path=shell.SHGetFolderPath(0, shellcon.CSIDL_WINDOWS, None, 0)
0080         if path:
0081             lookin.append(path)
0082     elif sys.platform=='linux2':
0083         binary="pvconv"
0084         lookin.append(_expand("~/bin"))
0085         lookin.append(_expand("~"))
0086         lookin.append(_expand("/usr/local/bin"))
0087     elif sys.platform=='darwin':
0088         binary="pvconv"
0089         lookin.append(_expand("~/bin"))
0090         lookin.append(_expand("~"))
0091         lookin.append(_expand("/usr/local/bin"))
0092         lookin.append(_expand("/usr/bin"))
0093     else:
0094         raise Exception("Unknown platform "+sys.platform)
0095     for dir in lookin:
0096         f=os.path.join(dir, binary)
0097         if os.path.exists(f):
0098             _foundpvconv=f
0099             return _foundpvconv
0100 
0101     raise common.HelperBinaryNotFound("pvconv", binary, lookin)
0102         
0103         
0104 
0105 def _expand(x):
0106     return os.path.expandvars(os.path.expanduser(x))
0107 
0108 def run(*args):
0109     """Runs the specified command (args[0]) with supplied parameters.
0110     Note that your path is not searched for the command."""
0111     print args
0112     p=subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
0113                        universal_newlines=True)
0114     _res=p.communicate()
0115     if p.returncode:
0116         # an error occurred, log it
0117         print _res[1]
0118         raise common.CommandExecutionFailed(p.returncode, args, _res[1])
0119 
0120 def convertto8bitpng(pngdata, maxsize):
0121     "Convert a PNG file to 8bit color map"
0122 
0123     # Return files small enough, or not PNG as is
0124     size=len(pngdata)
0125     if size<=maxsize or pngdata[1:4]!='PNG':
0126         return pngdata
0127 
0128     p=sys.path[0]
0129     if os.path.isfile(p):
0130         p=os.path.dirname(p)
0131     helpersdirectory=os.path.abspath(os.path.join(p, 'helpers'))
0132     print "Helper Directory: "+helpersdirectory
0133     if sys.platform=='win32':
0134         osext=".exe"
0135     if sys.platform=='darwin':
0136         osext=".mbin"
0137     if sys.platform=='linux2':
0138         osext=".lbin"
0139         
0140     pngtopnmbin=gethelperbinary('pngtopnm')
0141     ppmquantbin=gethelperbinary('ppmquant')
0142     pnmtopngbin=gethelperbinary('pnmtopng')
0143     print "pngtopnm: "+pngtopnmbin
0144     print "ppmquant: "+ppmquantbin
0145     print "pnmtopng: "+pnmtopngbin
0146 
0147     # Write original image to a temp file
0148     png=common.gettempfilename("png")
0149     open(png, "wb").write(pngdata)
0150 
0151     # Convert this image to pnm
0152     pnm=common.gettempfilename("pnm")
0153     s='"'+pngtopnmbin+'"' + ' < '+png+' > '+pnm
0154     os.system(s)
0155     #self.log(s)
0156     os.remove(png)
0157 
0158     # Binary search to find largest # of colors with a file size still
0159     # less than maxsize
0160 
0161     ncolormax=257
0162     ncolormin=1
0163     ncolortry=256
0164     ncolor=ncolortry
0165     pnmq=common.gettempfilename("pnm")
0166 
0167     while size>maxsize or ncolormax-ncolor>1:
0168         ncolor=ncolortry
0169         s='"'+ppmquantbin+'"'+' '+`ncolortry`+' '+pnm+ ' > '+pnmq
0170         #self.log(s)
0171         os.system(s)
0172         s ='"'+pnmtopngbin+'"' + ' < ' + pnmq + ' > '+png
0173         #self.log(s)
0174         os.system(s)
0175         os.remove(pnmq)
0176         pngquantdata=open(png,"rb").read()
0177         os.remove(png)
0178         size=len(pngquantdata)
0179         print `ncolor`+' '+`size`
0180         if size>maxsize:
0181             ncolormax=ncolor
0182             ncolortry=(ncolor+ncolormin)/2
0183         else:
0184             ncolormin=ncolor
0185             ncolortry=(ncolor+ncolormax)/2
0186 
0187     os.remove(pnm)
0188     return pngquantdata
0189 
0190 def convertto8bitpng_joe(pngdata):
0191     "Convert a PNG file to 8bit color map"
0192     "Separate routine for now so not to screw up existing one, may merge later"
0193     if pngdata[1:4]!='PNG':
0194         return pngdata
0195     # get the path to helper
0196         
0197     pngtopnmbin=gethelperbinary('pngtopnm')
0198     ppmquantbin=gethelperbinary('ppmquant')
0199     pnmtopngbin=gethelperbinary('pnmtopng')
0200     print "pngtopnm: "+pngtopnmbin
0201     print "ppmquant: "+ppmquantbin
0202     print "pnmtopng: "+pnmtopngbin
0203     # Write original image to a temp file
0204     png=common.gettempfilename("png")
0205     open(png, "wb").write(pngdata)
0206     num_of_colors=wx.Image(png).ComputeHistogram(wx.ImageHistogram())
0207     print 'number of colors:', num_of_colors
0208     if num_of_colors>256:
0209         # no optimization possible, just return
0210         os.remove(png)
0211         return pngdata
0212     # else optimize it
0213     # Convert this image to pnm
0214     pnm=common.gettempfilename("pnm")
0215     s='"'+pngtopnmbin+'"' + ' < '+png+' > '+pnm
0216     os.system(s)
0217     os.remove(png)
0218     # quantize & convert
0219     pnmq=common.gettempfilename("pnm")
0220     s='"'+ppmquantbin+'"'+' '+`num_of_colors`+' '+pnm+ ' > '+pnmq
0221     os.system(s)
0222     s ='"'+pnmtopngbin+'"' + ' < ' + pnmq + ' > '+png
0223     os.system(s)
0224     os.remove(pnmq)
0225     pngquantdata=open(png, 'rb').read()
0226     os.remove(png)
0227     os.remove(pnm)
0228     print 'old size: ',len(pngdata),', new size: ',len(pngquantdata)
0229     return pngquantdata
0230 
0231 
0232 def converttomp3(inputfilename, bitrate, samplerate, channels):
0233     """Reads inputfilename and returns data for an mp3 conversion
0234 
0235     @param bitrate: bitrate to use in khz (ie 16 is 16000 bits per second)
0236     @param samplerate: audio sampling rate in Hertz
0237     @param channels: 1 is mono, 2 is stereo
0238     """
0239     ffmpeg=gethelperbinary("ffmpeg")
0240     with common.usetempfile('mp3') as mp3file:
0241         try:
0242             run(ffmpeg, "-i", shortfilename(inputfilename), "-hq", "-ab", `bitrate`, "-ar", `samplerate`, "-ac", `channels`, shortfilename(mp3file))
0243         except common.CommandExecutionFailed, e:
0244             # we get this exception on bad parameters, or any other
0245             # issue so we assume bad parameters for the moment.
0246             raise ConversionFailed, ' '.join(e.args)+'\n'+e.logstr
0247         return file(mp3file, "rb").read()
0248 
0249 def converttowav(mp3filename, wavfilename, samplerate=None,
0250                     channels=None, start=None, duration=None):
0251     ffmpeg=gethelperbinary("ffmpeg")
0252     cmd=(ffmpeg, "-i", shortfilename(mp3filename))
0253     if samplerate is not None:
0254         cmd+=('-ar', str(samplerate))
0255     if channels is not None:
0256         cmd+=('-ac', str(channels))
0257     if start is not None:
0258         cmd+=('-ss', str(start))
0259     if duration is not None:
0260         cmd+=('-t', str(duration))
0261     cmd+=(shortfilename(wavfilename),)
0262     # ffmpeg queries about overwrite - grrr
0263     try: os.remove(cmd[-1])
0264     except OSError: pass
0265 
0266     run(*cmd)
0267 
0268 _qcp_optimization_params=('ffr', 'vfr', 'fhr', 'vhr')
0269 def convertwavtoqcp(wavfile, qcpfile, optimization=None):
0270     pvconv=shortfilename(gethelperbinary('pvconv'))
0271     w_name=shortfilename(wavfile)
0272     q_name=common.stripext(w_name)+'.qcp'
0273     try:
0274         os.remove(q_name)
0275     except:
0276         pass
0277     # Have not figured out how to specify output file for pvconv
0278     if optimization is None:
0279         run(pvconv, w_name)
0280     else:
0281         run(pvconv, '-r', _qcp_optimization_params[optimization], w_name)
0282     # mv output file to qcpfile
0283     try:
0284         os.remove(qcpfile)
0285     except:
0286         pass
0287     os.rename(q_name, qcpfile)
0288 
0289 def convertqcptowav(qcpfile, wavfile):
0290     pvconv=shortfilename(gethelperbinary('pvconv'))
0291     q_name=shortfilename(qcpfile)
0292     w_name=common.stripext(q_name)+'.wav'
0293     try:
0294         os.remove(w_name)
0295     except:
0296         pass
0297     run(pvconv, q_name)
0298     try:
0299         os.remove(wavfile)
0300     except:
0301         pass
0302     os.rename(w_name, wavfile)
0303 
0304 def adjustwavfilevolume(wavfilename, gain):
0305     """ Ajdust the volume of a wav file.
0306     """
0307     with file(wavfilename, 'rb') as f:
0308         # read in the headers
0309         headers=f.read(20)
0310         subchunk1size=common.LSBUint32(headers[16:20])
0311         headers+=f.read(subchunk1size)
0312         headers+=f.read(8)  # 4 byte ID and 4 byte length
0313         subchunk2size=common.LSBUint32(headers[-4:])
0314         bitspersample=common.LSBUint16(headers[34:36])
0315         if bitspersample!=16:
0316             print 'Volume adjustment only works with 16-bit wav file',bitspersample
0317             return
0318         sample_num=subchunk2size/2  # always 16-bit per channel per sample
0319         temp_name=common.gettempfilename("wav")
0320         with file(temp_name, 'wb') as f_temp:
0321             f_temp.write(headers)
0322             delta=pow(10.0, (gain/10.0))
0323             for i in range(sample_num):
0324                 d=int(struct.unpack('<h', f.read(2))[0]*delta)
0325                 if d>32767:
0326                     d=32767
0327                 elif d<-32768:
0328                     d=-32768
0329                 f_temp.write(struct.pack('<h', d))
0330     os.remove(wavfilename)
0331     os.rename(temp_name, wavfilename)
0332 
0333 def trimwavfile(wavfilename, wavoutfilename, start, duration=None, gain=None):
0334     with file(wavfilename, 'rb') as f:
0335         # read in the headers
0336         headers=f.read(20)
0337         subchunk1size=common.LSBUint32(headers[16:20])
0338         headers+=f.read(subchunk1size)
0339         subchunk2id=f.read(4)
0340         subchunk2size=common.LSBUint32(f.read(4))
0341         # check for a PCM file format
0342         if headers[:4]!='RIFF' or headers[8:12]!='WAVE' or \
0343         headers[12:16]!='fmt ' or common.LSBUint16(headers[20:22])!=1:
0344             # not a PCM file
0345             raise TypeError
0346         subchunk2start=20+subchunk1size
0347         subchunk2datastart=subchunk2start+8
0348         samplerate=common.LSBUint32(headers[24:28])
0349         blockalign=common.LSBUint16(headers[32:34])
0350         # compute new start & duration
0351         new_start=int(start*samplerate)*blockalign
0352         new_size=subchunk2size-new_start
0353         if duration is not None:
0354             i=int(duration*samplerate)*blockalign
0355             if i<new_size:
0356                 new_size=i
0357         # go get it
0358         f.seek(new_start, 1)
0359         open(wavoutfilename, 'wb').write("".join(['RIFF',
0360                                                   common.LSBstr32(4+8+subchunk1size+8+new_size),
0361                                                   headers[8:],
0362                                                   'data',
0363                                                   common.LSBstr32(new_size),
0364                                                   f.read(new_size)]))
0365         if gain is not None:
0366             adjustwavfilevolume(wavoutfilename, gain)
0367 
0368 def trimwavdata(wavedatain, start, duration=None):
0369     # check for a PCM file format
0370     if wavedatain[:4]!='RIFF' or wavedatain[8:12]!='WAVE' or \
0371        wavedatain[12:16]!='fmt ' or common.LSBUint16(wavedatain[20:22])!=1:
0372         raise ValueError, 'not a PCM file'
0373     subchunk1size=common.LSBUint32(wavedatain[16:20])
0374     subchunk2start=20+subchunk1size
0375     subchunk2size=common.LSBUint32(wavedatain[subchunk2start+4:subchunk2start+8])
0376     subchunk2datastart=subchunk2start+8
0377     samplerate=common.LSBUint32(wavedatain[24:28])
0378     blockalign=common.LSBUint16(wavedatain[32:34])
0379     # compute new start & duration
0380     new_start=int(start*samplerate)*blockalign
0381     newsubchunk2datastart=subchunk2datastart+new_start
0382     new_size=subchunk2size-new_start
0383     if duration is not None:
0384         i=int(duration*samplerate)*blockalign
0385         if i<new_size:
0386             new_size=i
0387     # return new data
0388     return 'RIFF'+common.LSBstr32(4+8+subchunk1size+8+new_size)+\
0389            wavedatain[8:subchunk2start]+\
0390            'data'+common.LSBstr32(new_size)+\
0391            wavedatain[newsubchunk2datastart:newsubchunk2datastart+new_size]
0392 
0393 def convertjpgtoavi(jpg_data, avi_file_name, fps=4, new_file=False):
0394     bmp2avi=shortfilename(gethelperbinary('bmp2avi'))
0395     if new_file:
0396         # delete any existing file and start fresh
0397         try:
0398             os.remove(avi_file_name)
0399         except:
0400             pass
0401     # convert the jpg data to bmp data
0402     with contextlib.nested(common.usetempfile('jpg'),
0403                            common.usetempfile('bmp')) as (_jpg, _bmp):
0404         jpg_name=shortfilename(_jpg)
0405         bmp_name=shortfilename(_bmp)
0406         file(jpg_name, "wb").write(jpg_data)
0407         wx.Image(jpg_name).SaveFile(bmp_name, wx.BITMAP_TYPE_BMP)
0408         # add the bmp frame to the avi file
0409         run(bmp2avi, '-f', `fps`, '-i', bmp_name, '-o', avi_file_name)
0410 
0411 def convertavitobmp(avi_data, frame_num=0):
0412     with common.usetempfile('avi') as _avi:
0413         avi_file=shortfilename(_avi)
0414         file(avi_file, 'wb').write(avi_data)
0415         return convertfileavitobmp(avi_file, frame_num)
0416 
0417 def convertfileavitobmp(avi_file_name, frame_num=0):
0418     bmp2avi=shortfilename(gethelperbinary('bmp2avi'))
0419     with common.usetempfile('bmp') as _bmp:
0420         bmp_file_name=shortfilename(_bmp)
0421         run(bmp2avi, '-t', `frame_num`, '-i', shortfilename(avi_file_name),
0422             '-o', bmp_file_name)
0423         return wx.Image(bmp_file_name)
0424 
0425 def convertfilelgbittobmp(bit_file_name):
0426     "File-based wrapper for convertlgbittobmp."
0427     with common.usetempfile('png') as bmp:
0428         bmpdata=convertlgbittobmp(file(bit_file_name,"rb").read())
0429         file(bmp, "wb").write(bmpdata)
0430         return wx.Image(bmp)
0431     
0432 def convertlgbittobmp(bit_data):
0433     """Takes a BIT image file (LG proprietary) and returns BMP
0434 
0435     @param bit_data: 16BPP BIT image file data
0436     @return: 24BPP BMP image file data
0437     """
0438     width=common.LSBUint16(bit_data[0:2])
0439     height=common.LSBUint16(bit_data[2:4])
0440     img='BM'
0441     img+=common.LSBstr32(width*height*3+54)  # file size
0442     img+=common.LSBstr16(0)                  # unused
0443     img+=common.LSBstr16(0)                  # unused
0444     img+=common.LSBstr32(54)                 # offset to pixel data (from byte 0)
0445     img+=common.LSBstr32(40)                 # info section size
0446     img+=common.LSBstr32(width)              # image width
0447     img+=common.LSBstr32(height)             # image height
0448     img+=common.LSBstr16(1)                  # image planes
0449     img+=common.LSBstr16(24)                 # bits-per-pixel
0450     img+=common.LSBstr32(0)                  # compression type (0=uncompressed)
0451     img+=common.LSBstr32(0)                  # image size (may be 0 for uncompressed images)
0452     img+=common.LSBstr32(0)                  # (ignored)
0453     img+=common.LSBstr32(0)                  # (ignored)
0454     img+=common.LSBstr32(0)                  # (ignored)
0455     img+=common.LSBstr32(0)                  # (ignored)
0456     # Now on to the char data
0457     for h in range(height):
0458         for w in range(width):
0459             # images can be zero len on phone
0460             if len(bit_data)==0:
0461                 bitdata = 0xffff
0462             else:
0463                 bitind=(height-h-1)*width*2+(w*2)+4
0464                 bitdata=common.LSBUint16(bit_data[bitind:bitind+2])
0465             red=(bitdata & 0xf800) >> 8
0466             green=(bitdata & 0x07e0) >> 3
0467             blue=(bitdata & 0x001f) << 3
0468             if (red & 0x8) != 0:
0469                 red=red | 0x7
0470             if (green & 0x4) != 0:
0471                 green=green | 0x3
0472             if (blue & 0x8) != 0:
0473                 blue=blue | 0x7
0474             img+=chr(blue)
0475             img+=chr(green)
0476             img+=chr(red)
0477     return img
0478 
0479 def convertbmptolgbit(bmp_data):
0480     """Takes a BMP image file and returns BIT image file (LG proprietary)
0481 
0482     @param bit_data: 8BPP or 24BPP BMP image file data
0483     @return: 16BPP LGBIT image file data
0484     """
0485     # This function only exists for the LG proprietary images (wallpaper, etc.)
0486     # on the LG-VX3200. 
0487     if bmp_data[0:2]!='BM':
0488         return None
0489     width=common.LSBUint32(bmp_data[18:22])
0490     height=common.LSBUint32(bmp_data[22:26])
0491     offset=common.LSBUint32(bmp_data[10:14])
0492     bpp=common.LSBUint16(bmp_data[28:30])
0493     img=common.LSBstr16(width)
0494     img+=common.LSBstr16(height)
0495     # Now on to the char data
0496     if bpp==8:
0497         # 8BPP (paletted) BMP data
0498         palette=bmp_data[54:54+256*4]
0499         for h in range(height):
0500             for w in range(width):
0501                 bitind=(height-h-1)*width+w+offset
0502                 palind=ord(bmp_data[bitind:bitind+1])*4
0503                 blue=ord(palette[palind:palind+1])
0504                 green=ord(palette[palind+1:palind+2])
0505                 red=ord(palette[palind+2:palind+3])
0506                 bitval=((red & 0xf8) << 8) | ((green & 0xfc) << 3) | ((blue & 0xf8) >> 3)
0507                 img+=common.LSBstr16(bitval)
0508     elif bpp==24:
0509         # 24BPP (non-paletted) BMP data
0510         for h in range(height):
0511             for w in range(width):
0512                 bitind=(height-h-1)*width*3+(w*3)+offset
0513                 blue=ord(bmp_data[bitind:bitind+1])
0514                 green=ord(bmp_data[bitind+1:bitind+2])
0515                 red=ord(bmp_data[bitind+2:bitind+3])
0516                 bitval=((red & 0xf8) << 8) | ((green & 0xfc) << 3) | ((blue & 0xf8) >> 3)
0517                 img+=common.LSBstr16(bitval)
0518     else:
0519         return None
0520     return img
0521 
0522 def helperavailable(helper_name):
0523     try:
0524         f=gethelperbinary(helper_name)
0525         return True
0526     except common.HelperBinaryNotFound:
0527         return False
0528     except:
0529         if __debug__: raise
0530         return False
0531 

Generated by PyXR 0.9.4