Example #1
0
	def SetCameraState( state = False ):
		global camera, font
		camera = None
		font = None
		if state:
			Utils.FixPILSearchPath()
			try:
				camera = Device()
			except Exception as e:
				logException( e, sys.exc_info() )
				camera = None
		return camera
Example #2
0
	def AddBibToPhoto( raceFileName, bib, raceSeconds ):
		dirName = getPhotoDirName( raceFileName )
		
		fnameOld = GetPhotoFName( None, raceSeconds )
		fnameNew = GetPhotoFName( bib, raceSeconds )
		
		fileNameOld = os.path.join( dirName, fnameOld )
		fileNameNew = os.path.join( dirName, fnameNew )
		try:
			os.rename( fileNameOld, fileNameNew )
		except Exception as e:
			logException( e, sys.exc_info() )
Example #3
0
	def recordVideoFrame( self ):
		tNow = now()
		self.sampleCount += 1
		if not self.camera:
			return
			
		try:
			image = self.camera.getImage()
			self.fcb.append( tNow, image )
			if self.owner is not None:
				wx.PostEvent( self.owner, PhotoEvent(t=tNew, photo=image) )
		except Exception as e:
			logException( e, sys.exc_info() )
Example #4
0
	def recordVideoFrame( self ):
		tNow = now()
		self.sampleCount += 1
		if not self.camera:
			return
			
		try:
			image = self.camera.getImage()
			self.fcb.append( tNow, image )
			if self.owner is not None:
				wx.PostEvent( self.owner, PhotoEvent(t=tNow, photo=image) )
		except Exception as e:
			logException( e, sys.exc_info() )
Example #5
0
	def doExport( self, event=None ):
		race = Model.race
		if not race:
			return
		
		fileName = Utils.getMainWin().fileName if Utils.getMainWin() else 'Test.cmn'
		
		#---------------------------------------------------------------------------------
		# Create an Excel file.
		#
		xlFileName = os.path.splitext(fileName)[0] + '-TeamResults.xlsx'

		try:
			wb = xlsxwriter.Workbook( xlFileName )
			formats = ExportGrid.ExportGrid.getExcelFormatsXLSX( wb )
			
			ues = Utils.UniqueExcelSheetName()
			for category in race.getCategories( publishOnly=True ):			
				eg = self.toExportGrid( category )
				if eg:
					ws = wb.add_worksheet( ues.getSheetName(category.fullname) )
					eg.toExcelSheetXLSX( formats, ws )
			wb.close()
		except Exception as e:
			logException( e, sys.exc_info() )
		del wb
		
		#---------------------------------------------------------------------------------
		# Create a PDF file.
		#
		pdfFileName = os.path.splitext(fileName)[0] + '-TeamResults.pdf'
		
		try:
			pdf = PDF( orientation = 'P' )
			pdf.set_font( 'Arial', '', 12 )
			pdf.set_author( getpass.getuser() )
			pdf.set_keywords( 'CrossMgr Team Results' )
			pdf.set_creator( Version.AppVerName )
			pdf.set_title( os.path.splitext(pdfFileName)[0].replace('-', ' ') )
			for category in race.getCategories( publishOnly=True ):
				eg = self.toExportGrid( category )
				if eg:
					eg.drawToFitPDF( pdf, orientation=wx.PORTRAIT )
			pdf.output( pdfFileName, 'F' )
		except Exception as e:
			logException( e, sys.exc_info() )
		del pdf
Example #6
0
def StartVideoBuffer( refTime, raceFileName ):
	global videoBuffer
	
	if not videoBuffer:
		camera = PhotoFinish.SetCameraState( True )
		if not camera:
			return False
			
		dirName = PhotoFinish.getPhotoDirName( raceFileName )
		if not os.path.isdir( dirName ):
			try:
				os.makedirs( dirName )
			except Exception as e:
				logException( e, sys.exc_info() )
				return False
				
		videoBuffer = VideoBuffer( camera, refTime, dirName )
		videoBuffer.start()
	return True
Example #7
0
def StartVideoBuffer( refTime, raceFileName ):
	global videoBuffer
	
	if not videoBuffer:
		camera = PhotoFinish.SetCameraState( True )
		if not camera:
			return False
			
		dirName = PhotoFinish.getPhotoDirName( raceFileName )
		if not os.path.isdir( dirName ):
			try:
				os.makedirs( dirName )
			except Exception as e:
				logException( e, sys.exc_info() )
				return False
				
		videoBuffer = VideoBuffer( camera, refTime, dirName )
		videoBuffer.start()
	return True
