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