Example #8
0
	def run( self ):
		self.reset()
		keepGoing = True
		while keepGoing:
			#sys.stderr.write( '.' )
			tNow = now()
			try:
				self.fcb.append( tNow, self.camera.getImage() )
			except Exception as e:
				logException( e, sys.exc_info() )
				break
			
			while 1:
				try:
					message = self.queue.get( False )
				except Empty:
					break
				
				if   message[0] == 'Save':
					cmd, bib, t = message
					tFind = self.refTime + timedelta( seconds = t + getattr(Model.race, 'advancePhotoMilliseconds', Model.Race.advancePhotoMillisecondsDefault) / 1000.0 )
					if tFind > tNow:
						threading.Timer( (tFind - tNow).total_seconds() + 0.1, self.takePhoto, args=[bib, t] ).start()
						continue
						
					times, frames = self.fcb.findBeforeAfter( tFind, 0, 1 )
					for i, frame in enumerate( frames ):
						t = (times[i]-self.refTime).total_seconds()
						self.frameSaver.save( GetFilename(bib, t, self.dirName, i), bib, t, frame )
					self.frameCount += len(frames)
					self.queue.task_done()
					
				elif message[0] == 'Terminate':
					self.queue.task_done()
					self.reset()
					keepGoing = False
					break
				
			# Sleep until we need to grab the next frame.
			frameWait = self.frameDelay - (now() - tNow).total_seconds()
			if frameWait < 0.0:
				frameWait = self.frameDelay		# Give some more time if we are falling behind.
			time.sleep( frameWait )
Example #9
0
	def logNum( self, nums ):
		if nums is None:
			return
		if not isinstance(nums, (list, tuple)):
			nums = [nums]
			
		with Model.LockRace() as race:
			if race is None or not race.isRunning():
				return
				
			t = race.curRaceTime()
			
			# Take the picture first to reduce latency to capturing the riders as they cross the line.
			if getattr(race, 'enableUSBCamera', False):
				for num in nums:
					try:
						num = int(num)
					except:
						continue
					try:
						race.photoCount = getattr(race,'photoCount',0) + VideoBuffer.ModelTakePhoto( num, t )
					except Exception as e:
						logException( e, sys.exc_info() )
			
			# Add the times to the model and write to the log.
			for num in nums:
				try:
					num = int(num)
				except:
					continue
				race.addTime( num, t )
				OutputStreamer.writeNumTime( num, t )
				
		self.playBlip()
		
		mainWin = Utils.getMainWin()
		if mainWin:
			mainWin.record.keypad.numEdit.SetValue( '' )
			mainWin.record.refreshLaps()
			wx.CallAfter( mainWin.refresh )
		if getattr(race, 'ftpUploadDuringRace', False):
			realTimeFtpPublish.publishEntry()
Example #10
0
def DeletePhotos( raceFileName ):
	dirName = getPhotoDirName( raceFileName )
	try:
		shutil.rmtree( dirName, True )
	except Exception as e:
		logException( e, sys.exc_info() )
Example #11
0
    def onPageChanging(self, evt):
        isForward = evt.GetDirection()
        if not isForward:
            return
        page = evt.GetPage()

        if page == self.introPage:
            pass
        elif page == self.fileNamePage:
            fileName = self.fileNamePage.getFileName()

            # Check for a valid file.
            try:
                open(fileName).close()
            except IOError:
                if fileName == '':
                    message = _('Please specify a GPX file.')
                else:
                    message = u'{}:\n\n    "{}"\n\n{}'.format(
                        _('Cannot open file'),
                        fileName,
                        _('Please check the file name and/or its read permissions.'
                          ),
                    )
                Utils.MessageOK(self.wizard,
                                message,
                                title=_('File Open Error'),
                                iconMask=wx.ICON_ERROR)
                evt.Veto()
                return

            # Check for valid content.
            geoTrack = GeoTrack()
            try:
                geoTrack.read(
                    fileName,
                    isPointToPoint=self.fileNamePage.getIsPointToPoint())
            except Exception as e:
                logException(e, sys.exc_info())
                Utils.MessageOK(self.wizard,
                                '{}:  {}\n({})'.format(
                                    _('Read Error'),
                                    _('Is this GPX file properly formatted?'),
                                    e),
                                title='Read Error',
                                iconMask=wx.ICON_ERROR)
                evt.Veto()
                return

            # Check for too few points.
            if geoTrack.numPoints < 2:
                Utils.MessageOK(
                    self.wizard,
                    u'{}: {}'.format(
                        _('Import Failed'),
                        _('GPX file contains fewer than two points.')),
                    title=_('File Format Error'),
                    iconMask=wx.ICON_ERROR)
                evt.Veto()
                return

            if self.fileNamePage.getUseElevation():
                fileNameElevation = os.path.join(os.path.dirname(fileName),
                                                 'elevation.csv')
                try:
                    open(fileNameElevation).close()
                except IOError as e:
                    logException(e, sys.exc_info())
                    message = u'{}: {}\n\n    "{}"\n\n{}'.format(
                        _('Cannot Open Elevation File'),
                        e,
                        fileNameElevation,
                        _('Please check the file name and/or its read permissions.'
                          ),
                    )
                    Utils.MessageOK(self.wizard,
                                    message,
                                    title=_('File Open Error'),
                                    iconMask=wx.ICON_ERROR)
                else:
                    try:
                        geoTrack.readElevation(fileNameElevation)
                    except Exception as e:
                        logException(e, sys.exc_info())
                        message = u'{}: {}\n\n    "{}"'.format(
                            _('Elevation File Error'), e, fileNameElevation)
                        Utils.MessageOK(self.wizard,
                                        message,
                                        title=_('File Read Error'),
                                        iconMask=wx.ICON_ERROR)

            self.geoTrackFName = fileName
            self.geoTrack = geoTrack
            self.summaryPage.setInfo(
                self.geoTrackFName, self.geoTrack.numPoints,
                self.geoTrack.lengthKm, self.geoTrack.totalElevationGainM,
                getattr(self.geoTrack, 'isPointToPoint', False))
            self.useTimesPage.setInfo(self.geoTrackFName)

        elif page == self.useTimesPage:
            if self.useTimesPage.getUseTimes():
                self.geoTrack.read(self.geoTrackFName, True)
Example #12
0
	def onPageChanging( self, evt ):
		isForward = evt.GetDirection()
		if not isForward:
			return
		page = evt.GetPage()
		
		if page == self.introPage:
			pass
		elif page == self.fileNamePage:
			fileName = self.fileNamePage.getFileName()
			
			# Check for a valid file.
			try:
				open(fileName).close()
			except IOError:
				if fileName == '':
					message = _('Please specify a GPX file.')
				else:
					message = u'{}:\n\n    "{}"\n\n{}'.format(
							_('Cannot open file'), fileName,
							_('Please check the file name and/or its read permissions.'),
						)
				Utils.MessageOK( self.wizard, message, title=_('File Open Error'), iconMask=wx.ICON_ERROR)
				evt.Veto()
				return
			
			# Check for valid content.
			geoTrack = GeoTrack()
			try:
				geoTrack.read( fileName, isPointToPoint = self.fileNamePage.getIsPointToPoint() )
			except Exception as e:
				logException( e, sys.exc_info() )
				Utils.MessageOK( self.wizard, '{}:  {}\n({})'.format(_('Read Error'), _('Is this GPX file properly formatted?'), e),
								title='Read Error', iconMask=wx.ICON_ERROR)
				evt.Veto()
				return
			
			# Check for too few points.
			if geoTrack.numPoints < 2:
				Utils.MessageOK( self.wizard,
					u'{}: {}'.format(_('Import Failed'), _('GPX file contains fewer than two points.')),
					title=_('File Format Error'),
					iconMask=wx.ICON_ERROR)
				evt.Veto()
				return
			
			if self.fileNamePage.getUseElevation():
				fileNameElevation = os.path.join( os.path.dirname(fileName), 'elevation.csv' )
				try:
					open(fileNameElevation).close()
				except IOError as e:
					logException( e, sys.exc_info() )
					message = u'{}: {}\n\n    "{}"\n\n{}'.format(
							_('Cannot Open Elevation File'), e, fileNameElevation,
							_('Please check the file name and/or its read permissions.'),
						)
					Utils.MessageOK( self.wizard, message, title=_('File Open Error'), iconMask=wx.ICON_ERROR)
				else:
					try:
						geoTrack.readElevation( fileNameElevation )
					except Exception as e:
						logException( e, sys.exc_info() )
						message = u'{}: {}\n\n    "{}"'.format(_('Elevation File Error'), e, fileNameElevation )
						Utils.MessageOK( self.wizard, message, title=_('File Read Error'), iconMask=wx.ICON_ERROR )
				
			self.geoTrackFName = fileName
			self.geoTrack = geoTrack
			self.summaryPage.setInfo(	self.geoTrackFName, self.geoTrack.numPoints,
										self.geoTrack.lengthKm, self.geoTrack.totalElevationGainM,
										getattr( self.geoTrack, 'isPointToPoint', False) )
			self.useTimesPage.setInfo( self.geoTrackFName )
			
		elif page == self.useTimesPage:
			if self.useTimesPage.getUseTimes():
				self.geoTrack.read( self.geoTrackFName, True )
		elif page == self.summaryPage:
			pass
		elif page == self.fixCategoryDistance:
			pass
Example #13
0
def DeletePhotos(raceFileName):
    dirName = getPhotoDirName(raceFileName)
    try:
        shutil.rmtree(dirName, True)
    except Exception as e:
        logException(e, sys.exc_info())
Example #14
0
def Server(q, shutdownQ, HOST, PORT, startTime):
    global readerEventWindow

    if not readerEventWindow:
        readerEventWindow = Utils.mainWin

    timeoutSecs = 5
    delaySecs = 3

    readerTime = None
    readerComputerTimeDiff = None

    s = None
    passingsCur = 0
    status = None
    startOperation = None

    def qLog(category, message):
        q.put((category, message))
        Utils.writeLog(u'RaceResult: {}: {}'.format(category, message))

    def keepGoing():
        try:
            shutdownQ.get_nowait()
        except Empty:
            return True
        return False

    def autoDetectCallback(m):
        qLog('autodetect', u'{} {}'.format(_('Checking'), m))
        return keepGoing()

    def makeCall(s, message):
        cmd = message.split(';', 1)[0]
        qLog('command', u'sending: {}'.format(message))
        try:
            socketSend(s, u'{}{}'.format(message, EOL))
            buffer = socketReadDelimited(s)
        except Exception as e:
            qLog('connection', u'{}: {}: "{}"'.format(cmd,
                                                      _('Connection failed'),
                                                      e))
            raise ValueError

        if not buffer.startswith(u'{};'.format(cmd)):
            qLog('command', u'{}: {} "{}"'.format(cmd, _('Unexpected return'),
                                                  buffer))
            raise ValueError

        return buffer

    while keepGoing():
        if s:
            try:
                s.shutdown(socket.SHUT_RDWR)
                s.close()
            except Exception as e:
                pass
            time.sleep(delaySecs)

        #-----------------------------------------------------------------------------------------------------
        qLog(
            'connection', u'{} {}:{}'.format(
                _('Attempting to connect to RaceResult reader at'), HOST,
                PORT))
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(timeoutSecs)
            s.connect((HOST, PORT))
        except Exception as e:
            qLog(
                'connection',
                u'{}: {}'.format(_('Connection to RaceResult reader failed'),
                                 e))
            s, status, startOperation = None, None, None

            qLog('connection', u'{}'.format(_('Attempting AutoDetect...')))
            HOST_AUTO = AutoDetect(callback=autoDetectCallback)
            if HOST_AUTO:
                qLog(
                    'connection',
                    u'{}: {}'.format(_('AutoDetect RaceResult at'), HOST_AUTO))
                HOST = HOST_AUTO
            else:
                time.sleep(delaySecs)
            continue

        #-----------------------------------------------------------------------------------------------------
        crossMgrMinProtocol = (1, 2)
        crossMgrMinProtocolStr = '.'.join('{}'.format(p)
                                          for p in crossMgrMinProtocol)
        try:
            buffer = makeCall(s, 'GETPROTOCOL')
        except ValueError as e:
            logException(e, sys.exc_info())
            continue

        current, minSupported, maxSupported = [
            f.strip() for f in buffer.strip().split(';')[1:]
        ]

        if tuple(int(i)
                 for i in maxSupported.split('.')) < crossMgrMinProtocol:
            qLog(
                'connection', u'{}. {} >={}. {} {}.'.format(
                    _("RaceResult requires Firmware Upgrade"),
                    _("CrossMgr requires PROTOCOL"),
                    crossMgrMinProtocolStr,
                    _("RaceResult supports"),
                    maxSupported,
                ))
            time.sleep(delaySecs)
            continue

        try:
            buffer = makeCall(s, 'SETPROTOCOL;{}'.format(maxSupported))
        except ValueError:
            continue

        qLog('status', u'{}'.format(buffer.strip()))

        try:
            buffer = makeCall(s, 'GETSTATUS')
        except ValueError:
            continue

        fields = [f.strip() for f in buffer.strip().split(';')]
        status = zip(statusFields, fields[1:])
        for name, value in status:
            qLog('status', u'{}: {}'.format(name, value))

        #-----------------------------------------------------------------------------------------------------
        try:
            buffer = makeCall(s, 'STOPOPERATION')
        except ValueError:
            continue

        #-----------------------------------------------------------------------------------------------------
        try:
            buffer = makeCall(
                s, 'SETTIME;{}'.format(datetime.datetime.now().strftime(
                    '%Y-%m-%d;%H:%M:%S.%f')[:-3]))
        except ValueError:
            continue

        #-----------------------------------------------------------------------------------------------------
        try:
            buffer = makeCall(s, 'STARTOPERATION')
        except ValueError:
            continue
        qLog('status', u'{}'.format(buffer.strip()))

        #-----------------------------------------------------------------------------------------------------
        try:
            buffer = makeCall(s, 'GETTIME')
        except ValueError:
            continue

        try:
            dt = reNonDigit.sub(' ', buffer).strip()
            fields[-1] = (
                fields[-1] +
                '000000')[:6]  # Pad with zeros to convert to microseconds.
            readerTime = datetime.datetime(*[int(f) for f in dt.split()])
            readerComputerTimeDiff = datetime.datetime.now() - readerTime
        except Exception as e:
            qLog(
                'command',
                u'GETTIME: {} "{}" "{}"'.format(_('Unexpected return'), buffer,
                                                e))
            continue

        while keepGoing():
            #-------------------------------------------------------------------------------------------------
            cmd = 'PASSINGS'
            try:
                socketSend(s, u'{}{}'.format(cmd, EOL))
                buffer = socketReadDelimited(s)
                if buffer.startswith('{};'.format(cmd)):
                    fields = buffer.split(';')
                    try:
                        passingsText = fields[1]
                    except Exception as e:
                        qLog(
                            'command',
                            u'{}: {} "{}" "{}"'.format(cmd,
                                                       _('Unexpected return'),
                                                       buffer, e))
                        continue

                    try:
                        passingsNew = int(
                            reNonDigit.sub(' ', passingsText).strip())
                    except Exception as e:
                        qLog(
                            'command',
                            u'{}: {} "{}" "{}"'.format(cmd,
                                                       _('Unexpected return'),
                                                       buffer, e))
                        continue

                else:
                    qLog(
                        'command',
                        u'{}: {} "{}"'.format(cmd, _('Unexpected return'),
                                              buffer))
                    continue
            except Exception as e:
                qLog('connection',
                     u'{}: {}: "{}"'.format(cmd, _('Connection failed'), e))
                break

            if passingsNew != passingsCur:
                if passingsNew < passingsCur:
                    passingsCur = 0

                tagTimes = []
                errors = []
                times = set()

                passingsCount = passingsNew - passingsCur

                #---------------------------------------------------------------------------------------------
                cmd = '{}:{}'.format(
                    passingsCur + 1,
                    passingsCount)  # Add one as the reader counts inclusively.
                qLog(
                    'command', u'sending: {} ({}+{}={} passings)'.format(
                        cmd, passingsCur, passingsCount, passingsNew))
                try:
                    # Get the passing data.
                    socketSend(s, u'{}{}'.format(cmd, EOL))
                except Exception as e:
                    qLog(
                        'connection',
                        u'cmd={}: {}: "{}"'.format(cmd, _('Connection failed'),
                                                   e))
                    break

                tagReadSuccess = False
                try:
                    readAllPassings = False
                    while not readAllPassings:
                        response = socketReadDelimited(s)

                        sStart = 0
                        while 1:
                            sEnd = response.find(EOL, sStart)
                            if sEnd < 0:
                                break
                            if sEnd == sStart:  # An empty passing indicates this is the last one.
                                readAllPassings = True
                                break

                            line = response[sStart:sEnd]
                            sStart = sEnd + len_EOL

                            tag, t = parseTagTime(line,
                                                  passingsCur + len(tagTimes),
                                                  errors)
                            if tag is None or t is None:
                                qLog(
                                    'command', u'{}: {} "{}"'.format(
                                        cmd, _('Unexpected return'), line))
                                continue

                            t += readerComputerTimeDiff
                            while t in times:  # Ensure no equal times.
                                t += tSmall

                            times.add(t)
                            tagTimes.append((tag, t))

                    tagReadSuccess = True

                except Exception as e:
                    qLog(
                        'connection',
                        u'cmd={}: {}: "{}"'.format(cmd, _('Connection failed'),
                                                   e))

                sendReaderEvent(tagTimes)
                for tag, t in tagTimes:
                    q.put(('data', tag, t))
                passingsCur += len(tagTimes)

                if not tagReadSuccess:
                    break

            time.sleep(delaySecs)

    # Final cleanup.
    cmd = 'STOPOPERATION'
    try:
        socketSend(s, u'{}{}'.format(cmd, EOL))
        buffer = socketReadDelimited(s)
        s.shutdown(socket.SHUT_RDWR)
        s.close()
    except:
        pass
Example #15
0
def SavePhoto( fileName, bib, raceSeconds, cameraImage ):
	global font, brandingBitmap, photoCache
	bitmap = wx.BitmapFromImage( PilImageToWxImage(cameraImage) )
	
	w, h = bitmap.GetSize()
	dc = wx.MemoryDC( bitmap )
	fontHeight = h//32
	if not font:
		font = wx.FontFromPixelSize( wx.Size(0,fontHeight), wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_BOLD )
		
		brandingHeight = fontHeight * 2.3
		brandingBitmap = wx.Bitmap( os.path.join(Utils.getImageFolder(), 'CrossMgrHeader.png'), wx.BITMAP_TYPE_PNG )
		scaleMult = float(brandingHeight) / float(brandingBitmap.GetHeight())
		
		brandingImage = brandingBitmap.ConvertToImage()
		brandingImage.Rescale( int(brandingImage.GetWidth() * scaleMult), int(brandingImage.GetHeight() * scaleMult), wx.IMAGE_QUALITY_HIGH )
		brandingBitmap = brandingImage.ConvertToBitmap( dc.GetDepth() )
	
	txt = []
	if bib:
		try:
			riderInfo = Model.race.excelLink.read()[int(bib)]
		except:
			riderInfo = {}
		riderName = ', '.join( [n for n in [riderInfo.get('LastName',''), riderInfo.get('FirstName','')] if n] )
		if riderName:
			team = riderInfo.get('Team', '')
			if team:
				txt.append( '  %s    (%s)' % (riderName, team) )
			else:
				txt.append( '  %s' % riderName )

		txt.append( _('  Bib: {}    RaceTime: {}    {}').format(
			bib, formatTime(raceSeconds), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) )
	else:
		txt.append( _('  RaceTime: {}    {}').format(
			formatTime(raceSeconds), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) )
	
	bitmapHeight = brandingBitmap.GetHeight()
	xText, yText = brandingBitmap.GetWidth(), int(fontHeight*0.14)
	
	dc.SetFont( font )
	dc.SetPen( wx.BLACK_PEN )
	dc.SetBrush( wx.WHITE_BRUSH )
	dc.DrawRectangle( 0, 0, w, bitmapHeight+1 )
	dc.GradientFillLinear( wx.Rect(int(w/2), 0, int(w/2), bitmapHeight), wx.WHITE, wx.Colour(200,200,200), wx.EAST )
	
	dc.DrawBitmap( brandingBitmap, 0, 0 )
	
	lineHeight = int(fontHeight*1.07)
	dc.SetTextForeground( wx.BLACK )
	for t in txt:
		dc.DrawText( t, xText, yText )
		yText += lineHeight
		
	dc.DrawText( AppVerName, w - dc.GetTextExtent(AppVerName)[0] - fontHeight*.25, yText - lineHeight )
	
	dc.DrawLine( xText, 0, xText, bitmapHeight )
	
	dc.SetBrush( wx.Brush(wx.WHITE, wx.TRANSPARENT) )
	dc.DrawRectangle( 0, 0, w, bitmapHeight+1 )
	
	dc.SelectObject( wx.NullBitmap )
	image = wx.ImageFromBitmap( bitmap )
	
	# Check if the folder exists before trying to write to it.
	# Otherwise the SaveFile will raise an error dialog, which we don't want.
	if not os.path.isdir( os.path.dirname(fileName) ):
		try:
			os.mkdir( os.path.dirname(fileName) )
		except Exception as e:
			logException( e, sys.exc_info() )
			return 0
	
	# Try to save the file.
	try:
		image.SaveFile( fileName, wx.BITMAP_TYPE_JPEG )
		photoCache.add( os.path.basename(fileName) )
		return 1
	except Exception as e:
		logException( e, sys.exc_info() )
		return 0