2 # -*- coding: iso-8859-1 -*-
5 # Based on K******, a fancy presentation tool, by Martin Fiedler
6 # Copyright (C) 2005-2007 Martin J. Fiedler <martin.fiedler@gmx.net>
7 # portions Copyright (C) 2005 Rob Reid <rreid@drao.nrc.ca>
8 # portions Copyright (C) 2006 Ronan Le Hy <rlehy@free.fr>
9 # portions Copyright (C) 2007 Luke Campagnola <luke.campagnola@gmail.com>
11 # New Maintainer of the Accentuate fork.
13 # portions Copyright (C) 2008 Jorden Mauro <jrm8005@gmail.com>
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License, version 2, as
17 # published by the Free Software Foundation.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 __title__
= "Accentuate"
30 __author__
= "Jorden Mauro (based on code by Martin Fiedler"
31 __email__
= "jrm8005@gmail.com"
32 __website__
= "http://www.cs.rit.edu/~jrm8005"
35 def greet(): print >>sys
.stderr
, "Welcome to", __title__
, "version", __version__
36 if __name__
== "__main__": greet()
39 TopLeft
, BottomLeft
, TopRight
, BottomRight
, TopCenter
, BottomCenter
= range(6)
40 NoCache
, MemCache
, FileCache
, PersistentCache
= range(4) # for CacheMode
41 Off
, First
, Last
= range(3) # for AutoOverview
43 # You may change the following lines to modify the default settings
47 BackgroundRendering
= True
48 UseGhostScript
= False
49 UseAutoScreenSize
= True
52 TransitionDuration
= 1000
56 BlankFadeDuration
= 250
59 MarkColor
= (1.0, 0.0, 0.0, 0.1)
68 FadeToBlackAtEnd
= False
70 RenderToDirectory
= None
72 AllowExtensions
= True
79 FontTextureWidth
= 512
80 FontTextureHeight
= 256
85 EstimatedDuration
= None
87 ProgressBarAlpha
= 128
89 CursorHotspot
= (0, 0)
94 OSDTitlePos
= BottomLeft
95 OSDPagePos
= BottomRight
96 OSDStatusPos
= TopLeft
99 # Support for LIRC remotes
101 import pylirc
, time
, select
102 from threading
import Thread
105 print "LIRC support unavailable."
111 Thread
.__init
__(self
)
114 lirchandle
= pylirc
.init("Accentuate", '/etc/lircrc', 0)
115 print "Succesfully opened lirc, handle: " + str(lirchandle
)
116 print "LIRC event loop started..."
118 # Man this is nasty indentation, but I use 8 space tabs
119 # while apparently most Pythoners use 3 :(
122 s
= pylirc
.nextcode()
129 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 275, 'unicode': u
'', 'mod': 0}))
130 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 275, 'mod': 0}))
131 elif(code
== "prev"):
132 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 276, 'unicode': u
'', 'mod': 0}))
133 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 276, 'mod': 0}))
134 elif(code
== "menu"):
135 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 9, 'unicode': u
'\t', 'mod': 0}))
136 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 9, 'mod': 0}))
137 elif(code
== "play"):
138 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 13, 'unicode': u
'\r', 'mod': 0}))
139 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 13, 'mod': 0}))
140 elif(code
== "volume_down"):
141 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 274, 'unicode': u
'', 'mod': 0}))
142 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 274, 'mod': 0}))
143 elif(code
== "volume_up"):
144 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 273, 'unicode': u
'\r', 'mod': 0}))
145 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 273, 'mod': 0}))
149 # End LIRC support code
151 # import basic modules
152 import random
, getopt
, os
, types
, re
, codecs
, tempfile
, glob
, StringIO
, md5
, re
156 # initialize some platform-specific settings
158 root
= os
.path
.split(sys
.argv
[0])[0] or "."
159 pdftoppmPath
= os
.path
.join(root
, "pdftoppm.exe")
160 GhostScriptPath
= os
.path
.join(root
, "gs\\gswin32c.exe")
161 GhostScriptPlatformOptions
= ["-I" + os
.path
.join(root
, "gs")]
164 MPlayerPath
= os
.path
.join(root
, "mplayer.exe")
166 dm
= win32api
.EnumDisplaySettings(None, -1) #ENUM_CURRENT_SETTINGS
167 return (int(dm
.PelsWidth
), int(dm
.PelsHeight
))
169 win32api
.ShellExecute(0, "open", url
, "", "", 0)
172 def GetScreenSize(): return pygame
.display
.list_modes()[0]
173 def RunURL(url
): print "Error: cannot run URL `%s'" % url
174 MPlayerPlatformOptions
= [ "-colorkey", "0x000000" ]
175 MPlayerColorKey
= True
176 pdftkPath
= os
.path
.join(root
, "pdftk.exe")
179 if getattr(sys
, "frozen", None):
180 sys
.path
.append(root
)
182 FontList
= ["Verdana.ttf", "Arial.ttf"]
184 pdftoppmPath
= "pdftoppm"
185 GhostScriptPath
= "gs"
186 GhostScriptPlatformOptions
= []
187 MPlayerPath
= "mplayer"
188 MPlayerPlatformOptions
= [ "-vo", "gl" ]
189 MPlayerColorKey
= False
193 FontPath
= ["/usr/share/fonts", "/usr/local/share/fonts", "/usr/X11R6/lib/X11/fonts/TTF"]
194 FontList
= ["DejaVuSans.ttf", "Vera.ttf", "Verdana.ttf"]
197 spawn(os
.P_NOWAIT
, "xdg-open", ["xdg-open", url
])
199 print >>sys
.stderr
, "Error: cannot open URL `%s'" % url
201 res_re
= re
.compile(r
'\s*(\d+)x(\d+)\s+\d+\.\d+\*')
202 for path
in os
.getenv("PATH").split(':'):
203 fullpath
= os
.path
.join(path
, "xrandr")
204 if os
.path
.exists(fullpath
):
207 for line
in os
.popen(fullpath
, "r"):
208 m
= res_re
.match(line
)
210 res
= tuple(map(int, m
.groups()))
215 return pygame
.display
.list_modes()[0]
217 # import special modules
219 from OpenGL
.GL
import *
221 from pygame
.locals import *
222 import Image
, ImageDraw
, ImageFont
, ImageFilter
223 import TiffImagePlugin
, BmpImagePlugin
, JpegImagePlugin
, PngImagePlugin
, PpmImagePlugin
224 except (ValueError, ImportError), err
:
225 print >>sys
.stderr
, "Oops! Cannot load necessary modules:", err
226 print >>sys
.stderr
, """To use Accentuate, you need to install the following Python modules:
227 - PyOpenGL [python-opengl] http://pyopengl.sourceforge.net/
228 - PyGame [python-pygame] http://www.pygame.org/
229 - PIL [python-imaging] http://www.pythonware.com/products/pil/
230 - PyWin32 (OPTIONAL, Win32) http://starship.python.net/crew/mhammond/win32/
231 Additionally, please be sure to have pdftoppm or GhostScript installed if you
232 intend to use PDF input."""
237 EnableBackgroundRendering
= True
238 def create_lock(): return thread
.allocate_lock()
240 EnableBackgroundRendering
= False
242 def __init__(self
): self
.state
= False
243 def acquire(self
, dummy
=0): self
.state
= True
244 def release(self
): self
.state
= False
245 def locked(self
): return self
.state
246 def create_lock(): return pseudolock()
249 ##### TOOL CODE ################################################################
251 # initialize private variables
254 InfoScriptPath
= None
276 ZoomWarningIssued
= False
277 TransitionRunning
= False
279 OverviewNeedUpdate
= False
282 CurrentOSDCaption
= ""
284 CurrentOSDStatus
= ""
285 CurrentOSDComment
= ""
286 Lrender
= create_lock()
287 Lcache
= create_lock()
288 Loverview
= create_lock()
303 # tool constants (used in info scripts)
307 USEREVENT_HIDE_MOUSE
= USEREVENT
308 USEREVENT_PAGE_TIMEOUT
= USEREVENT
+ 1
309 USEREVENT_POLL_FILE
= USEREVENT
+ 2
310 USEREVENT_TIMER_UPDATE
= USEREVENT
+ 3
313 # read and write the PageProps and FileProps meta-dictionaries
314 def GetProp(prop_dict
, key
, prop
, default
=None):
315 if not key
in prop_dict
: return default
316 if type(prop
) == types
.StringType
:
317 return prop_dict
[key
].get(prop
, default
)
320 return prop_dict
[key
][subprop
]
324 def SetProp(prop_dict
, key
, prop
, value
):
325 if not key
in prop_dict
:
326 prop_dict
[key
] = {prop
: value
}
328 prop_dict
[key
][prop
] = value
330 def GetPageProp(page
, prop
, default
=None):
332 return GetProp(PageProps
, page
, prop
, default
)
333 def SetPageProp(page
, prop
, value
):
335 SetProp(PageProps
, page
, prop
, value
)
336 def GetTristatePageProp(page
, prop
, default
=0):
337 res
= GetPageProp(page
, prop
, default
)
338 if res
!= FirstTimeOnly
: return res
339 return (GetPageProp(page
, '_shown', 0) == 1)
341 def GetFileProp(page
, prop
, default
=None):
343 return GetProp(FileProps
, page
, prop
, default
)
344 def SetFileProp(page
, prop
, value
):
346 SetProp(FileProps
, page
, prop
, value
)
348 # the Accentuate logo (256x64 pixels grayscale PNG)
349 LOGO
= '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x00\x00\x00\x00@\x08\x00\x00\x00\x00\xd06\xf6b\x00'+ \
350 '\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a'+ \
351 '\x9c\x18\x00\x00\x00\x07tIME\x07\xd8\t\x01\x10\x06\x0bZ\xbb\xec\x88\x00\x00\x00\x19tEXtComment'+ \
352 '\x00Created with GIMPW\x81\x0e\x17\x00\x00\x05TIDATx\xda\xedXkL\\E\x18=\xfb\xa0\xbc\x16\x02\x94W'+ \
353 '\xdd"4\x88\x8d\x85\xb6P\x8b\xa9\xa6\xb5)-\x11\xaa\xa8\xa4\xc4Tc\xd26bc\xd5h\xc4\x1a\x8d\xa6\x06\xac5FM'+ \
354 '\x94\xd8\x88\xb1\xc1\xbe\xb4\xbe\xfda1\xd1\xa8\xd8\x07M\x9a`\x81D\xc4\x18\x0cH\x1f\xb6\xa6\xa5\xd8'+ \
355 '\x07\xb0@w\xaf?\xbeYv\xe6\xee\x9d\xb9+\x90\xf4\xc7\xce\xf9\xc3|\xe7\x9b\xfb\xdds\xcf^\xbe\x99\xb9\x80'+ \
356 '\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86F\xf4b\xb9\x11\xc4\xf2\xe8yh\'7\xde49'+ \
357 '\xdax]\xb40\xf7\xaf[\xa5\xc4+\x93o\xc0\xe5\x84\xa81\x80{\x03j<\x93\xc3\xa4\x9ahl\x01?\x1b!\xb4F\xe1'+ \
358 '\xbf\xc0\xbc\x00g@ /Z\x0cp\x87\x1a\x9f\x03\x000\x1a\x0f\x00\x8e\x8d\xf5\xa6\x89q\x95\xe5\xc5\xb9i'+ \
359 '\xb3|\x83\xa7\xba\xdb[\xfb\xec\xf9\x8c\xeaU\x0b\xbd\x9e\xc0`\xef\xb1/:\xadn\xec}\xac\xf2\xe6\x84\xa1'+ \
360 '\x9eov]\x9e\xfas\xban[\xb3t~\xb6\xc7\x18\xb9\xd0\xd7\xf9C\xab\xdfv\xbeR\x93\xa3\x9f\\\xdbB\x7f\xfa'+ \
361 '\x1cB6\xa1\xfe"\xf7~\x18\xb6|F\x93\x8f\xfb\x7fZ\x18\xfe\xd3l\x1de\xc3s+\x85\x8cX\xcbTV\x0c\x97'+ \
362 '\xbc\'\xdc\xfb\xe4&(*)41\x94Q\xa2\x17=4X\xc5\'\x17\xfdiYR\xca\xaf\xfcG\xa0}\x1bL\x8f\xe0\xdc\x1bJ'+ \
363 '\x8e\xdc:E\x03\xc2\xae\xd8\xe7T\x19 \xd5\xc4\xb0\x9f\xf8z\xbc\xc8\xaaq\xb9\xd2K\xd6\x9e\xca\xf82'+ \
364 '\x9fY\xc3\x06Q\xf4N>\xd7\xe5\x98!\x03\x8cm\n\x03\xe4\x9a\xd8\xc27L\xecM\xc8\xa3f8\x9c\x14z\x9f\xff'+ \
365 '\xb6\xf6T\xc6{\x07\xc34\xf8\x8ad\xa2\r\xc3\xa8\x98)\x03F\xb3\xa4\x95\x14\x9a\x08\xb5D\x1e\x07\xd0F'+ \
366 '\xc3G&s\xcd\xec\x8a\x0b\xdb\x8a\x93]\xde\xfb?\x1a3\xd4\xfc\x01\xf6n7\xdc\x12\x9b\xbc\xb6\x83\x82'+ \
367 '\x9fL\xa2\xf7\x14\xc7\xe5\xbe\xc3\xc6\xef\xdb\x18\x10\x90\x19\xd0\xbd\xe3\xee\xfcDg\xacwu\xd3\x04e'+ \
368 '\xea\xa4\x95\x14\x9a\x08\xc7\x88{\x12\x00k\x83m\xc1\xd4\\V\xbd#\x9b\x119_*\xf9<?\x19\xbc\x0c\x00'+ \
369 '\x10\xdfN\xb3\x8a\x05\x03\x1ax\x07OH\x17/"\xaeY\x1b\xf0\xdd\xed\xdc.\x9e2\xdf\xca*\xa94\x01\x00\n'+ \
370 '\x88\x99\xc8\x000{\x9c\x82\x02\x96\xab\xa3\xf0\xdf\xb9\xa6\xae!\xe3\x9f#\xfe\r\x16\x96S\xf8*\xaf'+ \
371 '\xac\xc7\x05\x00XA\xd1\xa0\x8d\x01\x13\xf6\xcb{\x1ce\xfae\x13U\x9a\x00\x00\xaf\xf1\x0e\x1e\xa4`\x07'+ \
372 '\xcb\xb1\xf0u\xf3Me|\x0b\xf1%\xc1#\x06\x85\x87xeu\x94\x9a#\xfe\xc22\x03\xc6e\x06d?\xba\xfb\xf8\x99a?'+ \
373 '\xb7\xa2\xc8*\xa94\x01\x80\xf341\x0f\x02\x00\xd6Sp\x8a\x9d\x13\xfa(\xbc\xc3\xfc\xa02\xbe\xdf\xb2\xd3'+ \
374 '\x9d\xe1\x95-\x15t\x186\x06\xf8\xac\r\xf0~|\xcd|\x93\x80\xac\x92J\x13\x00T\x08\x87\xc0\x04v,\xbc\x8b'+ \
375 '\x92#\x14\xa5\x9a\x1f\xd4\x867\xf7\\^Y\n\xdb\x86N\xc7\x80\xe2\xf3\x16w\x91URi\x02\x80O\x89\xd8\xcb\xc2}'+ \
376 '\x14~B\x11;#\xb8\xcd\x0fj\xc3[jcc\x17\xdb}N\xc3\x80\xc4\x01\xe3\x7f\x18\xa0\xd2\x04 \xd5g\x99\x1eM\x99'+ \
377 '\xc17\xc0@\xa4\x0b\xbb\xb4\tz\xf8\t\xcf\xd0\xd8\xdfX\x9a\x14A\xa5\x11\xb5\x01\x8f[\xa7\x8d-\xd3\xe9\x01'+ \
378 '\x19\x11\x1c\xd3"3\xc0`\xcdh>?\xe1\xb0\xd0N\x93m*\xc959U_\xc0\x88\xff\x8d\x82*sV\xc6\xff\xce\xbe0\xce'+ \
379 '\xc4\xb1\x96\xfe\xb0\x96!X\xcd\xb6q\x1f\xb2\x86`SH\xad\xa9\xd0\x90a\x01\x00<K\xe3\xa1\x1bL\x97\xc9\xf8'+ \
380 '\xe7-\xbe\xa9\x14\x1c\x8c\xe0\r\xf0\x0b\xbf7\x00\xb0n\\JQ\x1b?\x9dMN\x146\x9f\xd2J*M\xc0[R\x03\xde\xe4w|'+ \
381 '\xed\x99l~\xd6\xe7P\xf1\xf3\xd8\xed_\t\xfdZ\xcd\x13F\x04\x06\xb0\xa3U~H#;\x99\xbe\r\x00xB\xf8\xcf\xbdJ'+ \
382 '\xe3\x87\x00\x00\x0f\x1b6\x95T\x9a\xe0>G\xc9Z\xfe\xe5\'\xea\xac\x0b\x00v\xb3\xea\xe7_Z\xecqeW|0\xc2'+ \
383 '\xae\x94\xf1\x9f1\xfe\xc8\xfa\x9cY1\xde\xd5/\xb7\x87\xa4)\r\xf8\x83\xa2\x96\xa2X\x88\xcbQ`\xbb7\xb6\xf0]'+ \
384 '\xbf\xf0\x94\'h|isz\xcc\x82F\xbfaWI\xa1\tU\xac\x9ffr\x06\xa4\xb3=\xc6=\x00\x90y\xd6\xba{\xca\xf8\x9c\x8b'+ \
385 '\xd2\x86\xab4\xe0@\xd8\x055\xf2\xde\xdd\xa0j\xeb\xe1\x95\x14\x9a\xf0\xb5\xe9\xf0\x03\x008B\xe4W\x00\x80eW'+ \
386 '\xac\xaf\x94\xf1\xe5\xbe)\x19\xf0@\xd8\x05n\xe1\x83\xcbN~z\x96\xf0-b\x8f]%\x85\xa6tv\xf6\xd9ju\xd2\x19\x9b'+ \
387 '\r\x00(\xe9\xb3^?e\xfc\x9d\xe6w\xa3\xbb2\x02\x03b:\xc3j\xad\x18\x0f\x11\xfb]\xc2\xf4u\xdc\x19\xe0P\x9cm%'+ \
388 '\xb9\xa6\xa7\x8d\xe0\xa7\x10\x1e\xf9\x8c}\x8a\xed\xda\xb7\x0fY~\xfb\x93\xf1i\x8d\xc3\xdc\x0e\xfd\xf0:g$'+ \
389 '\xfb\x00\xe4v\x84\xd5\xaa\x08nx}/8L\xd3\xef\x9b\xfc\xc6\xd1\x1c\x17A%\x89&\x07\xba\x16\xd3\xaa^$.f\xdd'+ \
390 '\x85\x00\x80\xae\xe0\x01*~\xed\x9a\x92\xdc4\xf7\xd8\xe0\xc9\x9e\xf6\x1f\xb9\xaf\xbf2>\xf9\xde\xb2E7'+ \
391 '\xa6\xb8\xae\x9e\xfe\xf5h\xcb\x80yawX\x87pWW/\x99\xe3q\xf2djmUa\xcah\xef\xf7M\x03a\xd3S7W\x15&\x8f'+ \
392 '\xfeu\xb4\xf9\x97\x88*\xc94ihhhhhhhhhhhhhhhD\x19\xfe\x03\xec\xf7\xad7pH,\x9f\x00\x00\x00\x00IEND\xaeB`\x82'
394 # determine the next power of two
397 while res
< x
: res
<<= 1
400 # convert boolean value to string
405 # extract a number at the beginning of a string
409 while s
[0] in "0123456789":
417 # get a representative subset of file statistics
418 def my_stat(filename
):
420 s
= os
.stat(filename
)
423 return (s
.st_size
, s
.st_mtime
, s
.st_ctime
, s
.st_mode
)
425 # determine (pagecount,width,height) of a PDF file
426 def analyze_pdf(filename
):
427 f
= file(filename
,"rb")
430 box
= map(float, pdf
.split("/MediaBox",1)[1].split("]",1)[0].split("[",1)[1].strip().split())
431 return (max(map(num
, pdf
.split("/Count")[1:])), box
[2]-box
[0], box
[3]-box
[1])
433 # unescape { literals in PDF files
434 re_unescape
= re
.compile(r
'&#[0-9]+;')
435 def decode_literal(m
):
437 return chr(int(m
.group(0)[2:-1]))
441 return re_unescape
.sub(decode_literal
, s
)
444 def pdftkParse(filename
, page_offset
=0):
445 f
= file(filename
, "r")
450 for line
in f
.xreadlines():
452 key
, value
= [item
.strip() for item
in line
.split(':', 1)]
456 if key
== "numberofpages":
458 elif key
== "infokey":
459 InfoKey
= value
.lower()
460 elif (key
== "infovalue") and (InfoKey
== "title"):
461 Title
= unescape_pdf(value
)
463 elif key
== "bookmarktitle":
464 BookmarkTitle
= unescape_pdf(value
)
465 elif key
== "bookmarkpagenumber" and BookmarkTitle
:
468 if not GetPageProp(page
+ page_offset
, '_title'):
469 SetPageProp(page
+ page_offset
, '_title', BookmarkTitle
)
475 SetPageProp(page_offset
+ 1, '_overview', True)
476 for page
in xrange(page_offset
+ 2, page_offset
+ Pages
):
477 SetPageProp(page
, '_overview', \
478 not(not(GetPageProp(page
+ AutoOverview
- 1, '_title'))))
479 SetPageProp(page_offset
+ Pages
, '_overview', True)
480 return (Title
, Pages
)
482 # translate pixel coordinates to normalized screen coordinates
483 def MouseToScreen(mousepos
):
484 return (ZoomX0
+ mousepos
[0] * ZoomArea
/ ScreenWidth
,
485 ZoomY0
+ mousepos
[1] * ZoomArea
/ ScreenHeight
)
487 # normalize rectangle coordinates so that the upper-left point comes first
488 def NormalizeRect(X0
, Y0
, X1
, Y1
):
489 return (min(X0
, X1
), min(Y0
, Y1
), max(X0
, X1
), max(Y0
, Y1
))
491 # check if a point is inside a box (or a list of boxes)
492 def InsideBox(x
, y
, box
):
493 return (x
>= box
[0]) and (y
>= box
[1]) and (x
< box
[2]) and (y
< box
[3])
494 def FindBox(x
, y
, boxes
):
495 for i
in xrange(len(boxes
)):
496 if InsideBox(x
, y
, boxes
[i
]):
500 # zoom an image size to a destination size, preserving the aspect ratio
501 def ZoomToFit(size
, dest
=None):
503 dest
= (ScreenWidth
, ScreenHeight
)
505 newy
= size
[1] * newx
/ size
[0]
508 newx
= size
[0] * newy
/ size
[1]
511 # get the overlay grid screen coordinates for a specific page
512 def OverviewPos(page
):
514 int(page
% OverviewGridSize
) * OverviewCellX
+ OverviewOfsX
, \
515 int(page
/ OverviewGridSize
) * OverviewCellY
+ OverviewOfsY \
519 global MPlayerPID
, VideoPlaying
520 if not MPlayerPID
: return
523 win32api
.TerminateProcess(MPlayerPID
, 0)
525 os
.kill(MPlayerPID
, 2)
531 def FormatTime(t
, minutes
=False):
532 if minutes
and (t
< 3600):
533 return "%d min" % (t
/ 60)
535 return "%d:%02d" % (t
/ 3600, (t
/ 60) % 60)
537 return "%d:%02d" % (t
/ 60, t
% 60)
540 return "%d:%02d:%02d" % (t
/ 3600, ms
/ 60, ms
% 60)
542 def SafeCall(func
, args
=[], kwargs
={}):
543 if not func
: return None
545 return func(*args
, **kwargs
)
547 print >>sys
.stderr
, "----- Exception in user function ----"
548 traceback
.print_exc(file=sys
.stderr
)
549 print >>sys
.stderr
, "----- End of traceback -----"
552 print >>sys
.stderr
, "Total presentation time: %s." % \
553 FormatTime((pygame
.time
.get_ticks() - StartTime
) / 1000)
557 ##### RENDERING TOOL CODE ######################################################
559 # draw a fullscreen quad
562 glTexCoord2d( 0.0, 0.0); glVertex2i(0, 0)
563 glTexCoord2d(TexMaxS
, 0.0); glVertex2i(1, 0)
564 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2i(1, 1)
565 glTexCoord2d( 0.0, TexMaxT
); glVertex2i(0, 1)
568 # draw a generic 2D quad
569 def DrawQuad(x0
=0.0, y0
=0.0, x1
=1.0, y1
=1.0):
571 glTexCoord2d( 0.0, 0.0); glVertex2d(x0
, y0
)
572 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(x1
, y0
)
573 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(x1
, y1
)
574 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(x0
, y1
)
577 # helper function: draw a translated fullscreen quad
578 def DrawTranslatedFullQuad(dx
, dy
, i
, a
):
579 glColor4d(i
, i
, i
, a
)
581 glTranslated(dx
, dy
, 0.0)
585 # draw a vertex in normalized screen coordinates,
586 # setting texture coordinates appropriately
588 glTexCoord2d(x
*TexMaxS
, y
* TexMaxT
)
590 def DrawPointEx(x
, y
, a
):
591 glColor4d(1.0, 1.0, 1.0, a
)
592 glTexCoord2d(x
* TexMaxS
, y
* TexMaxT
)
595 # a mesh transformation function: it gets the relative transition time (in the
596 # [0.0,0.1) interval) and the normalized 2D screen coordinates, and returns a
597 # 7-tuple containing the desired 3D screen coordinates, 2D texture coordinates,
598 # and intensity/alpha color values.
599 def meshtrans_null(t
, u
, v
):
600 return (u
, v
, 0.0, u
, v
, 1.0, t
)
601 # (x, y, z, s, t, i, a)
603 # draw a quad, applying a mesh transformation function
604 def DrawMeshQuad(time
=0.0, f
=meshtrans_null
):
605 line0
= [f(time
, u
* MeshStepX
, 0.0) for u
in xrange(MeshResX
+ 1)]
606 for v
in xrange(1, MeshResY
+ 1):
607 line1
= [f(time
, u
* MeshStepX
, v
* MeshStepY
) for u
in xrange(MeshResX
+ 1)]
608 glBegin(GL_QUAD_STRIP
)
609 for col
in zip(line0
, line1
):
610 for x
, y
, z
, s
, t
, i
, a
in col
:
611 glColor4d(i
, i
, i
, a
)
612 glTexCoord2d(s
* TexMaxS
, t
* TexMaxT
)
617 def GenerateSpotMesh():
619 rx0
= SpotRadius
* PixelX
620 ry0
= SpotRadius
* PixelY
621 rx1
= (SpotRadius
+ BoxEdgeSize
) * PixelX
622 ry1
= (SpotRadius
+ BoxEdgeSize
) * PixelY
623 steps
= max(6, int(2.0 * pi
* SpotRadius
/ SpotDetail
/ ZoomArea
))
624 SpotMesh
=[(rx0
* sin(a
), ry0
* cos(a
), rx1
* sin(a
), ry1
* cos(a
)) for a
in \
625 [i
* 2.0 * pi
/ steps
for i
in range(steps
+ 1)]]
628 ##### TRANSITIONS ##############################################################
630 # Each transition is represented by a class derived from Accentuate.Transition
631 # The interface consists of only two methods: the __init__ method may perform
632 # some transition-specific initialization, and render() finally renders a frame
633 # of the transition, using the global texture identifierst Tcurrent and Tnext.
635 # Transition itself is an abstract class
636 class AbstractError(StandardError):
644 # an array containing all possible transition classes
647 # a helper function doing the common task of directly blitting a background page
648 def DrawPageDirect(tex
):
650 glBindTexture(TextureTarget
, tex
)
654 # a helper function that enables alpha blending
655 def EnableAlphaBlend():
657 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
660 # Crossfade: one of the simplest transition you can think of :)
661 class Crossfade(Transition
):
662 """simple crossfade"""
664 DrawPageDirect(Tcurrent
)
666 glBindTexture(TextureTarget
, Tnext
)
667 glColor4d(1, 1, 1, t
)
669 AllTransitions
.append(Crossfade
)
672 # Slide: a class of transitions that simply slide the new page in from one side
673 # after an idea from Joachim B Haga
674 class Slide(Transition
):
678 cx
, cy
, nx
, ny
= self
.origin(t
)
679 glBindTexture(TextureTarget
, Tcurrent
)
680 DrawQuad(cx
, cy
, cx
+1.0, cy
+1.0)
681 glBindTexture(TextureTarget
, Tnext
)
682 DrawQuad(nx
, ny
, nx
+1.0, ny
+1.0)
684 class SlideLeft(Slide
):
685 """Slide to the left"""
686 def origin(self
, t
): return (-t
, 0.0, 1.0-t
, 0.0)
687 class SlideRight(Slide
):
688 """Slide to the right"""
689 def origin(self
, t
): return (t
, 0.0, t
-1.0, 0.0)
690 class SlideUp(Slide
):
692 def origin(self
, t
): return (0.0, -t
, 0.0, 1.0-t
)
693 class SlideDown(Slide
):
694 """Slide downwards"""
695 def origin(self
, t
): return (0.0, t
, 0.0, t
-1.0)
696 AllTransitions
.extend([SlideLeft
, SlideRight
, SlideUp
, SlideDown
])
699 # Squeeze: a class of transitions that squeeze the new page in from one size
700 class Squeeze(Transition
):
703 def inv(self
): return 0
705 cx1
, cy1
, nx0
, ny0
= self
.params(t
)
707 t1
, t2
= (Tnext
, Tcurrent
)
709 t1
, t2
= (Tcurrent
, Tnext
)
710 glBindTexture(TextureTarget
, t1
)
711 DrawQuad(0.0, 0.0, cx1
, cy1
)
712 glBindTexture(TextureTarget
, t2
)
713 DrawQuad(nx0
, ny0
, 1.0, 1.0)
714 class SqueezeHorizontal(Squeeze
):
715 def split(self
, t
): raise AbstractError
718 return (t
, 1.0, t
, 0.0)
719 class SqueezeVertical(Squeeze
):
720 def split(self
, t
): raise AbstractError
723 return (1.0, t
, 0.0, t
)
725 class SqueezeLeft(SqueezeHorizontal
):
726 """Squeeze to the left"""
727 def split(self
, t
): return 1.0 - t
728 class SqueezeRight(SqueezeHorizontal
):
729 """Squeeze to the right"""
730 def split(self
, t
): return t
731 def inv(self
): return 1
732 class SqueezeUp(SqueezeVertical
):
733 """Squeeze upwards"""
734 def split(self
, t
): return 1.0 - t
735 class SqueezeDown(SqueezeVertical
):
736 """Squeeze downwards"""
737 def split(self
, t
): return t
738 def inv(self
): return 1
739 AllTransitions
.extend([SqueezeLeft
, SqueezeRight
, SqueezeUp
, SqueezeDown
])
742 # Wipe: a class of transitions that softly "wipe" the new image over the old
743 # one along a path specified by a gradient function that maps normalized screen
744 # coordinates to a number in the range [0.0,1.0]
746 class Wipe(Transition
):
747 def grad(self
, u
, v
):
750 pos
= (g
- self
.Wipe_start
) / WipeWidth
751 return max(min(pos
, 1.0), 0.0)
753 DrawPageDirect(Tnext
)
755 glBindTexture(TextureTarget
, Tcurrent
)
756 self
.Wipe_start
= t
* (1.0 + WipeWidth
) - WipeWidth
757 DrawMeshQuad(t
, lambda t
, u
, v
: \
758 (u
, v
, 0.0, u
,v
, 1.0, self
.afunc(self
.grad(u
, v
))))
760 class WipeDown(Wipe
):
762 def grad(self
, u
, v
): return v
765 def grad(self
, u
, v
): return 1.0 - v
766 class WipeRight(Wipe
):
767 """wipe from left to right"""
768 def grad(self
, u
, v
): return u
769 class WipeLeft(Wipe
):
770 """wipe from right to left"""
771 def grad(self
, u
, v
): return 1.0 - u
772 class WipeDownRight(Wipe
):
773 """wipe from the upper-left to the lower-right corner"""
774 def grad(self
, u
, v
): return 0.5 * (u
+ v
)
775 class WipeUpLeft(Wipe
):
776 """wipe from the lower-right to the upper-left corner"""
777 def grad(self
, u
, v
): return 1.0 - 0.5 * (u
+ v
)
778 class WipeCenterOut(Wipe
):
779 """wipe from the center outwards"""
780 def grad(self
, u
, v
):
783 return sqrt(u
* u
* 1.777 + v
* v
) / 0.833
784 class WipeCenterIn(Wipe
):
785 """wipe from the edges inwards"""
786 def grad(self
, u
, v
):
789 return 1.0 - sqrt(u
* u
* 1.777 + v
* v
) / 0.833
790 AllTransitions
.extend([WipeDown
, WipeUp
, WipeRight
, WipeLeft
, \
791 WipeDownRight
, WipeUpLeft
, WipeCenterOut
, WipeCenterIn
])
793 class WipeBlobs(Wipe
):
794 """wipe using nice \"blob\"-like patterns"""
796 self
.uscale
= (5.0 + random
.random() * 15.0) * 1.333
797 self
.vscale
= 5.0 + random
.random() * 15.0
798 self
.uofs
= random
.random() * 6.2
799 self
.vofs
= random
.random() * 6.2
801 return 0.5 + 0.25 * (cos(self
.uofs
+ u
* self
.uscale
) \
802 + cos(self
.vofs
+ v
* self
.vscale
))
803 AllTransitions
.append(WipeBlobs
)
805 class PagePeel(Transition
):
806 """an unrealistic, but nice page peel effect"""
809 glBindTexture(TextureTarget
, Tnext
)
810 DrawMeshQuad(t
, lambda t
, u
, v
: \
811 (u
, v
, 0.0, u
, v
, 1.0 - 0.5 * (1.0 - u
) * (1.0 - t
), 1.0))
813 glBindTexture(TextureTarget
, Tcurrent
)
814 DrawMeshQuad(t
, lambda t
, u
, v
: \
815 (u
* (1.0 - t
), 0.5 + (v
- 0.5) * (1.0 + u
* t
) * (1.0 + u
* t
), 0.0,
816 u
, v
, 1.0 - u
* t
* t
, 1.0))
817 AllTransitions
.append(PagePeel
)
819 ### additional transition by Ronan Le Hy <rlehy@free.fr> ###
821 class PageTurn(Transition
):
822 """another page peel effect, slower but more realistic than PagePeel"""
824 alpha_square
= alpha
* alpha
826 inv_sqrt_two
= 1. / sqrt(2.)
827 def warp(self
, t
, u
, v
):
828 # distance from the 2d origin to the folding line
829 dpt
= PageTurn
.sqrt_two
* (1.0 - t
)
830 # distance from the 2d origin to the projection of (u,v) on the folding line
831 d
= PageTurn
.inv_sqrt_two
* (u
+ v
)
833 # the smaller rho is, the closer to asymptotes are the x(u) and y(v) curves
834 # ie, smaller rho => neater fold
836 common_sq
= sqrt(4. - 8 * t
- 4.*(u
+v
) + 4.*t
*(t
+ v
+ u
) + (u
+v
)*(u
+v
) + 4 * rho
) / 2.
837 x
= 1. - t
+ 0.5 * (u
- v
) - common_sq
838 y
= 1. - t
+ 0.5 * (v
- u
) - common_sq
839 z
= - 0.5 * (PageTurn
.alpha
* dmdpt
+ sqrt(PageTurn
.alpha_square
* dmdpt
*dmdpt
+ 4))
841 # part of the sheet still flat on the screen: lit and opaque
845 # part of the sheet in the air, after the fold: shadowed and transparent
846 # z goes from -0.8 to -2 approximately
848 alpha
= 0.5 * z
+ 1.5
849 # the corner of the page that you hold between your fingers
850 dthumb
= 0.6 * u
+ 1.4 * v
- 2 * 0.95
857 return (x
,y
,z
, u
,v
, i
, alpha
)
860 glBindTexture(TextureTarget
, Tnext
)
861 DrawMeshQuad(t
,lambda t
, u
, v
: \
862 (u
, v
, 0.0, u
, v
, 1.0 - 0.5 * (1.0 - u
) * (1.0 - t
), 1.0))
864 glBindTexture(TextureTarget
, Tcurrent
)
865 DrawMeshQuad(t
, self
.warp
)
866 AllTransitions
.append(PageTurn
)
868 ##### some additional transitions by Rob Reid <rreid@drao.nrc.ca> #####
870 class Dissipate(Transition
):
871 """Sort of, but not quite, like blowing a bubble."""
873 glBindTexture(TextureTarget
,Tcurrent
)
875 DrawMeshQuad(t
,lambda t
,u
,v
: (u
, v
, t
, 0.5 + scalfact
* (u
- 0.5),\
876 0.5 + scalfact
* (v
- 0.5), 1.0, \
877 scalfact
* scalfact
))
879 glBindTexture(TextureTarget
,Tnext
)
882 AllTransitions
.append(Dissipate
)
884 class OldOutNewIn(Transition
):
885 """Parent class for transitions that do something with the current slide for
886 the first half, and then something with the next slide for the second half."""
887 def ffdmq(self
, t
, u
, v
):
888 """Function for use by DrawMeshQuad()."""
894 glBindTexture(TextureTarget
, Tcurrent
)
896 glBindTexture(TextureTarget
, Tnext
)
897 DrawMeshQuad(t
, self
.ffdmq
)
899 class ZoomOutIn(OldOutNewIn
):
900 """Zooms the current page out, and the next one in."""
901 def ffdmq(self
, t
, u
, v
):
902 scalfact
= abs(1.0 - 2.0 * t
)
903 return (0.5 + scalfact
* (u
- 0.5), 0.5 + scalfact
* (v
- 0.5), 0.0, \
905 AllTransitions
.append(ZoomOutIn
)
907 class SpinOutIn(OldOutNewIn
):
908 """Spins the current page out, and the next one in. Inspired by a classic
910 def ffdmq(self
, t
, u
, v
):
911 scalfact
= abs(1.0 - 2.0 * t
)
912 sa
= scalfact
* sin(16.0 * t
)
913 ca
= scalfact
* cos(16.0 * t
)
914 return (0.5 + ca
* (u
- 0.5) - 0.75 * sa
* (v
- 0.5),\
915 0.5 + 1.333 * sa
* (u
- 0.5) + ca
* (v
- 0.5),\
917 AllTransitions
.append(SpinOutIn
)
919 class SpiralOutIn(OldOutNewIn
):
920 """Flushes the current page away, only to have the next one overflow!"""
921 def ffdmq(self
, t
, u
, v
):
922 scalfact
= abs(1.0 - 2.0 * t
)
923 sa
= scalfact
* sin(16.0 * t
)
924 ca
= scalfact
* cos(16.0 * t
)
925 return (0.5 + sa
+ ca
* (u
- 0.5) - 0.75 * sa
* (v
- 0.5),\
926 0.5 + ca
+ 1.333 * sa
* (u
- 0.5) + ca
* (v
- 0.5),\
928 AllTransitions
.append(SpiralOutIn
)
930 class SlideCarousel(OldOutNewIn
):
931 """Old school transition accidentally made while working on SpinOutIn."""
932 def ffdmq(self
, t
, u
, v
):
935 scalfact
= 0.5 + 0.5 * abs(1.0 - 2.0 * t
)
936 const
= 0.5 - 0.5 * scalfact
937 return (ca
* (const
+ scalfact
* u
) - sa
* (const
+ scalfact
* v
), \
938 sa
* (const
+ scalfact
* u
) + ca
* (const
+ scalfact
* v
), \
940 AllTransitions
.append(SlideCarousel
)
942 class FlipBoardVert(OldOutNewIn
):
943 """Should look something like the spinning bookcase to the secret room..."""
944 def ffdmq(self
, t
, u
, v
):
949 x
= 0.5 + (u
- 0.5) * c
952 x
= 0.5 - (u
- 0.5) * c
955 return (x
, v
, z
, u
, v
, 1.0, 1.0)
956 AllTransitions
.append(FlipBoardVert
)
958 class FlipBoardHori(OldOutNewIn
):
959 """Some blackboards and whiteboards do this..."""
960 def ffdmq(self
, t
, u
, v
):
965 y
= 0.5 + (v
- 0.5) * c
968 y
= 0.5 - (v
- 0.5) * c
971 return (u
, y
, z
, u
, v
, 1.0, 1.0)
972 AllTransitions
.append(FlipBoardHori
)
974 def verticalblindold(t
, u
, v
):
976 umuaxis
= (u
% 0.2) - 0.1
978 return (uaxis
+ umuaxis
* cos(pit
), v
, umuaxis
* sin(pit
), u
, v
, 1.0, 1.0)
980 def verticalblindnew(t
, u
, v
):
981 vmvaxis
= (v
% 0.2) - 0.1
983 return (u
, vaxis
- vmvaxis
* cos(3.1415626 * t
),\
984 -vmvaxis
* sin(3.1415626 * t
), u
, v
, 1.0, 1.0)
986 class VerticalBlinds(Transition
):
987 """Vertical Venetian Blinds"""
992 glBindTexture(TextureTarget
,Tcurrent
)
993 DrawMeshQuad(t
, verticalblindold
)
995 glBindTexture(TextureTarget
,Tnext
)
996 DrawMeshQuad(1.0 - t
, verticalblindold
)
997 AllTransitions
.append(VerticalBlinds
)
999 # the AvailableTransitions array contains a list of all transition classes that
1000 # can be randomly assigned to pages
1001 AvailableTransitions
=[ # from coolest to lamest
1002 # PagePeel, # deactivated: too intrusive
1004 WipeCenterOut
,WipeCenterIn
,
1005 WipeDownRight
,WipeUpLeft
,WipeDown
,WipeUp
,WipeRight
,WipeLeft
,
1010 ##### OSD FONT RENDERER ########################################################
1012 # force a string or sequence of ordinals into a unicode string
1013 def ForceUnicode(s
, charset
='iso8859-15'):
1014 if type(s
) == types
.UnicodeType
:
1016 if type(s
) == types
.StringType
:
1017 return unicode(s
, charset
, 'ignore')
1018 if type(s
) in (types
.TupleType
, types
.ListType
):
1019 return u
''.join(map(unichr, s
))
1020 raise TypeError, "string argument not convertible to Unicode"
1022 # search a system font path for a font file
1023 def SearchFont(root
, name
):
1024 if not os
.path
.isdir(root
):
1028 while (len(infix
) < 10) and (len(fontfile
) != 1):
1029 fontfile
= filter(os
.path
.isfile
, glob
.glob(root
+ infix
+ name
))
1031 if len(fontfile
) != 1:
1036 # load a system font
1037 def LoadFont(dirs
, name
, size
):
1038 # first try to load the font directly
1040 return ImageFont
.truetype(name
, size
, encoding
='unic')
1043 # no need to search further on Windows
1046 # start search for the font
1048 fontfile
= SearchFont(dir + "/", name
)
1051 return ImageFont
.truetype(fontfile
, size
, encoding
='unic')
1056 # alignment constants
1064 # font renderer class
1066 def __init__(self
, width
, height
, name
, size
, search_path
=[], default_charset
='iso8859-15', extend
=1, blur
=1):
1068 self
.height
= height
1069 self
._i
_extend
= range(extend
)
1070 self
._i
_blur
= range(blur
)
1071 self
.feather
= extend
+ blur
+ 1
1077 self
.line_height
= 0
1078 self
.default_charset
= default_charset
1079 if type(name
) == types
.StringType
:
1080 self
.font
= LoadFont(search_path
, name
, size
)
1082 for check_name
in name
:
1083 self
.font
= LoadFont(search_path
, check_name
, size
)
1086 raise IOError, "font file not found"
1087 self
.img
= Image
.new('LA', (width
, height
))
1088 self
.alpha
= Image
.new('L', (width
, height
))
1089 self
.extend
= ImageFilter
.MaxFilter()
1090 self
.blur
= ImageFilter
.Kernel((3, 3), [1,2,1,2,4,2,1,2,1])
1091 self
.tex
= glGenTextures(1)
1092 glBindTexture(GL_TEXTURE_2D
, self
.tex
)
1093 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
)
1094 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
)
1095 self
.AddString(range(32, 128))
1097 def AddCharacter(self
, c
):
1098 w
, h
= self
.font
.getsize(c
)
1099 self
.line_height
= max(self
.line_height
, h
)
1100 size
= (w
+ 2 * self
.feather
, h
+ 2 * self
.feather
)
1101 glyph
= Image
.new('L', size
)
1102 draw
= ImageDraw
.Draw(glyph
)
1103 draw
.text((self
.feather
, self
.feather
), c
, font
=self
.font
, fill
=255)
1106 box
= self
.AllocateGlyphBox(*size
)
1107 self
.img
.paste(glyph
, (box
.orig_x
, box
.orig_y
))
1109 for i
in self
._i
_extend
: glyph
= glyph
.filter(self
.extend
)
1110 for i
in self
._i
_blur
: glyph
= glyph
.filter(self
.blur
)
1111 self
.alpha
.paste(glyph
, (box
.orig_x
, box
.orig_y
))
1117 def AddString(self
, s
, charset
=None, fail_silently
=False):
1120 for c
in ForceUnicode(s
, self
.GetCharset(charset
)):
1121 if c
in self
.widths
:
1123 self
.AddCharacter(c
)
1130 if not update_count
: return
1131 self
.img
.putalpha(self
.alpha
)
1132 glBindTexture(GL_TEXTURE_2D
, self
.tex
)
1133 glTexImage2D(GL_TEXTURE_2D
, 0, GL_LUMINANCE_ALPHA
, \
1134 self
.width
, self
.height
, 0, \
1135 GL_LUMINANCE_ALPHA
, GL_UNSIGNED_BYTE
, self
.img
.tostring())
1137 def AllocateGlyphBox(self
, w
, h
):
1138 if self
.current_x
+ w
> self
.width
:
1140 self
.current_y
+= self
.max_height
1142 if self
.current_y
+ h
> self
.height
:
1143 raise ValueError, "bitmap too small for all the glyphs"
1144 box
= self
.GlyphBox()
1145 box
.orig_x
= self
.current_x
1146 box
.orig_y
= self
.current_y
1149 box
.x0
= self
.current_x
/ float(self
.width
)
1150 box
.y0
= self
.current_y
/ float(self
.height
)
1151 box
.x1
= (self
.current_x
+ w
) / float(self
.width
)
1152 box
.y1
= (self
.current_y
+ h
) / float(self
.height
)
1153 box
.dsx
= w
* PixelX
1154 box
.dsy
= h
* PixelY
1156 self
.max_height
= max(self
.max_height
, h
)
1159 def GetCharset(self
, charset
=None):
1160 if charset
: return charset
1161 return self
.default_charset
1163 def SplitText(self
, s
, charset
=None):
1164 return ForceUnicode(s
, self
.GetCharset(charset
)).split(u
'\n')
1166 def GetLineHeight(self
):
1167 return self
.line_height
1169 def GetTextWidth(self
, s
, charset
=None):
1170 return max([self
.GetTextWidthEx(line
) for line
in self
.SplitText(s
, charset
)])
1172 def GetTextHeight(self
, s
, charset
=None):
1173 return len(self
.SplitText(s
, charset
)) * self
.line_height
1175 def GetTextSize(self
, s
, charset
=None):
1176 lines
= self
.SplitText(s
, charset
)
1177 return (max([self
.GetTextWidthEx(line
) for line
in lines
]), len(lines
) * self
.line_height
)
1179 def GetTextWidthEx(self
, u
):
1180 if u
: return sum([self
.widths
.get(c
, 0) for c
in u
])
1183 def GetTextHeightEx(self
, u
=[]):
1184 return self
.line_height
1186 def AlignTextEx(self
, x
, u
, align
=Left
):
1187 if not align
: return x
1188 return x
- (self
.GetTextWidthEx(u
) / align
)
1190 def Draw(self
, origin
, text
, charset
=None, align
=Left
, color
=(1.0, 1.0, 1.0), alpha
=1.0, beveled
=True):
1191 lines
= self
.SplitText(text
, charset
)
1195 glEnable(GL_TEXTURE_2D
)
1197 glBindTexture(GL_TEXTURE_2D
, self
.tex
)
1199 glBlendFunc(GL_ZERO
, GL_ONE_MINUS_SRC_ALPHA
)
1200 glColor4d(0.0, 0.0, 0.0, alpha
)
1201 self
.DrawLinesEx(x0
, y0
, lines
, align
)
1202 glBlendFunc(GL_ONE
, GL_ONE
)
1203 glColor3d(color
[0] * alpha
, color
[1] * alpha
, color
[2] * alpha
)
1204 self
.DrawLinesEx(x0
, y0
, lines
, align
)
1206 glDisable(GL_TEXTURE_2D
)
1208 def DrawLinesEx(self
, x0
, y
, lines
, align
=Left
):
1209 global PixelX
, PixelY
1213 x
= self
.AlignTextEx(x0
, line
, align
)
1215 if not c
in self
.widths
: continue
1216 self
.boxes
[c
].render(x
* PixelX
, sy
)
1218 y
+= self
.line_height
1222 def render(self
, sx
=0.0, sy
=0.0):
1223 glTexCoord2d(self
.x0
, self
.y0
); glVertex2d(sx
, sy
)
1224 glTexCoord2d(self
.x0
, self
.y1
); glVertex2d(sx
, sy
+self
.dsy
)
1225 glTexCoord2d(self
.x1
, self
.y1
); glVertex2d(sx
+self
.dsx
, sy
+self
.dsy
)
1226 glTexCoord2d(self
.x1
, self
.y0
); glVertex2d(sx
+self
.dsx
, sy
)
1228 # high-level draw function
1229 def DrawOSD(x
, y
, text
, halign
=Auto
, valign
=Auto
, alpha
=1.0):
1230 if not(OSDFont
) or not(text
) or (alpha
<= 0.004): return
1231 if alpha
> 1.0: alpha
= 1.0
1245 y
-= OSDFont
.GetLineHeight() / valign
1246 if TextureTarget
!= GL_TEXTURE_2D
:
1247 glDisable(TextureTarget
)
1248 OSDFont
.Draw((x
, y
), text
, align
=halign
, alpha
=alpha
)
1250 # very high-level draw function
1251 def DrawOSDEx(position
, text
, alpha_factor
=1.0):
1252 xpos
= position
>> 1
1253 y
= (1 - 2 * (position
& 1)) * OSDMargin
1255 x
= (1 - 2 * xpos
) * OSDMargin
1260 DrawOSD(x
, y
, text
, halign
, alpha
= OSDAlpha
* alpha_factor
)
1263 ##### PDF PARSER ###############################################################
1265 class PDFError(Exception):
1269 def __init__(self
, ref
):
1272 return "PDFref(%d)" % self
.ref
1274 re_pdfstring
= re
.compile(r
'\(\)|\(.*?[^\\]\)')
1275 pdfstringrepl
= [("\\"+x
[0], x
[1:]) for x
in "(( )) n\n r\r t\t".split(" ")]
1276 def pdf_maskstring(s
):
1278 for a
, b
in pdfstringrepl
:
1280 return " <" + "".join(["%02X"%ord(c
) for c
in s
]) + "> "
1281 def pdf_mask_all_strings(s
):
1282 return re_pdfstring
.sub(lambda x
: pdf_maskstring(x
.group(0)), s
)
1283 def pdf_unmaskstring(s
):
1284 return "".join([chr(int(s
[i
:i
+2], 16)) for i
in xrange(1, len(s
)-1, 2)])
1287 def __init__(self
, filename
):
1288 self
.f
= file(filename
, "rb")
1290 # find the first cross-reference table
1292 filesize
= self
.f
.tell()
1293 self
.f
.seek(filesize
- 128)
1294 trailer
= self
.f
.read()
1295 i
= trailer
.rfind("startxref")
1297 raise PDFError
, "cross-reference table offset missing"
1299 offset
= int(trailer
[i
:].split("\n")[1].strip())
1300 except (IndexError, ValueError):
1301 raise PDFError
, "malformed cross-reference table offset"
1303 # follow the trailer chain
1307 self
.xref
, rootref
, offset
= self
.parse_trailer(offset
)
1308 self
.xref
.update(newxref
)
1310 # scan the page tree
1316 root
= self
.getobj(rootref
, 'Catalog')
1318 self
.scan_page_tree(root
['Pages'].ref
)
1320 raise PDFError
, "root page tree node missing"
1324 line
= self
.f
.readline().strip()
1325 if line
: return line
1327 def find_length(self
, tokens
, begin
, end
):
1329 for i
in xrange(1, len(tokens
)):
1330 if tokens
[i
] == begin
: level
+= 1
1331 if tokens
[i
] == end
: level
-= 1
1335 def parse_tokens(self
, tokens
, want_list
=False):
1341 if (len(tokens
) >= 3) and (tokens
[2] == 'R'):
1345 tlen
= self
.find_length(tokens
, "<<", ">>")
1346 v
= self
.parse_tokens(tokens
[1 : tlen
- 1], True)
1347 v
= dict(zip(v
[::2], v
[1::2]))
1349 tlen
= self
.find_length(tokens
, "[", "]")
1350 v
= self
.parse_tokens(tokens
[1 : tlen
- 1], True)
1351 elif not(t
) or (t
[0] == "null"):
1353 elif (t
[0] == '<') and (t
[-1] == '>'):
1354 v
= pdf_unmaskstring(t
)
1375 def parse(self
, data
):
1376 data
= pdf_mask_all_strings(data
)
1377 data
= data
.replace("<<", " << ").replace("[", " [ ").replace("(", " (")
1378 data
= data
.replace(">>", " >> ").replace("]", " ] ").replace(")", ") ")
1379 data
= data
.replace("/", " /")
1380 return self
.parse_tokens(filter(None, data
.split()))
1382 def getobj(self
, obj
, force_type
=None):
1383 offset
= self
.xref
.get(obj
, 0)
1385 raise PDFError
, "referenced non-existing PDF object"
1387 header
= self
.getline().split(None, 2)
1388 if (header
[-1] != "obj") or (header
[0] != str(obj
)):
1389 raise PDFError
, "object does not start where it's supposed to"
1392 line
= self
.getline()
1393 if line
in ("endobj", "stream"): break
1395 data
= self
.parse(" ".join(data
))
1399 except (KeyError, IndexError, ValueError):
1402 raise PDFError
, "object does not match the intended type"
1405 def parse_xref_section(self
, start
, count
):
1407 for obj
in xrange(start
, start
+ count
):
1408 line
= self
.getline()
1412 xref
[obj
] = int(line
[:10], 10)
1415 def parse_trailer(self
, offset
):
1420 if self
.getline() != "xref":
1421 raise PDFError
, "cross-reference table does not start where it's supposed to"
1422 return (xref
, rootref
, offset
) # no xref table found, abort
1423 # parse xref sections
1425 line
= self
.getline()
1426 if line
== "trailer": break
1427 start
, count
= map(int, line
.split())
1428 xref
.update(self
.parse_xref_section(start
, count
))
1431 line
= self
.getline()
1432 if line
in ("startxref", "%%EOF"): break
1433 if line
[0] != '/': continue
1434 parts
= line
[1:].split()
1435 if parts
[0] == 'Prev':
1436 offset
= int(parts
[1])
1437 if parts
[0] == 'Root':
1438 if (len(parts
) != 4) or (parts
[3] != 'R'):
1439 raise PDFError
, "root catalog entry is not a reference"
1440 rootref
= int(parts
[1])
1441 return (xref
, rootref
, offset
)
1443 def scan_page_tree(self
, obj
, mbox
=None, cbox
=None):
1444 node
= self
.getobj(obj
)
1445 if node
['Type'] == 'Pages':
1446 for kid
in node
['Kids']:
1447 self
.scan_page_tree(kid
.ref
, node
.get('MediaBox', mbox
), node
.get('CropBox', cbox
))
1449 page
= self
.page_count
+ 1
1450 self
.page_count
= page
1451 self
.obj2page
[obj
] = page
1452 self
.page2obj
[page
] = obj
1453 self
.annots
[page
] = [a
.ref
for a
in node
.get('Annots', [])]
1454 self
.box
[page
] = node
.get('CropBox', cbox
) or node
.get('MediaBox', mbox
)
1456 def dest2page(self
, dest
):
1457 if type(dest
) != types
.ListType
:
1459 elif dest
[0].__class
__ == PDFref
:
1460 return self
.obj2page
.get(dest
[0].ref
, None)
1464 def get_href(self
, obj
):
1465 node
= self
.getobj(obj
, 'Annot')
1466 if node
['Subtype'] != 'Link': return None
1469 dest
= self
.dest2page(node
['Dest'])
1471 action
= node
['A']['S']
1473 dest
= node
['A'].get('URI', None)
1474 elif action
== 'GoTo':
1475 dest
= self
.dest2page(node
['A'].get('D', None))
1477 return tuple(node
['Rect'] + [dest
])
1479 def GetHyperlinks(self
):
1481 for page
in self
.annots
:
1482 a
= filter(None, map(self
.get_href
, self
.annots
[page
]))
1487 def AddHyperlink(page_offset
, page
, target
, linkbox
, pagebox
):
1489 if type(target
) == types
.IntType
:
1490 target
+= page_offset
1491 w
= 1.0 / (pagebox
[2] - pagebox
[0])
1492 h
= 1.0 / (pagebox
[3] - pagebox
[1])
1493 x0
= (linkbox
[0] - pagebox
[0]) * w
1494 y0
= (pagebox
[3] - linkbox
[3]) * h
1495 x1
= (linkbox
[2] - pagebox
[0]) * w
1496 y1
= (pagebox
[3] - linkbox
[1]) * h
1497 href
= (0, target
, x0
, y0
, x1
, y1
)
1498 if GetPageProp(page
, '_href'):
1499 PageProps
[page
]['_href'].append(href
)
1501 SetPageProp(page
, '_href', [href
])
1504 def FixHyperlinks(page
):
1505 if not(GetPageProp(page
, '_box')) or not(GetPageProp(page
, '_href')):
1506 return # no hyperlinks or unknown page size
1507 bx0
, by0
, bx1
, by1
= GetPageProp(page
, '_box')
1511 for fixed
, target
, x0
, y0
, x1
, y1
in GetPageProp(page
, '_href'):
1513 href
.append((1, target
, x0
, y0
, x1
, y1
))
1515 href
.append((1, target
, \
1516 int(bx0
+ bdx
* x0
), int(by0
+ bdy
* y0
), \
1517 int(bx0
+ bdx
* x1
), int(by0
+ bdy
* y1
)))
1518 SetPageProp(page
, '_href', href
)
1521 def ParsePDF(filename
):
1523 assert 0 == spawn(os
.P_WAIT
, pdftkPath
, \
1524 ["pdftk", FileNameEscape
+ filename
+ FileNameEscape
, \
1525 "output", FileNameEscape
+ TempFileName
+ ".pdf" + FileNameEscape
,
1528 print >>sys
.stderr
, "Note: pdftk not found, hyperlinks disabled."
1530 except AssertionError:
1531 print >>sys
.stderr
, "Note: pdftk failed, hyperlinks disabled."
1537 pdf
= PDFParser(TempFileName
+ ".pdf")
1538 for page
, annots
in pdf
.GetHyperlinks().iteritems():
1539 for page_offset
in FileProps
[filename
]['offsets']:
1541 AddHyperlink(page_offset
, page
, a
[4], a
[:4], pdf
.box
[page
])
1542 count
+= len(annots
)
1547 print >>sys
.stderr
, "Note: file produced by pdftk not readable, hyperlinks disabled."
1549 print >>sys
.stderr
, "Note: error in file produced by pdftk, hyperlinks disabled."
1550 print >>sys
.stderr
, " PDF parser error message:", e
1553 os
.remove(TempFileName
+ ".pdf")
1558 ##### PAGE CACHE MANAGEMENT ####################################################
1560 # helper class that allows PIL to write and read image files with an offset
1562 def __init__(self
, f
, offset
=0):
1564 self
.offset
= offset
1566 def read(self
, count
=None):
1568 return self
.f
.read()
1570 return self
.f
.read(count
)
1571 def write(self
, data
):
1573 def seek(self
, pos
, whence
=0):
1574 assert(whence
in (0, 1))
1578 self
.f
.seek(pos
+ self
.offset
)
1580 return self
.f
.tell() - self
.offset
1582 # generate a "magic number" that is used to identify persistent cache files
1583 def UpdateCacheMagic():
1585 pool
= [PageCount
, ScreenWidth
, ScreenHeight
, b2s(Scaling
), b2s(Supersample
), b2s(Rotation
)]
1586 flist
= list(FileProps
.keys())
1587 flist
.sort(lambda a
,b
: cmp(a
.lower(), b
.lower()))
1590 pool
.extend(list(GetFileProp(f
, 'stat', [])))
1591 CacheMagic
= md5
.new("\0".join(map(str, pool
))).hexdigest()
1593 # set the persistent cache file position to the current end of the file
1594 def UpdatePCachePos():
1596 CacheFile
.seek(0, 2)
1597 CacheFilePos
= CacheFile
.tell()
1599 # rewrite the header of the persistent cache
1600 def WritePCacheHeader(reset
=False):
1601 pages
= ["%08x" % PageCache
.get(page
, 0) for page
in range(1, PageCount
+1)]
1603 CacheFile
.write(CacheMagic
+ "".join(pages
))
1605 CacheFile
.truncate()
1608 # return an image from the persistent cache or None if none is available
1609 def GetPCacheImage(page
):
1610 if CacheMode
!= PersistentCache
:
1611 return # not applicable if persistent cache isn't used
1614 if page
in PageCache
:
1615 img
= Image
.open(IOWrapper(CacheFile
, PageCache
[page
]))
1621 # returns an image from the non-persistent cache or None if none is available
1622 def GetCacheImage(page
):
1623 if CacheMode
in (NoCache
, PersistentCache
):
1624 return # not applicable in uncached or persistent-cache mode
1627 if page
in PageCache
:
1628 if CacheMode
== FileCache
:
1629 CacheFile
.seek(PageCache
[page
])
1630 return CacheFile
.read(TexSize
)
1632 return PageCache
[page
]
1636 # adds an image to the persistent cache
1637 def AddToPCache(page
, img
):
1638 if CacheMode
!= PersistentCache
:
1639 return # not applicable if persistent cache isn't used
1642 if page
in PageCache
:
1643 return # page is already cached and we can't update it safely
1644 # -> stop here (the new image will be identical to the old
1646 img
.save(IOWrapper(CacheFile
, CacheFilePos
), "ppm")
1647 PageCache
[page
] = CacheFilePos
1652 # adds an image to the non-persistent cache
1653 def AddToCache(page
, data
):
1655 if CacheMode
in (NoCache
, PersistentCache
):
1656 return # not applicable in uncached or persistent-cache mode
1659 if CacheMode
== FileCache
:
1660 if not(page
in PageCache
):
1661 PageCache
[page
] = CacheFilePos
1662 CacheFilePos
+= len(data
)
1663 CacheFile
.seek(PageCache
[page
])
1664 CacheFile
.write(data
)
1666 PageCache
[page
] = data
1670 # invalidates the whole cache
1671 def InvalidateCache():
1672 global PageCache
, CacheFilePos
1676 if CacheMode
== PersistentCache
:
1678 WritePCacheHeader(True)
1684 # initialize the persistent cache
1686 global CacheFile
, CacheMode
1688 # try to open the pre-existing cache file
1690 CacheFile
= file(CacheFileName
, "rb+")
1694 # check the cache magic
1696 if CacheFile
and (CacheFile
.read(32) != CacheMagic
):
1697 print >>sys
.stderr
, "Cache file mismatch, recreating cache."
1702 # if the magic was valid, import cache data
1703 print >>sys
.stderr
, "Using already existing persistent cache file."
1704 for page
in range(1, PageCount
+1):
1705 offset
= int(CacheFile
.read(8), 16)
1707 PageCache
[page
] = offset
1710 # if the magic was invalid or the file didn't exist, (re-)create it
1712 CacheFile
= file(CacheFileName
, "wb+")
1714 print >>sys
.stderr
, "Error: cannot write the persistent cache file (`%s')" % CacheFileName
1715 print >>sys
.stderr
, "Falling back to temporary file cache."
1716 CacheMode
= FileCache
1720 ##### PAGE RENDERING ###########################################################
1722 # generate a dummy image
1724 img
= Image
.new('RGB', (ScreenWidth
, ScreenHeight
))
1725 img
.paste(LogoImage
, ((ScreenWidth
- LogoImage
.size
[0]) / 2,
1726 (ScreenHeight
- LogoImage
.size
[1]) / 2))
1729 # load a page from a PDF file
1730 def RenderPDF(page
, MayAdjustResolution
, ZoomMode
):
1731 global UseGhostScript
1732 UseGhostScriptOnce
= False
1734 SourceFile
= GetPageProp(page
, '_file')
1735 Resolution
= GetFileProp(SourceFile
, 'res', 96)
1736 RealPage
= GetPageProp(page
, '_page')
1738 if Supersample
and not(ZoomMode
):
1739 UseRes
= int(0.5 + Resolution
) * Supersample
1742 UseRes
= int(0.5 + Resolution
)
1747 # call pdftoppm to generate the page image
1748 if not UseGhostScript
:
1749 renderer
= "pdftoppm"
1751 assert 0 == spawn(os
.P_WAIT
, \
1752 pdftoppmPath
, ["pdftoppm", "-q"] + [ \
1753 "-f", str(RealPage
), "-l", str(RealPage
),
1754 "-r", str(int(UseRes
)),
1755 FileNameEscape
+ SourceFile
+ FileNameEscape
,
1757 # determine output filename
1758 digits
= GetFileProp(SourceFile
, 'digits', 6)
1759 imgfile
= TempFileName
+ ("-%%0%dd.ppm" % digits
) % RealPage
1760 if not os
.path
.exists(imgfile
):
1761 for digits
in xrange(6, 0, -1):
1762 imgfile
= TempFileName
+ ("-%%0%dd.ppm" % digits
) % RealPage
1763 if os
.path
.exists(imgfile
): break
1764 SetFileProp(SourceFile
, 'digits', digits
)
1765 except OSError, (errcode
, errmsg
):
1766 print >>sys
.stderr
, "Warning: Cannot start pdftoppm -", errmsg
1767 print >>sys
.stderr
, "Falling back to GhostScript (permanently)."
1768 UseGhostScript
= True
1769 except AssertionError:
1770 print >>sys
.stderr
, "There was an error while rendering page %d" % page
1771 print >>sys
.stderr
, "Falling back to GhostScript for this page."
1772 UseGhostScriptOnce
= True
1774 # fallback to GhostScript
1775 if UseGhostScript
or UseGhostScriptOnce
:
1776 imgfile
= TempFileName
+ ".tif"
1777 renderer
= "GhostScript"
1779 assert 0 == spawn(os
.P_WAIT
, \
1780 GhostScriptPath
, ["gs", "-q"] + GhostScriptPlatformOptions
+ [ \
1781 "-dBATCH", "-dNOPAUSE", "-sDEVICE=tiff24nc", "-dUseCropBox",
1782 "-sOutputFile=" + imgfile
, \
1783 "-dFirstPage=%d" % RealPage
, "-dLastPage=%d" % RealPage
,
1784 "-r%dx%d" % (UseRes
, int(UseRes
* PAR
)), \
1785 "-dTextAlphaBits=%d" % AlphaBits
, \
1786 "-dGraphicsAlphaBits=%s" % AlphaBits
, \
1787 FileNameEscape
+ SourceFile
+ FileNameEscape
])
1788 except OSError, (errcode
, errmsg
):
1789 print >>sys
.stderr
, "Error: Cannot start GhostScript -", errmsg
1791 except AssertionError:
1792 print >>sys
.stderr
, "There was an error while rendering page %d" % page
1795 # open the page image file with PIL
1797 img
= Image
.open(imgfile
)
1799 print >>sys
.stderr
, "Error: %s produced an unreadable file (page %d)" % (renderer
, page
)
1802 # try to delete the file again (this constantly fails on Win32 ...)
1809 rot
= GetPageProp(page
, 'rotate')
1813 img
= img
.rotate(90 * (4 - rot
))
1815 # determine real display size (don't care for ZoomMode, DisplayWidth and
1816 # DisplayHeight are only used for Supersample and AdjustResolution anyway)
1818 DisplayWidth
= img
.size
[0] / Supersample
1819 DisplayHeight
= img
.size
[1] / Supersample
1821 DisplayWidth
= img
.size
[0]
1822 DisplayHeight
= img
.size
[1]
1824 # if the image size is strange, re-adjust the rendering resolution
1825 if MayAdjustResolution \
1826 and ((abs(ScreenWidth
- DisplayWidth
) > 4) \
1827 or (abs(ScreenHeight
- DisplayHeight
) > 4)):
1828 newsize
= ZoomToFit((DisplayWidth
,DisplayHeight
))
1829 NewResolution
= newsize
[0] * Resolution
/DisplayWidth
1830 if abs(1.0 - NewResolution
/ Resolution
) > 0.05:
1831 # only modify anything if the resolution deviation is large enough
1832 SetFileProp(SourceFile
, 'res', NewResolution
)
1833 return RenderPDF(page
, False, ZoomMode
)
1835 # downsample a supersampled image
1836 if Supersample
and not(ZoomMode
):
1837 return img
.resize((DisplayWidth
, DisplayHeight
), Image
.ANTIALIAS
)
1842 # load a page from an image file
1843 def LoadImage(page
, ZoomMode
):
1844 # open the image file with PIL
1846 img
= Image
.open(GetPageProp(page
, '_file'))
1848 print >>sys
.stderr
, "Image file `%s' is broken." % (FileList
[page
- 1])
1852 rot
= GetPageProp(page
, 'rotate')
1856 img
= img
.rotate(90 * (4 - rot
))
1858 # determine destination size
1859 newsize
= ZoomToFit(img
.size
)
1860 # don't scale if the source size is too close to the destination size
1861 if abs(newsize
[0] - img
.size
[0]) < 2: newsize
= img
.size
1862 # don't scale if the source is smaller than the destination
1863 if not(Scaling
) and (newsize
> img
.size
): newsize
= img
.size
1864 # zoom up (if wanted)
1865 if ZoomMode
: newsize
=(2 * newsize
[0], 2 * newsize
[1])
1866 # skip processing if there was no change
1867 if newsize
== img
.size
: return img
1869 # select a nice filter and resize the image
1870 if newsize
> img
.size
:
1871 filter = Image
.BICUBIC
1873 filter = Image
.ANTIALIAS
1874 return img
.resize(newsize
, filter)
1877 # render a page to an OpenGL texture
1878 def PageImage(page
, ZoomMode
=False, RenderMode
=False):
1879 global OverviewNeedUpdate
1880 EnableCacheRead
= not(ZoomMode
or RenderMode
)
1881 EnableCacheWrite
= EnableCacheRead
and \
1882 (page
>= PageRangeStart
) and (page
<= PageRangeEnd
)
1884 # check for the image in the cache
1886 data
= GetCacheImage(page
)
1887 if data
: return data
1889 # if it's not in the temporary cache, render it
1892 # retrieve the image from the persistent cache or fully re-render it
1894 img
= GetPCacheImage(page
)
1898 if GetPageProp(page
, '_page'):
1899 img
= RenderPDF(page
, not(ZoomMode
), ZoomMode
)
1901 img
= LoadImage(page
, ZoomMode
)
1902 if EnableCacheWrite
:
1903 AddToPCache(page
, img
)
1905 # create black background image to paste real image onto
1907 TextureImage
= Image
.new('RGB', (2 * TexWidth
, 2 * TexHeight
))
1908 TextureImage
.paste(img
, ((2 * ScreenWidth
- img
.size
[0]) / 2, \
1909 (2 * ScreenHeight
- img
.size
[1]) / 2))
1911 TextureImage
= Image
.new('RGB', (TexWidth
, TexHeight
))
1912 x0
= (ScreenWidth
- img
.size
[0]) / 2
1913 y0
= (ScreenHeight
- img
.size
[1]) / 2
1914 TextureImage
.paste(img
, (x0
, y0
))
1915 SetPageProp(page
, '_box', (x0
, y0
, x0
+ img
.size
[0], y0
+ img
.size
[1]))
1918 # paste thumbnail into overview image
1919 if GetPageProp(page
, ('overview', '_overview'), True) \
1920 and (page
>= PageRangeStart
) and (page
<= PageRangeEnd
) \
1921 and not(GetPageProp(page
, '_overview_rendered')) \
1922 and not(RenderMode
):
1923 pos
= OverviewPos(OverviewPageMapInv
[page
])
1926 # first, fill the underlying area with black (i.e. remove the dummy logo)
1927 blackness
= Image
.new('RGB', (OverviewCellX
- OverviewBorder
, \
1928 OverviewCellY
- OverviewBorder
))
1929 OverviewImage
.paste(blackness
, (pos
[0] + OverviewBorder
/ 2, \
1930 pos
[1] + OverviewBorder
))
1932 # then, scale down the original image and paste it
1933 img
.thumbnail((OverviewCellX
- 2 * OverviewBorder
, \
1934 OverviewCellY
- 2 * OverviewBorder
), \
1936 OverviewImage
.paste(img
, \
1937 (pos
[0] + (OverviewCellX
- img
.size
[0]) / 2, \
1938 pos
[1] + (OverviewCellY
- img
.size
[1]) / 2))
1941 SetPageProp(page
, '_overview_rendered', True)
1942 OverviewNeedUpdate
= True
1945 # return texture data
1948 data
=TextureImage
.tostring()
1953 # finally add it back into the cache and return it
1954 if EnableCacheWrite
:
1955 AddToCache(page
, data
)
1958 # render a page to an OpenGL texture
1959 def RenderPage(page
, target
):
1960 glBindTexture(TextureTarget
,target
)
1962 glTexImage2D(TextureTarget
, 0, 3, TexWidth
, TexHeight
, 0,\
1963 GL_RGB
, GL_UNSIGNED_BYTE
, PageImage(page
))
1965 print >>sys
.stderr
, "I'm sorry, but your graphics card is not capable of rendering presentations"
1966 print >>sys
.stderr
, "in this resolution. Either the texture memory is exhausted, or there is no"
1967 print >>sys
.stderr
, "support for large textures (%dx%d). Please try to run Accentuate in a" % (TexWidth
, TexHeight
)
1968 print >>sys
.stderr
, "smaller resolution using the -g command-line option."
1971 # background rendering thread
1972 def RenderThread(p1
, p2
):
1973 global RTrunning
, RTrestart
1978 for pdf
in FileProps
:
1979 if not pdf
.lower().endswith(".pdf"): continue
1982 if RTrestart
: continue
1983 for page
in xrange(1, PageCount
+ 1):
1985 if (page
!= p1
) and (page
!= p2
) \
1986 and (page
>= PageRangeStart
) and (page
<= PageRangeEnd
):
1989 if CacheMode
>= FileCache
:
1990 print >>sys
.stderr
, "Background rendering finished, used %.1f MiB of disk space." %\
1991 (CacheFilePos
/ 1048576.0)
1994 ##### RENDER MODE ##############################################################
1997 global TexWidth
, TexHeight
1998 TexWidth
= ScreenWidth
1999 TexHeight
= ScreenHeight
2000 if os
.path
.exists(RenderToDirectory
):
2001 print >>sys
.stderr
, "Destination directory `%s' already exists," % RenderToDirectory
2002 print >>sys
.stderr
, "refusing to overwrite anything."
2005 os
.mkdir(RenderToDirectory
)
2007 print >>sys
.stderr
, "Cannot create destination directory `%s':" % RenderToDirectory
2008 print >>sys
.stderr
, e
.strerror
2010 print >>sys
.stderr
, "Rendering presentation into `%s'" % RenderToDirectory
2011 for page
in xrange(1, PageCount
+ 1):
2012 PageImage(page
, RenderMode
=True).save("%s/page%04d.png" % (RenderToDirectory
, page
))
2013 sys
.stdout
.write("[%d] " % page
)
2016 print >>sys
.stderr
, "Done."
2020 ##### INFO SCRIPT I/O ##########################################################
2022 # info script reader
2023 def LoadInfoScript():
2026 OldPageProps
= PageProps
2027 execfile(InfoScriptPath
, globals())
2028 NewPageProps
= PageProps
2029 PageProps
= OldPageProps
2031 for page
in NewPageProps
:
2032 for prop
in NewPageProps
[page
]:
2033 SetPageProp(page
, prop
, NewPageProps
[page
][prop
])
2038 print >>sys
.stderr
, "----- Exception in info script ----"
2039 traceback
.print_exc(file=sys
.stderr
)
2040 print >>sys
.stderr
, "----- End of traceback -----"
2042 # we can't save lamba expressions, so we need to warn the user
2043 # in every possible way
2044 ScriptTainted
= False
2045 LambdaWarning
= False
2046 def here_was_a_lambda_expression_that_could_not_be_saved():
2047 global LambdaWarning
2048 if not LambdaWarning
:
2049 print >>sys
.stderr
, "WARNING: The info script for the current file contained lambda expressions that"
2050 print >>sys
.stderr
, " were removed during the a save operation."
2051 LambdaWarning
= True
2053 # "clean" a PageProps entry so that only 'public' properties are left
2054 def GetPublicProps(props
):
2055 props
= props
.copy()
2056 # delete private (underscore) props
2057 for prop
in list(props
.keys()):
2058 if str(prop
)[0] == '_':
2060 # clean props to default values
2061 if props
.get('overview', False):
2062 del props
['overview']
2063 if not props
.get('skip', True):
2065 if ('boxes' in props
) and not(props
['boxes']):
2069 # Generate a string representation of a property value. Mainly this converts
2070 # classes or instances to the name of the class.
2071 def PropValueRepr(value
):
2072 global ScriptTainted
2073 if type(value
) == types
.FunctionType
:
2074 if value
.__name
__ != "<lambda>":
2075 return value
.__name
__
2076 if not ScriptTainted
:
2077 print >>sys
.stderr
, "WARNING: The info script contains lambda expressions, which cannot be saved"
2078 print >>sys
.stderr
, " back. The modifed script will be written into a separate file to"
2079 print >>sys
.stderr
, " minimize data loss."
2080 ScriptTainted
= True
2081 return "here_was_a_lambda_expression_that_could_not_be_saved"
2082 elif type(value
) == types
.ClassType
:
2083 return value
.__name
__
2084 elif type(value
) == types
.InstanceType
:
2085 return value
.__class
__.__name
__
2086 elif type(value
) == types
.DictType
:
2087 return "{ " + ", ".join([PropValueRepr(k
) + ": " + PropValueRepr(value
[k
]) for k
in value
]) + " }"
2091 # generate a nicely formatted string representation of a page's properties
2092 def SinglePagePropRepr(page
):
2093 props
= GetPublicProps(PageProps
[page
])
2094 if not props
: return None
2095 return "\n%3d: {%s\n }" % (page
, \
2096 ",".join(["\n " + repr(prop
) + ": " + PropValueRepr(props
[prop
]) for prop
in props
]))
2098 # generate a nicely formatted string representation of all page properties
2100 pages
= PageProps
.keys()
2102 return "PageProps = {%s\n}" % (",".join(filter(None, map(SinglePagePropRepr
, pages
))))
2104 # count the characters of a python dictionary source code, correctly handling
2105 # embedded strings and comments, and nested dictionaries
2106 def CountDictChars(s
, start
=0):
2109 for i
in xrange(start
, len(s
)):
2112 if c
== '{': level
+= 1
2113 if c
== '}': level
-= 1
2114 if c
== '#': context
= '#'
2115 if c
== '"': context
= '"'
2116 if c
== "'": context
= "'"
2117 elif context
[0] == "\\":
2119 elif context
== '#':
2120 if c
in "\r\n": context
= None
2121 elif context
== '"':
2122 if c
== "\\": context
= "\\\""
2123 if c
== '"': context
= None
2124 elif context
== "'":
2125 if c
== "\\": context
= "\\'"
2126 if c
== "'": context
= None
2127 if level
< 0: return i
2128 raise ValueError, "the dictionary never ends"
2130 # modify and save a file's info script
2131 def SaveInfoScript(filename
):
2132 # read the old info script
2134 f
= file(filename
, "r")
2140 script
= "# -*- coding: iso-8859-1 -*-\n"
2142 # replace the PageProps of the old info script with the current ones
2144 m
= re
.search("^.*(PageProps)\s*=\s*(\{).*$", script
,re
.MULTILINE
)
2146 script
= script
[:m
.start(1)] + PagePropRepr() + \
2147 script
[CountDictChars(script
, m
.end(2)) + 1 :]
2149 script
+= "\n" + PagePropRepr() + "\n"
2150 except (AttributeError, ValueError):
2154 filename
+= ".modified"
2156 # write the script back
2158 f
= file(filename
, "w")
2162 print >>sys
.stderr
, "Oops! Could not write info script!"
2165 ##### OPENGL RENDERING #########################################################
2169 reltime
= pygame
.time
.get_ticks() - StartTime
2170 if EstimatedDuration
and (OverviewMode
or GetPageProp(Pcurrent
, 'progress', True)):
2171 rel
= (0.001 * reltime
) / EstimatedDuration
2172 x
= int(ScreenWidth
* rel
)
2173 y
= 1.0 - ProgressBarSize
* PixelX
2174 a
= min(255, max(0, x
- ScreenWidth
))
2175 b
= min(255, max(0, x
- ScreenWidth
- 256))
2179 glDisable(TextureTarget
)
2180 glDisable(GL_TEXTURE_2D
)
2182 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
2184 glColor4ub(r
, g
, b
, 0)
2187 glColor4ub(r
, g
, b
, ProgressBarAlpha
)
2188 glVertex2d(rel
, 1.0)
2193 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
)
2196 DrawOSDEx(OSDTimePos
, FormatTime(t
, MinutesOnly
))
2197 if CurrentOSDComment
and (OverviewMode
or not(TransitionRunning
)):
2198 DrawOSD(ScreenWidth
/2, \
2199 ScreenHeight
- 3*OSDMargin
- FontSize
, \
2200 CurrentOSDComment
, Center
, Up
)
2201 if CursorImage
and CursorVisible
:
2202 x
, y
= pygame
.mouse
.get_pos()
2203 x
-= CursorHotspot
[0]
2204 y
-= CursorHotspot
[1]
2209 glDisable(TextureTarget
)
2210 glEnable(GL_TEXTURE_2D
)
2211 glBindTexture(GL_TEXTURE_2D
, CursorTexture
)
2213 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
2214 glColor4ub(255, 255, 255, 255)
2216 glTexCoord2d(0.0, 0.0); glVertex2d(X0
, Y0
)
2217 glTexCoord2d(CursorTX
, 0.0); glVertex2d(X1
, Y0
)
2218 glTexCoord2d(CursorTX
, CursorTY
); glVertex2d(X1
, Y1
)
2219 glTexCoord2d(0.0, CursorTY
); glVertex2d(X0
, Y1
)
2222 glDisable(GL_TEXTURE_2D
)
2224 # draw the complete image of the current page
2225 def DrawCurrentPage(dark
=1.0, do_flip
=True):
2226 if VideoPlaying
: return
2227 boxes
= GetPageProp(Pcurrent
, 'boxes')
2228 glClear(GL_COLOR_BUFFER_BIT
)
2230 # pre-transform for zoom
2232 glOrtho(ZoomX0
, ZoomX0
+ ZoomArea
, ZoomY0
+ ZoomArea
, ZoomY0
, -10.0, 10.0)
2234 # background layer -- the page's image, darkened if it has boxes
2236 glEnable(TextureTarget
)
2237 glBindTexture(TextureTarget
, Tcurrent
)
2238 if boxes
or Tracing
:
2239 light
= 1.0 - 0.25 * dark
2242 glColor3d(light
, light
, light
)
2245 if boxes
or Tracing
:
2246 # alpha-blend the same image some times to blur it
2248 DrawTranslatedFullQuad(+PixelX
* ZoomArea
, 0.0, light
, dark
/ 2)
2249 DrawTranslatedFullQuad(-PixelX
* ZoomArea
, 0.0, light
, dark
/ 3)
2250 DrawTranslatedFullQuad(0.0, +PixelY
* ZoomArea
, light
, dark
/ 4)
2251 DrawTranslatedFullQuad(0.0, -PixelY
* ZoomArea
, light
, dark
/ 5)
2254 # draw outer box fade
2256 for X0
, Y0
, X1
, Y1
in boxes
:
2257 glBegin(GL_QUAD_STRIP
)
2258 DrawPointEx(X0
, Y0
, 1); DrawPointEx(X0
- EdgeX
, Y0
- EdgeY
, 0)
2259 DrawPointEx(X1
, Y0
, 1); DrawPointEx(X1
+ EdgeX
, Y0
- EdgeY
, 0)
2260 DrawPointEx(X1
, Y1
, 1); DrawPointEx(X1
+ EdgeX
, Y1
+ EdgeY
, 0)
2261 DrawPointEx(X0
, Y1
, 1); DrawPointEx(X0
- EdgeX
, Y1
+ EdgeY
, 0)
2262 DrawPointEx(X0
, Y0
, 1); DrawPointEx(X0
- EdgeX
, Y0
- EdgeY
, 0)
2268 for X0
, Y0
, X1
, Y1
in boxes
:
2276 x
, y
= MouseToScreen(pygame
.mouse
.get_pos())
2279 glBegin(GL_TRIANGLE_STRIP
)
2280 for x0
, y0
, x1
, y1
in SpotMesh
:
2281 DrawPointEx(x
+ x0
, y
+ y0
, 1)
2282 DrawPointEx(x
+ x1
, y
+ y1
, 0)
2286 glBegin(GL_TRIANGLE_FAN
)
2288 for x0
, y0
, x1
, y1
in SpotMesh
:
2289 DrawPoint(x
+ x0
, y
+ y0
)
2293 # soft alpha-blended rectangle
2294 glDisable(TextureTarget
)
2295 glColor4d(*MarkColor
)
2298 glVertex2d(MarkUL
[0], MarkUL
[1])
2299 glVertex2d(MarkLR
[0], MarkUL
[1])
2300 glVertex2d(MarkLR
[0], MarkLR
[1])
2301 glVertex2d(MarkUL
[0], MarkLR
[1])
2305 glBegin(GL_LINE_STRIP
)
2306 glVertex2d(MarkUL
[0], MarkUL
[1])
2307 glVertex2d(MarkLR
[0], MarkUL
[1])
2308 glVertex2d(MarkLR
[0], MarkLR
[1])
2309 glVertex2d(MarkUL
[0], MarkLR
[1])
2310 glVertex2d(MarkUL
[0], MarkUL
[1])
2312 glEnable(TextureTarget
)
2314 # unapply the zoom transform
2316 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
2321 pygame
.display
.flip()
2323 # draw a black screen with the Accentuate logo at the center
2325 glClear(GL_COLOR_BUFFER_BIT
)
2326 glColor3ub(255, 255, 255)
2327 if TextureTarget
!= GL_TEXTURE_2D
:
2328 glDisable(TextureTarget
)
2329 glEnable(GL_TEXTURE_2D
)
2330 glBindTexture(GL_TEXTURE_2D
, LogoTexture
)
2332 glTexCoord2d(0, 0); glVertex2d(0.5 - 128.0 / ScreenWidth
, 0.5 - 32.0 / ScreenHeight
)
2333 glTexCoord2d(1, 0); glVertex2d(0.5 + 128.0 / ScreenWidth
, 0.5 - 32.0 / ScreenHeight
)
2334 glTexCoord2d(1, 1); glVertex2d(0.5 + 128.0 / ScreenWidth
, 0.5 + 32.0 / ScreenHeight
)
2335 glTexCoord2d(0, 1); glVertex2d(0.5 - 128.0 / ScreenWidth
, 0.5 + 32.0 / ScreenHeight
)
2338 OSDFont
.Draw((ScreenWidth
/ 2, ScreenHeight
/ 2 + 48), \
2339 __version__
, align
=Center
, alpha
=0.25)
2340 glDisable(GL_TEXTURE_2D
)
2342 # draw the prerender progress bar
2343 def DrawProgress(position
):
2344 glDisable(TextureTarget
)
2347 x1
= position
* x2
+ (1.0 - position
) * x0
2349 y0
= y1
- 16.0 / ScreenHeight
2351 glColor3ub( 64, 64, 64); glVertex2d(x0
, y0
); glVertex2d(x2
, y0
)
2352 glColor3ub(128, 128, 128); glVertex2d(x2
, y1
); glVertex2d(x0
, y1
)
2353 glColor3ub( 64, 128, 255); glVertex2d(x0
, y0
); glVertex2d(x1
, y0
)
2354 glColor3ub( 8, 32, 128); glVertex2d(x1
, y1
); glVertex2d(x0
, y1
)
2356 glEnable(TextureTarget
)
2359 def DrawFadeMode(intensity
, alpha
):
2360 if VideoPlaying
: return
2361 DrawCurrentPage(do_flip
=False)
2362 glDisable(TextureTarget
)
2364 glColor4d(intensity
, intensity
, intensity
, alpha
)
2366 glEnable(TextureTarget
)
2367 pygame
.display
.flip()
2369 def FadeMode(intensity
):
2370 t0
= pygame
.time
.get_ticks()
2372 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2373 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BlankFadeDuration
2375 DrawFadeMode(intensity
, t
)
2376 DrawFadeMode(intensity
, 1.0)
2379 event
= pygame
.event
.wait()
2380 if event
.type == QUIT
:
2383 elif event
.type == VIDEOEXPOSE
:
2384 DrawFadeMode(intensity
, 1.0)
2385 elif event
.type == MOUSEBUTTONUP
:
2387 elif event
.type == KEYDOWN
:
2388 if event
.unicode == u
'q':
2389 pygame
.event
.post(pygame
.event
.Event(QUIT
))
2393 t0
= pygame
.time
.get_ticks()
2395 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2396 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BlankFadeDuration
2398 DrawFadeMode(intensity
, 1.0 - t
)
2402 def SetGamma(new_gamma
=None, new_black
=None, force
=False):
2403 global Gamma
, BlackLevel
2404 if new_gamma
is None: new_gamma
= Gamma
2405 if new_gamma
< 0.1: new_gamma
= 0.1
2406 if new_gamma
> 10.0: new_gamma
= 10.0
2407 if new_black
is None: new_black
= BlackLevel
2408 if new_black
< 0: new_black
= 0
2409 if new_black
> 254: new_black
= 254
2410 if not(force
) and (abs(Gamma
- new_gamma
) < 0.01) and (new_black
== BlackLevel
):
2413 BlackLevel
= new_black
2414 scale
= 1.0 / (255 - BlackLevel
)
2416 ramp
= [int(65535.0 * ((max(0, x
- BlackLevel
) * scale
) ** power
)) for x
in range(256)]
2417 return pygame
.display
.set_gamma_ramp(ramp
, ramp
, ramp
)
2420 def PrepareCustomCursor(cimg
):
2421 global CursorTexture
, CursorSX
, CursorSY
, CursorTX
, CursorTY
2423 tw
, th
= map(npot
, cimg
.size
)
2424 if (tw
> 256) or (th
> 256):
2425 print >>sys
.stderr
, "Custom cursor is rediculously large, reverting to normal one."
2427 img
= Image
.new('RGBA', (tw
, th
))
2428 img
.paste(cimg
, (0, 0))
2429 CursorTexture
= glGenTextures(1)
2430 glBindTexture(GL_TEXTURE_2D
, CursorTexture
)
2431 glTexImage2D(GL_TEXTURE_2D
, 0, GL_RGBA
, tw
, th
, 0, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.tostring())
2432 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
)
2433 CursorSX
= w
* PixelX
2434 CursorSY
= h
* PixelY
2435 CursorTX
= w
/ float(tw
)
2436 CursorTY
= h
/ float(th
)
2440 ##### CONTROL AND NAVIGATION ###################################################
2442 # update the applications' title bar
2443 def UpdateCaption(page
=0, force
=False):
2444 global CurrentCaption
, CurrentOSDCaption
, CurrentOSDPage
, CurrentOSDStatus
2445 global CurrentOSDComment
2446 if (page
== CurrentCaption
) and not(force
):
2448 CurrentCaption
= page
2451 caption
+= " - " + DocumentTitle
2453 CurrentOSDCaption
= ""
2455 CurrentOSDStatus
= ""
2456 CurrentOSDComment
= ""
2457 pygame
.display
.set_caption(caption
, __title__
)
2459 CurrentOSDPage
= "%d/%d" % (page
, PageCount
)
2460 caption
= "%s (%s)" % (caption
, CurrentOSDPage
)
2461 title
= GetPageProp(page
, 'title') or GetPageProp(page
, '_title')
2463 caption
+= ": %s" % title
2464 CurrentOSDCaption
= title
2466 CurrentOSDCaption
= ""
2468 if GetPageProp(page
, 'skip', False):
2469 status
.append("skipped: yes")
2470 if not GetPageProp(page
, ('overview', '_overview'), True):
2471 status
.append("on overview page: no")
2472 CurrentOSDStatus
= ", ".join(status
)
2473 CurrentOSDComment
= GetPageProp(page
, 'comment')
2474 pygame
.display
.set_caption(caption
, __title__
)
2476 # get next/previous page
2477 def GetNextPage(page
, direction
, keypressed
=0):
2480 try_page
+= direction
2481 if try_page
== page
:
2482 return 0 # tried all pages, but none found
2484 if try_page
< 1: try_page
= PageCount
2485 if try_page
> PageCount
: try_page
= 1
2486 elif (FadeToBlackAtEnd
and keypressed
):
2487 if try_page
> PageCount
:
2490 if try_page
< 1 or try_page
> PageCount
:
2491 return 0 # start or end of presentation
2492 if not GetPageProp(try_page
, 'skip', False):
2495 # pre-load the following page into Pnext/Tnext
2496 def PreloadNextPage(page
):
2498 if (page
< 1) or (page
> PageCount
):
2503 RenderPage(page
, Tnext
)
2507 # perform box fading; the fade animation time is mapped through func()
2509 t0
= pygame
.time
.get_ticks()
2511 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2512 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BoxFadeDuration
2514 DrawCurrentPage(func(t
))
2515 DrawCurrentPage(func(1.0))
2520 global StartTime
, PageEnterTime
2521 if TimeTracking
and not(FirstPage
):
2522 print "--- timer was reset here ---"
2523 StartTime
= pygame
.time
.get_ticks()
2526 # start video playback
2527 def PlayVideo(video
):
2528 global MPlayerPID
, VideoPlaying
2529 if not video
: return
2532 MPlayerPID
= spawn(os
.P_NOWAIT
, \
2533 MPlayerPath
, [MPlayerPath
, "-quiet", \
2534 "-monitorpixelaspect", "1:1", "-autosync", "100"] + \
2535 MPlayerPlatformOptions
+ [ "-slave", \
2536 "-wid", str(pygame
.display
.get_wm_info()['window']), \
2537 FileNameEscape
+ video
+ FileNameEscape
])
2539 glClear(GL_COLOR_BUFFER_BIT
)
2540 pygame
.display
.flip()
2545 # called each time a page is entered
2546 def PageEntered(update_time
=True):
2547 global PageEnterTime
, MPlayerPID
, IsZoomed
, WantStatus
2549 PageEnterTime
= pygame
.time
.get_ticks() - StartTime
2550 IsZoomed
= False # no, we don't have a pre-zoomed image right now
2551 WantStatus
= False # don't show status unless it's changed interactively
2552 timeout
= AutoAdvance
2553 shown
= GetPageProp(Pcurrent
, '_shown', 0)
2555 timeout
= GetPageProp(Pcurrent
, 'timeout', timeout
)
2556 video
= GetPageProp(Pcurrent
, 'video')
2557 sound
= GetPageProp(Pcurrent
, 'sound')
2559 if sound
and not(video
):
2562 MPlayerPID
= spawn(os
.P_NOWAIT
, \
2563 MPlayerPath
, [MPlayerPath
, "-quiet", "-really-quiet", \
2564 FileNameEscape
+ sound
+ FileNameEscape
])
2567 SafeCall(GetPageProp(Pcurrent
, 'OnEnterOnce'))
2568 SafeCall(GetPageProp(Pcurrent
, 'OnEnter'))
2569 if timeout
: pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, timeout
)
2570 SetPageProp(Pcurrent
, '_shown', shown
+ 1)
2572 # called each time a page is left
2573 def PageLeft(overview
=False):
2574 global FirstPage
, LastPage
, WantStatus
2577 if GetTristatePageProp(Pcurrent
, 'reset'):
2581 if GetPageProp(Pcurrent
, '_shown', 0) == 1:
2582 SafeCall(GetPageProp(Pcurrent
, 'OnLeaveOnce'))
2583 SafeCall(GetPageProp(Pcurrent
, 'OnLeave'))
2585 t1
= pygame
.time
.get_ticks() - StartTime
2586 dt
= (t1
- PageEnterTime
+ 500) / 1000
2590 p
= "%4d" % Pcurrent
2591 print "%s%9s%9s%9s" % (p
, FormatTime(dt
), \
2592 FormatTime(PageEnterTime
/ 1000), \
2593 FormatTime(t1
/ 1000))
2595 # perform a transition to a specified page
2596 def TransitionTo(page
):
2597 global Pcurrent
, Pnext
, Tcurrent
, Tnext
2598 global PageCount
, Marking
, Tracing
, Panning
, TransitionRunning
2600 # first, stop the auto-timer
2601 pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, 0)
2603 # invalid page? go away
2604 if not PreloadNextPage(page
):
2607 # notify that the page has been left
2611 if GetPageProp(Pcurrent
, 'boxes') or Tracing
:
2612 skip
= BoxFade(lambda t
: 1.0 - t
)
2621 # check if the transition is valid
2622 tpage
= min(Pcurrent
, Pnext
)
2623 if 'transition' in PageProps
[tpage
]:
2626 tkey
= '_transition'
2627 trans
= PageProps
[tpage
][tkey
]
2631 transtime
= GetPageProp(tpage
, 'transtime', TransitionDuration
)
2633 dummy
= trans
.__class
__
2634 except AttributeError:
2635 # ah, gotcha! the transition is not yet intantiated!
2637 PageProps
[tpage
][tkey
] = trans
2639 # backward motion? then swap page buffers now
2640 backward
= (Pnext
< Pcurrent
)
2642 Pcurrent
, Pnext
= (Pnext
, Pcurrent
)
2643 Tcurrent
, Tnext
= (Tnext
, Tcurrent
)
2645 # transition animation
2646 if not(skip
) and transtime
:
2647 transtime
= 1.0 / transtime
2648 TransitionRunning
= True
2649 t0
= pygame
.time
.get_ticks()
2650 while not(VideoPlaying
):
2651 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]):
2654 t
= (pygame
.time
.get_ticks() - t0
) * transtime
2656 if backward
: t
= 1.0 - t
2657 glEnable(TextureTarget
)
2660 pygame
.display
.flip()
2661 TransitionRunning
= False
2663 # forward motion => swap page buffers now
2665 Pcurrent
, Pnext
= (Pnext
, Pcurrent
)
2666 Tcurrent
, Tnext
= (Tnext
, Tcurrent
)
2669 if not(skip
) and GetPageProp(Pcurrent
, 'boxes'): BoxFade(lambda t
: t
)
2671 # finally update the screen and preload the next page
2672 DrawCurrentPage() # I do that twice because for some strange reason, the
2674 if not PreloadNextPage(GetNextPage(Pcurrent
, 1)):
2675 PreloadNextPage(GetNextPage(Pcurrent
, -1))
2678 # zoom mode animation
2679 def ZoomAnimation(targetx
, targety
, func
):
2680 global ZoomX0
, ZoomY0
, ZoomArea
2681 t0
= pygame
.time
.get_ticks()
2683 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2684 t
= (pygame
.time
.get_ticks() - t0
)* 1.0 / ZoomDuration
2688 ZoomX0
= targetx
* t
2689 ZoomY0
= targety
* t
2690 ZoomArea
= 1.0 - 0.5 * t
2693 ZoomX0
= targetx
* t
2694 ZoomY0
= targety
* t
2695 ZoomArea
= 1.0 - 0.5 * t
2700 def EnterZoomMode(targetx
, targety
):
2701 global ZoomMode
, IsZoomed
, ZoomWarningIssued
2702 ZoomAnimation(targetx
, targety
, lambda t
: t
)
2704 if TextureTarget
!= GL_TEXTURE_2D
:
2705 if not ZoomWarningIssued
:
2706 print >>sys
.stderr
, "Sorry, but I can't increase the detail level in zoom mode any further when"
2707 print >>sys
.stderr
, "GL_ARB_texture_rectangle is used. Please try running Accentuate with the"
2708 print >>sys
.stderr
, "'-e' parameter. If a modern nVidia or ATI graphics card is used, a driver"
2709 print >>sys
.stderr
, "update may also fix the problem."
2710 ZoomWarningIssued
= True
2713 glBindTexture(TextureTarget
, Tcurrent
)
2715 glTexImage2D(TextureTarget
, 0, 3, TexWidth
* 2, TexHeight
* 2, 0, \
2716 GL_RGB
, GL_UNSIGNED_BYTE
, PageImage(Pcurrent
, True))
2718 if not ZoomWarningIssued
:
2719 print >>sys
.stderr
, "Sorry, but I can't increase the detail level in zoom mode any further, because"
2720 print >>sys
.stderr
, "your OpenGL implementation does not support that. Either the texture memory is"
2721 print >>sys
.stderr
, "exhausted, or there is no support for large textures (%dx%d). If you really" % (TexWidth
* 2, TexHeight
* 2)
2722 print >>sys
.stderr
, "need high-res zooming, please try to run Accentuate in a smaller resolution"
2723 print >>sys
.stderr
, "using the -g command-line option."
2724 ZoomWarningIssued
= True
2729 # leave zoom mode (if enabled)
2730 def LeaveZoomMode():
2732 if not ZoomMode
: return
2733 ZoomAnimation(ZoomX0
, ZoomY0
, lambda t
: 1.0 - t
)
2737 # increment/decrement spot radius
2738 def IncrementSpotSize(delta
):
2742 SpotRadius
= max(SpotRadius
+ delta
, 8)
2746 # post-initialize the page transitions
2747 def PrepareTransitions():
2748 Unspecified
= 0xAFFED00F
2749 # STEP 1: randomly assign transitions where the user didn't specify them
2750 cnt
= sum([1 for page
in xrange(1, PageCount
+ 1) \
2751 if GetPageProp(page
, 'transition', Unspecified
) == Unspecified
])
2752 newtrans
= ((cnt
/ len(AvailableTransitions
) + 1) * AvailableTransitions
)[:cnt
]
2753 random
.shuffle(newtrans
)
2754 for page
in xrange(1, PageCount
+ 1):
2755 if GetPageProp(page
, 'transition', Unspecified
) == Unspecified
:
2756 SetPageProp(page
, '_transition', newtrans
.pop())
2757 # STEP 2: instantiate transitions
2758 for page
in PageProps
:
2759 for key
in ('transition', '_transition'):
2760 if not key
in PageProps
[page
]:
2762 trans
= PageProps
[page
][key
]
2763 if trans
is not None:
2764 PageProps
[page
][key
] = trans()
2766 # update timer values and screen timer
2768 global CurrentTime
, ProgressBarPos
2770 newtime
= (pygame
.time
.get_ticks() - StartTime
) * 0.001
2771 if EstimatedDuration
:
2772 newpos
= int(ScreenWidth
* newtime
/ EstimatedDuration
)
2773 if newpos
!= ProgressBarPos
:
2775 ProgressBarPos
= newpos
2776 newtime
= int(newtime
)
2777 if TimeDisplay
and (CurrentTime
!= newtime
):
2779 CurrentTime
= newtime
2782 # set cursor visibility
2783 def SetCursor(visible
):
2784 global CursorVisible
2785 CursorVisible
= visible
2787 pygame
.mouse
.set_visible(visible
)
2790 def IsValidShortcutKey(key
):
2791 return ((key
>= K_a
) and (key
<= K_z
)) \
2792 or ((key
>= K_0
) and (key
<= K_9
)) \
2793 or ((key
>= K_F1
) and (key
<= K_F12
))
2794 def FindShortcut(shortcut
):
2795 for page
, props
in PageProps
.iteritems():
2797 check
= props
['shortcut']
2798 if type(check
) != types
.StringType
:
2800 elif (len(check
) > 1) and (check
[0] in "Ff"):
2801 check
= K_F1
- 1 + int(check
[1:])
2803 check
= ord(check
.lower())
2804 except (KeyError, TypeError, ValueError):
2806 if check
== shortcut
:
2809 def AssignShortcut(page
, key
):
2810 old_page
= FindShortcut(key
)
2812 del PageProps
[old_page
]['shortcut']
2815 elif (key
>= K_F1
) and (key
<= K_F15
):
2816 shortcut
= "F%d" % (key
- K_F1
+ 1)
2819 SetPageProp(page
, 'shortcut', shortcut
)
2822 ##### OVERVIEW MODE ############################################################
2824 def UpdateOverviewTexture():
2825 global OverviewNeedUpdate
2826 glBindTexture(TextureTarget
, Tnext
)
2829 glTexImage2D(TextureTarget
, 0, 3, TexWidth
, TexHeight
, 0, \
2830 GL_RGB
, GL_UNSIGNED_BYTE
, OverviewImage
.tostring())
2833 OverviewNeedUpdate
= False
2835 # draw the overview page
2837 if VideoPlaying
: return
2838 glClear(GL_COLOR_BUFFER_BIT
)
2840 glEnable(TextureTarget
)
2841 glBindTexture(TextureTarget
, Tnext
)
2842 glColor3ub(192, 192, 192)
2845 pos
= OverviewPos(OverviewSelection
)
2846 X0
= PixelX
* pos
[0]
2847 Y0
= PixelY
* pos
[1]
2848 X1
= PixelX
* (pos
[0] + OverviewCellX
)
2849 Y1
= PixelY
* (pos
[1] + OverviewCellY
)
2850 glColor3d(1.0, 1.0, 1.0)
2858 DrawOSDEx(OSDTitlePos
, CurrentOSDCaption
)
2859 DrawOSDEx(OSDPagePos
, CurrentOSDPage
)
2860 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
)
2862 pygame
.display
.flip()
2864 # overview zoom effect, time mapped through func
2865 def OverviewZoom(func
):
2866 global TransitionRunning
2867 pos
= OverviewPos(OverviewSelection
)
2868 X0
= PixelX
* (pos
[0] + OverviewBorder
)
2869 Y0
= PixelY
* (pos
[1] + OverviewBorder
)
2870 X1
= PixelX
* (pos
[0] - OverviewBorder
+ OverviewCellX
)
2871 Y1
= PixelY
* (pos
[1] - OverviewBorder
+ OverviewCellY
)
2873 TransitionRunning
= True
2874 t0
= pygame
.time
.get_ticks()
2875 while not(VideoPlaying
):
2876 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / ZoomDuration
2882 zoom
= (t
* (X1
- X0
) + t1
) / (X1
- X0
)
2883 OX
= zoom
* (t
* X0
- X0
) - (zoom
- 1.0) * t
* X0
2884 OY
= zoom
* (t
* Y0
- Y0
) - (zoom
- 1.0) * t
* Y0
2885 OX
= t
* X0
- zoom
* X0
2886 OY
= t
* Y0
- zoom
* Y0
2889 glEnable(TextureTarget
)
2890 glBindTexture(TextureTarget
, Tnext
)
2892 glColor3ub(192, 192, 192)
2893 glTexCoord2d( 0.0, 0.0); glVertex2d(OX
, OY
)
2894 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(OX
+ zoom
, OY
)
2895 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(OX
+ zoom
, OY
+ zoom
)
2896 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(OX
, OY
+ zoom
)
2897 glColor3ub(255, 255, 255)
2898 glTexCoord2d(X0
* TexMaxS
, Y0
* TexMaxT
); glVertex2d(OX
+ X0
*zoom
, OY
+ Y0
* zoom
)
2899 glTexCoord2d(X1
* TexMaxS
, Y0
* TexMaxT
); glVertex2d(OX
+ X1
*zoom
, OY
+ Y0
* zoom
)
2900 glTexCoord2d(X1
* TexMaxS
, Y1
* TexMaxT
); glVertex2d(OX
+ X1
*zoom
, OY
+ Y1
* zoom
)
2901 glTexCoord2d(X0
* TexMaxS
, Y1
* TexMaxT
); glVertex2d(OX
+ X0
*zoom
, OY
+ Y1
* zoom
)
2905 glBindTexture(TextureTarget
, Tcurrent
)
2906 glColor4d(1.0, 1.0, 1.0, 1.0 - t
* t
* t
)
2908 glTexCoord2d( 0.0, 0.0); glVertex2d(t
* X0
, t
* Y0
)
2909 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(t
* X1
+ t1
, t
* Y0
)
2910 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(t
* X1
+ t1
, t
* Y1
+ t1
)
2911 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(t
* X0
, t
* Y1
+ t1
)
2914 DrawOSDEx(OSDTitlePos
, CurrentOSDCaption
, alpha_factor
=t
)
2915 DrawOSDEx(OSDPagePos
, CurrentOSDPage
, alpha_factor
=t
)
2916 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
, alpha_factor
=t
)
2918 pygame
.display
.flip()
2919 TransitionRunning
= False
2921 # overview keyboard navigation
2922 def OverviewKeyboardNav(delta
):
2923 global OverviewSelection
2924 dest
= OverviewSelection
+ delta
2925 if (dest
>= OverviewPageCount
) or (dest
< 0):
2927 OverviewSelection
= dest
2928 x
, y
= OverviewPos(OverviewSelection
)
2929 pygame
.mouse
.set_pos((x
+ (OverviewCellX
/ 2), y
+ (OverviewCellY
/ 2)))
2931 # overview mode PageProp toggle
2932 def OverviewTogglePageProp(prop
, default
):
2933 if (OverviewSelection
< 0) or (OverviewSelection
>= len(OverviewPageMap
)):
2935 page
= OverviewPageMap
[OverviewSelection
]
2936 SetPageProp(page
, prop
, not(GetPageProp(page
, prop
, default
)))
2937 UpdateCaption(page
, force
=True)
2940 # overview event handler
2941 def HandleOverviewEvent(event
):
2942 global OverviewSelection
, TimeDisplay
2944 if event
.type == QUIT
:
2945 PageLeft(overview
=True)
2947 elif event
.type == VIDEOEXPOSE
:
2950 elif event
.type == KEYDOWN
:
2951 if (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
2952 pygame
.event
.post(pygame
.event
.Event(QUIT
))
2953 elif event
.unicode == u
'f':
2954 SetFullscreen(not Fullscreen
)
2955 elif event
.unicode == u
't':
2956 TimeDisplay
= not(TimeDisplay
)
2958 elif event
.unicode == u
'r':
2960 if TimeDisplay
: DrawOverview()
2961 elif event
.unicode == u
's':
2962 SaveInfoScript(InfoScriptPath
)
2963 elif event
.unicode == u
'o':
2964 OverviewTogglePageProp('overview', GetPageProp(Pcurrent
, '_overview', True))
2965 elif event
.unicode == u
'i':
2966 OverviewTogglePageProp('skip', False)
2967 elif event
.key
== K_UP
: OverviewKeyboardNav(-OverviewGridSize
)
2968 elif event
.key
== K_LEFT
: OverviewKeyboardNav(-1)
2969 elif event
.key
== K_RIGHT
: OverviewKeyboardNav(+1)
2970 elif event
.key
== K_DOWN
: OverviewKeyboardNav(+OverviewGridSize
)
2971 elif event
.key
== K_TAB
:
2972 OverviewSelection
= -1
2974 elif event
.key
in (K_RETURN
, K_KP_ENTER
):
2976 elif IsValidShortcutKey(event
.key
):
2977 if event
.mod
& KMOD_SHIFT
:
2979 AssignShortcut(OverviewPageMap
[OverviewSelection
], event
.key
)
2981 pass # no valid page selected
2984 page
= FindShortcut(event
.key
)
2986 OverviewSelection
= OverviewPageMapInv
[page
]
2987 x
, y
= OverviewPos(OverviewSelection
)
2988 pygame
.mouse
.set_pos((x
+ (OverviewCellX
/ 2), \
2989 y
+ (OverviewCellY
/ 2)))
2992 elif event
.type == MOUSEBUTTONUP
:
2993 if event
.button
== 1:
2995 elif event
.button
in (2, 3):
2996 OverviewSelection
= -1
2999 elif event
.type == MOUSEMOTION
:
3000 pygame
.event
.clear(MOUSEMOTION
)
3001 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3003 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3005 # determine highlighted page
3006 OverviewSelection
= \
3007 int((event
.pos
[0] - OverviewOfsX
) / OverviewCellX
) + \
3008 int((event
.pos
[1] - OverviewOfsY
) / OverviewCellY
) * OverviewGridSize
3009 if (OverviewSelection
< 0) or (OverviewSelection
>= len(OverviewPageMap
)):
3012 UpdateCaption(OverviewPageMap
[OverviewSelection
])
3015 elif event
.type == USEREVENT_HIDE_MOUSE
:
3016 # mouse timer event -> hide fullscreen cursor
3017 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3023 # overview mode entry/loop/exit function
3025 global Pcurrent
, Pnext
, Tcurrent
, Tnext
, Tracing
, OverviewSelection
3026 global PageEnterTime
, OverviewMode
3028 pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, 0)
3030 UpdateOverviewTexture()
3032 if GetPageProp(Pcurrent
, 'boxes') or Tracing
:
3033 BoxFade(lambda t
: 1.0 - t
)
3035 OverviewSelection
= OverviewPageMapInv
[Pcurrent
]
3038 OverviewZoom(lambda t
: 1.0 - t
)
3040 PageEnterTime
= pygame
.time
.get_ticks() - StartTime
3042 event
= pygame
.event
.poll()
3043 if event
.type == NOEVENT
:
3044 force_update
= OverviewNeedUpdate
3045 if OverviewNeedUpdate
:
3046 UpdateOverviewTexture()
3047 if TimerTick() or force_update
:
3049 pygame
.time
.wait(20)
3050 elif not HandleOverviewEvent(event
):
3052 PageLeft(overview
=True)
3054 if (OverviewSelection
< 0) or (OverviewSelection
>= OverviewPageCount
):
3055 OverviewSelection
= OverviewPageMapInv
[Pcurrent
]
3058 Pnext
= OverviewPageMap
[OverviewSelection
]
3059 if Pnext
!= Pcurrent
:
3061 RenderPage(Pcurrent
, Tcurrent
)
3062 UpdateCaption(Pcurrent
)
3063 OverviewZoom(lambda t
: t
)
3064 OverviewMode
= False
3067 if GetPageProp(Pcurrent
, 'boxes'):
3068 BoxFade(lambda t
: t
)
3070 if not PreloadNextPage(GetNextPage(Pcurrent
, 1)):
3071 PreloadNextPage(GetNextPage(Pcurrent
, -1))
3074 ##### EVENT HANDLING ###########################################################
3076 # set fullscreen mode
3077 def SetFullscreen(fs
, do_init
=True):
3080 # let pygame do the real work
3082 if fs
== Fullscreen
: return
3083 if not pygame
.display
.toggle_fullscreen(): return
3086 # redraw the current page (pygame is too lazy to send an expose event ...)
3089 # show cursor and set auto-hide timer
3091 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3093 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3097 def TogglePageProp(prop
, default
):
3099 SetPageProp(Pcurrent
, prop
, not(GetPageProp(Pcurrent
, prop
, default
)))
3100 UpdateCaption(Pcurrent
, force
=True)
3104 # main event handling function
3105 def HandleEvent(event
):
3106 global HaveMark
, ZoomMode
, Marking
, Tracing
, Panning
, SpotRadius
, FileStats
3107 global MarkUL
, MarkLR
, MouseDownX
, MouseDownY
, PanAnchorX
, PanAnchorY
3108 global ZoomX0
, ZoomY0
, RTrunning
, RTrestart
, StartTime
, PageEnterTime
3109 global CurrentTime
, TimeDisplay
, TimeTracking
, ProgressBarPos
3111 if event
.type == QUIT
:
3114 elif event
.type == VIDEOEXPOSE
:
3117 elif event
.type == KEYDOWN
:
3121 elif (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
3122 pygame
.event
.post(pygame
.event
.Event(QUIT
))
3123 elif event
.unicode == u
'f':
3124 SetFullscreen(not Fullscreen
)
3125 elif (event
.key
== K_TAB
) and (event
.mod
& KMOD_ALT
) and Fullscreen
:
3126 SetFullscreen(False)
3127 elif event
.unicode == u
's':
3128 SaveInfoScript(InfoScriptPath
)
3129 elif event
.unicode == u
'z': # handle QWERTY and QWERTZ keyboards
3133 tx
, ty
= MouseToScreen(pygame
.mouse
.get_pos())
3134 EnterZoomMode(0.5 * tx
, 0.5 * ty
)
3135 elif event
.unicode == u
'b':
3137 elif event
.unicode == u
'w':
3139 elif event
.unicode == u
't':
3140 TimeDisplay
= not(TimeDisplay
)
3142 if TimeDisplay
and not(TimeTracking
) and FirstPage
:
3143 print >>sys
.stderr
, "Time tracking mode enabled."
3145 print "page duration enter leave"
3146 print "---- -------- -------- --------"
3147 elif event
.unicode == u
'r':
3149 if TimeDisplay
: DrawCurrentPage()
3150 elif event
.unicode == u
'l':
3151 TransitionTo(LastPage
)
3152 elif event
.unicode == u
'o':
3153 TogglePageProp('overview', GetPageProp(Pcurrent
, '_overview', True))
3154 elif event
.unicode == u
'i':
3155 TogglePageProp('skip', False)
3156 elif event
.key
== K_TAB
:
3159 elif event
.key
in (32, K_DOWN
, K_RIGHT
, K_PAGEDOWN
):
3161 TransitionTo(GetNextPage(Pcurrent
, 1, 1))
3162 elif event
.key
in (K_BACKSPACE
, K_UP
, K_LEFT
, K_PAGEUP
):
3164 TransitionTo(GetNextPage(Pcurrent
, -1, 1))
3165 elif event
.key
== K_HOME
:
3168 elif event
.key
== K_END
:
3169 if Pcurrent
!= PageCount
:
3170 TransitionTo(PageCount
)
3171 elif event
.key
in (K_RETURN
, K_KP_ENTER
):
3172 if not(GetPageProp(Pcurrent
, 'boxes')) and Tracing
:
3173 BoxFade(lambda t
: 1.0 - t
)
3174 Tracing
= not(Tracing
)
3175 if not(GetPageProp(Pcurrent
, 'boxes')) and Tracing
:
3176 BoxFade(lambda t
: t
)
3177 elif event
.unicode == u
'+':
3178 IncrementSpotSize(+8)
3179 elif event
.unicode == u
'-':
3180 IncrementSpotSize(-8)
3181 elif event
.unicode == u
'[':
3182 SetGamma(new_gamma
=Gamma
/ GammaStep
)
3183 elif event
.unicode == u
']':
3184 SetGamma(new_gamma
=Gamma
* GammaStep
)
3185 elif event
.unicode == u
'{':
3186 SetGamma(new_black
=BlackLevel
- BlackLevelStep
)
3187 elif event
.unicode == u
'}':
3188 SetGamma(new_black
=BlackLevel
+ BlackLevelStep
)
3189 elif event
.unicode == u
'\\':
3192 keyfunc
= GetPageProp(Pcurrent
, 'keys', {}).get(event
.unicode, None)
3195 elif IsValidShortcutKey(event
.key
):
3196 if event
.mod
& KMOD_SHIFT
:
3197 AssignShortcut(Pcurrent
, event
.key
)
3199 # load keyboard shortcut
3200 page
= FindShortcut(event
.key
)
3201 if page
and (page
!= Pcurrent
):
3204 elif event
.type == MOUSEBUTTONDOWN
:
3209 MouseDownX
, MouseDownY
= event
.pos
3210 if event
.button
== 1:
3211 MarkUL
= MarkLR
= MouseToScreen(event
.pos
)
3212 elif (event
.button
== 3) and ZoomMode
:
3215 elif event
.button
== 4:
3216 IncrementSpotSize(+8)
3217 elif event
.button
== 5:
3218 IncrementSpotSize(-8)
3220 elif event
.type == MOUSEBUTTONUP
:
3227 if event
.button
== 2:
3231 if event
.button
== 1:
3233 # left mouse button released in marking mode -> stop box marking
3235 # reject too small boxes
3236 if (abs(MarkUL
[0] - MarkLR
[0]) > 0.04) \
3237 and (abs(MarkUL
[1] - MarkLR
[1]) > 0.03):
3238 boxes
= GetPageProp(Pcurrent
, 'boxes', [])
3239 oldboxcount
= len(boxes
)
3240 boxes
.append(NormalizeRect(MarkUL
[0], MarkUL
[1], MarkLR
[0], MarkLR
[1]))
3241 SetPageProp(Pcurrent
, 'boxes', boxes
)
3242 if not(oldboxcount
) and not(Tracing
):
3243 BoxFade(lambda t
: t
)
3246 # left mouse button released, but no marking
3248 dest
= GetNextPage(Pcurrent
, 1)
3250 for valid
, target
, x0
, y0
, x1
, y1
in GetPageProp(Pcurrent
, '_href', []):
3251 if valid
and (x
>= x0
) and (x
< x1
) and (y
>= y0
) and (y
< y1
):
3254 if type(dest
) == types
.IntType
:
3258 if (event
.button
== 3) and not(Panning
):
3259 # right mouse button -> check if a box has to be killed
3260 boxes
= GetPageProp(Pcurrent
, 'boxes', [])
3261 x
, y
= MouseToScreen(event
.pos
)
3263 # if a box is already present around the clicked position, kill it
3264 idx
= FindBox(x
, y
, boxes
)
3265 if (len(boxes
) == 1) and not(Tracing
):
3266 BoxFade(lambda t
: 1.0 - t
)
3268 SetPageProp(Pcurrent
, 'boxes', boxes
)
3271 # no box present -> go to previous page
3273 TransitionTo(GetNextPage(Pcurrent
, -1))
3276 elif event
.type == MOUSEMOTION
:
3277 pygame
.event
.clear(MOUSEMOTION
)
3278 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3280 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3282 # don't react on mouse input during video playback
3283 if VideoPlaying
: return
3284 # activate marking if mouse is moved away far enough
3285 if event
.buttons
[0] and not(Marking
):
3287 if (abs(x
- MouseDownX
) > 4) and (abs(y
- MouseDownY
) > 4):
3289 # mouse move while marking -> update marking box
3291 MarkLR
= MouseToScreen(event
.pos
)
3292 # mouse move while RMB is pressed -> panning
3293 if event
.buttons
[2] and ZoomMode
:
3295 if not(Panning
) and (abs(x
- MouseDownX
) > 4) and (abs(y
- MouseDownY
) > 4):
3297 ZoomX0
= PanAnchorX
+ (MouseDownX
- x
) * ZoomArea
/ ScreenWidth
3298 ZoomY0
= PanAnchorY
+ (MouseDownY
- y
) * ZoomArea
/ ScreenHeight
3299 ZoomX0
= min(max(ZoomX0
, 0.0), 1.0 - ZoomArea
)
3300 ZoomY0
= min(max(ZoomY0
, 0.0), 1.0 - ZoomArea
)
3301 # if anything changed, redraw the page
3302 if Marking
or Tracing
or event
.buttons
[2] or (CursorImage
and CursorVisible
):
3305 elif event
.type == USEREVENT_HIDE_MOUSE
:
3306 # mouse timer event -> hide fullscreen cursor
3307 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3311 elif event
.type == USEREVENT_PAGE_TIMEOUT
:
3312 TransitionTo(GetNextPage(Pcurrent
, 1))
3314 elif event
.type == USEREVENT_POLL_FILE
:
3317 if my_stat(f
) != GetFileProp(f
, 'stat'):
3321 # first, check if the new file is valid
3322 if not os
.path
.isfile(GetPageProp(Pcurrent
, '_file')):
3324 # invalidate everything we used to know about the input files
3326 for props
in PageProps
.itervalues():
3327 for prop
in ('_overview_rendered', '_box', '_href'):
3328 if prop
in props
: del props
[prop
]
3330 # force a transition to the current page, reloading it
3332 TransitionTo(Pcurrent
)
3333 # restart the background renderer thread. this is not completely safe,
3334 # i.e. there's a small chance that we fail to restart the thread, but
3335 # this isn't critical
3336 if CacheMode
and BackgroundRendering
:
3341 thread
.start_new_thread(RenderThread
, (Pcurrent
, Pnext
))
3343 elif event
.type == USEREVENT_TIMER_UPDATE
:
3348 ##### FILE LIST GENERATION #####################################################
3350 def IsImageFileName(name
):
3351 return os
.path
.splitext(name
)[1].lower() in \
3352 (".jpg", ".jpeg", ".png", ".tif", ".tiff", ".bmp", ".ppm", ".pgm")
3353 def IsPlayable(name
):
3354 return IsImageFileName(name
) or name
.lower().endswith(".pdf") or os
.path
.isdir(name
)
3356 def AddFile(name
, title
=None):
3357 global FileList
, FileName
3359 if os
.path
.isfile(name
):
3360 FileList
.append(name
)
3361 if title
: SetFileProp(name
, 'title', title
)
3363 elif os
.path
.isdir(name
):
3364 images
= [os
.path
.join(name
, f
) for f
in os
.listdir(name
) if IsImageFileName(f
)]
3365 images
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
3367 print >>sys
.stderr
, "Warning: no image files in directory `%s'" % name
3368 for img
in images
: AddFile(img
)
3370 elif name
.startswith('@') and os
.path
.isfile(name
[1:]):
3372 dirname
= os
.path
.dirname(name
)
3377 line
= [part
.strip() for part
in line
.split('#', 1)]
3382 subfile
, title
= line
3384 AddFile(os
.path
.normpath(os
.path
.join(dirname
, subfile
)), title
)
3387 print >>sys
.stderr
, "Error: cannot read list file `%s'" % name
3394 files
= list(filter(IsPlayable
, glob
.glob(name
)))
3396 for f
in files
: AddFile(f
)
3398 print >>sys
.stderr
, "Error: input file `%s' not found" % name
3401 ##### INITIALIZATION ###########################################################
3404 global ScreenWidth
, ScreenHeight
, TexWidth
, TexHeight
, TexSize
, LogoImage
3405 global TexMaxS
, TexMaxT
, MeshStepX
, MeshStepY
, EdgeX
, EdgeY
, PixelX
, PixelY
3406 global OverviewGridSize
, OverviewCellX
, OverviewCellY
3407 global OverviewOfsX
, OverviewOfsY
, OverviewImage
, OverviewPageCount
3408 global OverviewPageMap
, OverviewPageMapInv
, FileName
, FileList
, PageCount
3409 global DocumentTitle
, PageProps
, LogoTexture
, OSDFont
3410 global Pcurrent
, Pnext
, Tcurrent
, Tnext
, InitialPage
3411 global CacheFile
, CacheFileName
3412 global Extensions
, AllowExtensions
, TextureTarget
, PAR
, DAR
, TempFileName
3413 global BackgroundRendering
, FileStats
, RTrunning
, RTrestart
, StartTime
3414 global CursorImage
, CursorVisible
, InfoScriptPath
3416 # allocate temporary file
3417 TempFileName
= tempfile
.mktemp(prefix
="Accentuate-", suffix
="_tmp")
3419 # some input guesswork
3420 DocumentTitle
= os
.path
.splitext(os
.path
.split(FileName
)[1])[0]
3421 if FileName
and not(FileList
):
3423 if not(FileName
) and (len(FileList
) == 1):
3424 FileName
= FileList
[0]
3426 # fill the page list
3428 for name
in FileList
:
3429 ispdf
= name
.lower().endswith(".pdf")
3431 # PDF input -> try to pre-parse the PDF file
3433 # phase 1: internal PDF parser
3435 pages
, pdf_width
, pdf_height
= analyze_pdf(name
)
3437 pdf_width
, pdf_height
= (pdf_height
, pdf_width
)
3438 res
= min(ScreenWidth
* 72.0 / pdf_width
, \
3439 ScreenHeight
* 72.0 / pdf_height
)
3443 # phase 2: use pdftk
3445 assert 0 == spawn(os
.P_WAIT
, pdftkPath
, \
3446 ["pdftk", FileNameEscape
+ name
+ FileNameEscape
, \
3447 "dump_data", "output", TempFileName
+ ".txt"])
3448 title
, pages
= pdftkParse(TempFileName
+ ".txt", PageCount
)
3449 if DocumentTitle
and title
: DocumentTitle
= title
3455 SetPageProp(PageCount
+ 1, '_title', os
.path
.split(name
)[-1])
3459 print >>sys
.stderr
, "Warning: The input file `%s' could not be analyzed." % name
3462 # add pages and files into PageProps and FileProps
3463 pagerange
= list(range(PageCount
+ 1, PageCount
+ pages
+ 1))
3464 for page
in pagerange
:
3465 SetPageProp(page
, '_file', name
)
3466 if ispdf
: SetPageProp(page
, '_page', page
- PageCount
)
3467 title
= GetFileProp(name
, 'title')
3468 if title
: SetPageProp(page
, '_title', title
)
3469 SetFileProp(name
, 'pages', GetFileProp(name
, 'pages', []) + pagerange
)
3470 SetFileProp(name
, 'offsets', GetFileProp(name
, 'offsets', []) + [PageCount
])
3471 if not GetFileProp(name
, 'stat'): SetFileProp(name
, 'stat', my_stat(name
))
3472 if ispdf
: SetFileProp(name
, 'res', res
)
3475 # no pages? strange ...
3477 print >>sys
.stderr
, "The presentation doesn't have any pages, quitting."
3480 # if rendering is wanted, do it NOW
3481 if RenderToDirectory
:
3482 sys
.exit(DoRender())
3484 # load and execute info script
3485 if not InfoScriptPath
:
3486 InfoScriptPath
= FileName
+ ".info"
3489 # initialize graphics
3491 if Fullscreen
and UseAutoScreenSize
:
3492 size
= GetScreenSize()
3494 ScreenWidth
, ScreenHeight
= size
3495 print >>sys
.stderr
, "Detected screen size: %dx%d pixels" % (ScreenWidth
, ScreenHeight
)
3496 flags
= OPENGL|DOUBLEBUF
3500 pygame
.display
.set_mode((ScreenWidth
, ScreenHeight
), flags
)
3502 print >>sys
.stderr
, "FATAL: cannot create rendering surface in the desired resolution (%dx%d)" % (ScreenWidth
, ScreenHeight
)
3504 pygame
.display
.set_caption(__title__
)
3505 pygame
.key
.set_repeat(500, 30)
3507 pygame
.mouse
.set_visible(False)
3508 CursorVisible
= False
3509 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
3510 if (Gamma
<> 1.0) or (BlackLevel
<> 0):
3511 SetGamma(force
=True)
3513 # check if graphics are unaccelerated
3514 renderer
= glGetString(GL_RENDERER
)
3515 print >>sys
.stderr
, "OpenGL renderer:", renderer
3516 if renderer
.lower() in ("mesa glx indirect", "gdi generic"):
3517 print >>sys
.stderr
, "WARNING: Using an OpenGL software renderer. Accentuate will work, but it will"
3518 print >>sys
.stderr
, " very likely be too slow to be usable."
3520 # setup the OpenGL texture mode
3521 Extensions
= dict([(ext
.split('_', 2)[-1], None) for ext
in \
3522 glGetString(GL_EXTENSIONS
).split()])
3523 if AllowExtensions
and ("texture_non_power_of_two" in Extensions
):
3524 print >>sys
.stderr
, "Using GL_ARB_texture_non_power_of_two."
3525 TextureTarget
= GL_TEXTURE_2D
3526 TexWidth
= ScreenWidth
3527 TexHeight
= ScreenHeight
3530 elif AllowExtensions
and ("texture_rectangle" in Extensions
):
3531 print >>sys
.stderr
, "Using GL_ARB_texture_rectangle."
3532 TextureTarget
= 0x84F5 # GL_TEXTURE_RECTANGLE_ARB
3533 TexWidth
= ScreenWidth
3534 TexHeight
= ScreenHeight
3535 TexMaxS
= ScreenWidth
3536 TexMaxT
= ScreenHeight
3538 print >>sys
.stderr
, "Using conventional power-of-two textures with padding."
3539 TextureTarget
= GL_TEXTURE_2D
3540 TexWidth
= npot(ScreenWidth
)
3541 TexHeight
= npot(ScreenHeight
)
3542 TexMaxS
= ScreenWidth
* 1.0 / TexWidth
3543 TexMaxT
= ScreenHeight
* 1.0 / TexHeight
3544 TexSize
= TexWidth
* TexHeight
* 3
3546 # set up some variables
3548 PAR
= DAR
/ float(ScreenWidth
) * float(ScreenHeight
)
3549 MeshStepX
= 1.0 / MeshResX
3550 MeshStepY
= 1.0 / MeshResY
3551 PixelX
= 1.0 / ScreenWidth
3552 PixelY
= 1.0 / ScreenHeight
3553 EdgeX
= BoxEdgeSize
* 1.0 / ScreenWidth
3554 EdgeY
= BoxEdgeSize
* 1.0 / ScreenHeight
3555 if InitialPage
is None:
3556 InitialPage
= GetNextPage(0, 1)
3557 Pcurrent
= InitialPage
3559 # prepare logo image
3560 LogoImage
= Image
.open(StringIO
.StringIO(LOGO
))
3561 LogoTexture
= glGenTextures(1)
3562 glBindTexture(GL_TEXTURE_2D
, LogoTexture
)
3563 glTexImage2D(GL_TEXTURE_2D
, 0, 1, 256, 64, 0, GL_LUMINANCE
, GL_UNSIGNED_BYTE
, LogoImage
.tostring())
3564 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
)
3566 pygame
.display
.flip()
3568 # initialize OSD font
3570 OSDFont
= GLFont(FontTextureWidth
, FontTextureHeight
, FontList
, FontSize
, search_path
=FontPath
)
3573 for key
in ('title', '_title'):
3574 titles
.extend([p
[key
] for p
in PageProps
.itervalues() if key
in p
])
3576 OSDFont
.AddString("".join(titles
))
3578 print >>sys
.stderr
, "The OSD font size is too large, the OSD will be rendered incompletely."
3580 print >>sys
.stderr
, "Could not open OSD font file, disabling OSD."
3581 except (NameError, AttributeError, TypeError):
3582 print >>sys
.stderr
, "Your version of PIL is too old or incomplete, disabling OSD."
3584 # initialize mouse cursor
3587 CursorImage
= PrepareCustomCursor(Image
.open(CursorImage
))
3589 print >>sys
.stderr
, "Could not open the mouse cursor image, using standard cursor."
3593 if CacheMode
== PersistentCache
:
3594 if not CacheFileName
:
3595 CacheFileName
= FileName
+ ".cache"
3597 if CacheMode
== FileCache
:
3598 CacheFile
= tempfile
.TemporaryFile(prefix
="Accentuate-", suffix
=".cache")
3600 # initialize overview metadata
3601 OverviewPageMap
=[i
for i
in xrange(1, PageCount
+ 1) \
3602 if GetPageProp(i
, ('overview', '_overview'), True) \
3603 and (i
>= PageRangeStart
) and (i
<= PageRangeEnd
)]
3604 OverviewPageCount
= max(len(OverviewPageMap
), 1)
3605 OverviewPageMapInv
= {}
3606 for page
in xrange(1, PageCount
+ 1):
3607 OverviewPageMapInv
[page
] = len(OverviewPageMap
) - 1
3608 for i
in xrange(len(OverviewPageMap
)):
3609 if OverviewPageMap
[i
] >= page
:
3610 OverviewPageMapInv
[page
] = i
3613 # initialize overview page geometry
3614 OverviewGridSize
= 1
3615 while OverviewPageCount
> OverviewGridSize
* OverviewGridSize
:
3616 OverviewGridSize
+= 1
3617 OverviewCellX
= int(ScreenWidth
/ OverviewGridSize
)
3618 OverviewCellY
= int(ScreenHeight
/ OverviewGridSize
)
3619 OverviewOfsX
= int((ScreenWidth
- OverviewCellX
* OverviewGridSize
)/2)
3620 OverviewOfsY
= int((ScreenHeight
- OverviewCellY
* \
3621 int((OverviewPageCount
+ OverviewGridSize
- 1) / OverviewGridSize
)) / 2)
3622 OverviewImage
= Image
.new('RGB', (TexWidth
, TexHeight
))
3624 # fill overlay "dummy" images
3625 dummy
= LogoImage
.copy()
3626 maxsize
= (OverviewCellX
- 2 * OverviewBorder
, OverviewCellY
- 2 * OverviewBorder
)
3627 if (dummy
.size
[0] > maxsize
[0]) or (dummy
.size
[1] > maxsize
[1]):
3628 dummy
.thumbnail(ZoomToFit(dummy
.size
, maxsize
), Image
.ANTIALIAS
)
3629 margX
= int((OverviewCellX
- dummy
.size
[0]) / 2)
3630 margY
= int((OverviewCellY
- dummy
.size
[1]) / 2)
3631 dummy
= dummy
.convert(mode
='RGB')
3632 for page
in range(OverviewPageCount
):
3633 pos
= OverviewPos(page
)
3634 OverviewImage
.paste(dummy
, (pos
[0] + margX
, pos
[1] + margY
))
3637 # set up background rendering
3638 if not EnableBackgroundRendering
:
3639 print >>sys
.stderr
, "Background rendering isn't available on this platform."
3640 BackgroundRendering
= False
3642 # if caching is enabled, pre-render all pages
3643 if CacheMode
and not(BackgroundRendering
):
3646 pygame
.display
.flip()
3647 for pdf
in FileProps
:
3648 if pdf
.lower().endswith(".pdf"):
3652 for page
in range(InitialPage
, PageCount
+ 1) + range(1, InitialPage
):
3653 event
= pygame
.event
.poll()
3654 while event
.type != NOEVENT
:
3655 if event
.type == KEYDOWN
:
3656 if (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
3659 elif event
.type == MOUSEBUTTONUP
:
3661 event
= pygame
.event
.poll()
3663 if (page
>= PageRangeStart
) and (page
<= PageRangeEnd
):
3666 progress
+= 1.0 / PageCount
;
3667 DrawProgress(progress
)
3668 pygame
.display
.flip()
3670 # create buffer textures
3672 pygame
.display
.flip()
3673 glEnable(TextureTarget
)
3674 Tcurrent
= glGenTextures(1)
3675 Tnext
= glGenTextures(1)
3676 for T
in (Tcurrent
, Tnext
):
3677 glBindTexture(TextureTarget
, T
)
3678 glTexParameteri(TextureTarget
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
)
3679 glTexParameteri(TextureTarget
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
)
3680 glTexParameteri(TextureTarget
, GL_TEXTURE_WRAP_S
, GL_CLAMP
)
3681 glTexParameteri(TextureTarget
, GL_TEXTURE_WRAP_T
, GL_CLAMP
)
3683 # prebuffer current and next page
3685 RenderPage(Pcurrent
, Tcurrent
)
3686 PageEntered(update_time
=False)
3687 PreloadNextPage(GetNextPage(Pcurrent
, 1))
3689 # some other preparations
3690 PrepareTransitions()
3693 pygame
.time
.set_timer(USEREVENT_POLL_FILE
, PollInterval
* 1000)
3695 # start the background rendering thread
3696 if CacheMode
and BackgroundRendering
:
3698 thread
.start_new_thread(RenderThread
, (Pcurrent
, Pnext
))
3700 # start output and enter main loop
3701 StartTime
= pygame
.time
.get_ticks()
3702 pygame
.time
.set_timer(USEREVENT_TIMER_UPDATE
, 100)
3703 if not(Fullscreen
) and CursorImage
:
3704 pygame
.mouse
.set_visible(False)
3706 UpdateCaption(Pcurrent
)
3708 # Kick off LIRC thread
3709 if(UseLIRC
is True):
3714 HandleEvent(pygame
.event
.wait())
3717 # wrapper around main() that ensures proper uninitialization
3724 # ensure that background rendering is halted
3727 # remove all temp files
3728 if 'CacheFile' in globals():
3730 for tmp
in glob
.glob(TempFileName
+ "*"):
3736 if(UseLIRC
is True):
3740 ##### COMMAND-LINE PARSER AND HELP #############################################
3742 def if_op(cond
, res_then
, res_else
):
3743 if cond
: return res_then
3744 else: return res_else
3746 def HelpExit(code
=0):
3747 print """A nice presentation tool.
3749 Usage: """+os
.path
.basename(sys
.argv
[0])+""" [OPTION...] <INPUT(S)...>
3751 You may either play a PDF file, a directory containing image files or
3752 individual image files.
3755 -r, --rotate <n> rotate pages clockwise in 90-degree steps
3756 --scale scale images to fit screen (not used in PDF mode)
3757 --supersample use supersampling (only used in PDF mode)
3758 -s --supersample for PDF files, --scale for image files
3759 -I, --script <path> set the path of the info script
3760 -u, --poll <seconds> check periodically if the source file has been
3761 updated and reload it if it did
3762 -o, --output <dir> don't display the presentation, only render to .png
3763 -h, --help show this help text and exit
3766 -f, --fullscreen """+if_op(Fullscreen
,"do NOT ","")+"""start in fullscreen mode
3767 -g, --geometry <WxH> set window size or fullscreen resolution
3768 -A, --aspect <X:Y> adjust for a specific display aspect ratio (e.g. 5:4)
3769 -G, --gamma <G[:BL]> specify startup gamma and black level
3772 -i, --initialpage <n> start with page <n>
3773 -p, --pages <A-B> only cache pages in the specified range;
3774 implicitly sets -i <A>
3775 -w, --wrap go back to the first page after the last page
3776 -J, --fade fade to black after the last page
3777 -a, --auto <seconds> automatically advance to next page after some seconds
3778 -O, --autooverview <x> automatically derive page visibility on overview page
3779 -O first = show pages with captions
3780 -O last = show pages before pages with captions
3783 -t, --transition <trans[,trans2...]>
3784 force a specific transitions or set of transitions
3785 -l, --listtrans print a list of available transitions and exit
3786 -F, --font <file> use a specific TrueType font file for the OSD
3787 -S, --fontsize <px> specify the OSD font size in pixels
3788 -C, --cursor <F[:X,Y]> use a .png image as the mouse cursor
3789 -L, --layout <spec> set the OSD layout (please read the documentation)
3792 -M, --minutes display time in minutes, not seconds
3793 -d, --duration <time> set the desired duration of the presentation and show
3794 a progress bar at the bottom of the screen
3795 -T, --transtime <ms> set transition duration in milliseconds
3796 -D, --mousedelay <ms> set mouse hide delay for fullscreen mode (in ms)
3797 -B, --boxfade <ms> set highlight box fade duration in milliseconds
3798 -Z, --zoom <ms> set zoom duration in milliseconds
3801 -c, --cache <mode> set page cache mode:
3802 -c none = disable caching completely
3803 -c memory = store cache in RAM
3804 -c disk = store cache on disk temporarily
3805 -c persistent = store cache on disk persistently
3806 --cachefile <path> set the persistent cache file path (implies -cp)
3807 -b, --noback don't pre-render images in the background
3808 -P, --gspath <path> set path to GhostScript or pdftoppm executable
3809 -R, --meshres <XxY> set mesh resolution for effects (default: 48x36)
3810 -e, --noext don't use OpenGL texture size extensions
3812 For detailed information, visit""", __website__
3815 def ListTransitions():
3816 print "Available transitions:"
3817 standard
= dict([(tc
.__name
__, None) for tc
in AvailableTransitions
])
3818 trans
= [(tc
.__name
__, tc
.__doc
__) for tc
in AllTransitions
]
3819 trans
.append(('None', "no transition"))
3821 maxlen
= max([len(item
[0]) for item
in trans
])
3822 for name
, desc
in trans
:
3823 if name
in standard
:
3827 print star
, name
.ljust(maxlen
), '-', desc
3828 print "(transitions with * are enabled by default)"
3831 def TryTime(s
, regexp
, func
):
3832 m
= re
.match(regexp
, s
, re
.I
)
3834 return func(map(int, m
.groups()))
3836 return TryTime(s
, r
'([0-9]+)s?$', lambda m
: m
[0]) \
3837 or TryTime(s
, r
'([0-9]+)m$', lambda m
: m
[0] * 60) \
3838 or TryTime(s
, r
'([0-9]+)[m:]([0-9]+)[ms]?$', lambda m
: m
[0] * 60 + m
[1]) \
3839 or TryTime(s
, r
'([0-9]+)[h:]([0-9]+)[hm]?$', lambda m
: m
[0] * 3600 + m
[1] * 60) \
3840 or TryTime(s
, r
'([0-9]+)[h:]([0-9]+)[m:]([0-9]+)s?$', lambda m
: m
[0] * 3600 + m
[1] * 60 + m
[2])
3843 print >>sys
.stderr
, "command line parse error:", msg
3844 print >>sys
.stderr
, "use `%s -h' to get help" % sys
.argv
[0]
3845 print >>sys
.stderr
, "or visit", __website__
, "for full documentation"
3848 def SetTransitions(list):
3849 global AvailableTransitions
3850 index
= dict([(tc
.__name
__.lower(), tc
) for tc
in AllTransitions
])
3851 index
['none'] = None
3852 AvailableTransitions
=[]
3853 for trans
in list.split(','):
3855 AvailableTransitions
.append(index
[trans
.lower()])
3857 opterr("unknown transition `%s'" % trans
)
3859 def ParseLayoutPosition(value
):
3862 for c
in value
.strip().lower():
3863 if c
== 't': ypos
.append(0)
3864 elif c
== 'b': ypos
.append(1)
3865 elif c
== 'l': xpos
.append(0)
3866 elif c
== 'r': xpos
.append(1)
3867 elif c
== 'c': xpos
.append(2)
3868 else: opterr("invalid position specification `%s'" % value
)
3869 if not xpos
: opterr("position `%s' lacks X component" % value
)
3870 if not ypos
: opterr("position `%s' lacks Y component" % value
)
3871 if len(xpos
)>1: opterr("position `%s' has multiple X components" % value
)
3872 if len(ypos
)>1: opterr("position `%s' has multiple Y components" % value
)
3873 return (xpos
[0] << 1) | ypos
[0]
3874 def SetLayoutSubSpec(key
, value
):
3875 global OSDTimePos
, OSDTitlePos
, OSDPagePos
, OSDStatusPos
3876 global OSDAlpha
, OSDMargin
3877 lkey
= key
.strip().lower()
3878 if lkey
in ('a', 'alpha', 'opacity'):
3880 OSDAlpha
= float(value
)
3882 opterr("invalid alpha value `%s'" % value
)
3884 OSDAlpha
*= 0.01 # accept percentages, too
3885 if (OSDAlpha
< 0.0) or (OSDAlpha
> 1.0):
3886 opterr("alpha value %s out of range" % value
)
3887 elif lkey
in ('margin', 'dist', 'distance'):
3889 OSDMargin
= float(value
)
3891 opterr("invalid margin value `%s'" % value
)
3893 opterr("margin value %s out of range" % value
)
3894 elif lkey
in ('t', 'time'):
3895 OSDTimePos
= ParseLayoutPosition(value
)
3896 elif lkey
in ('title', 'caption'):
3897 OSDTitlePos
= ParseLayoutPosition(value
)
3898 elif lkey
in ('page', 'number'):
3899 OSDPagePos
= ParseLayoutPosition(value
)
3900 elif lkey
in ('status', 'info'):
3901 OSDStatusPos
= ParseLayoutPosition(value
)
3903 opterr("unknown layout element `%s'" % key
)
3904 def SetLayout(spec
):
3905 for sub
in spec
.replace(':', '=').split(','):
3907 key
, value
= sub
.split('=')
3909 opterr("invalid layout spec `%s'" % sub
)
3910 SetLayoutSubSpec(key
, value
)
3912 def ParseCacheMode(arg
):
3913 arg
= arg
.strip().lower()
3914 if "none".startswith(arg
): return NoCache
3915 if "off".startswith(arg
): return NoCache
3916 if "memory".startswith(arg
): return MemCache
3917 if "disk".startswith(arg
): return FileCache
3918 if "file".startswith(arg
): return FileCache
3919 if "persistent".startswith(arg
): return PersistentCache
3920 opterr("invalid cache mode `%s'" % arg
)
3922 def ParseAutoOverview(arg
):
3923 arg
= arg
.strip().lower()
3924 if "off".startswith(arg
): return Off
3925 if "first".startswith(arg
): return First
3926 if "last".startswith(arg
): return Last
3929 assert (i
>= Off
) and (i
<= Last
)
3931 opterr("invalid auto-overview mode `%s'" % arg
)
3933 def ParseOptions(argv
):
3934 global FileName
, FileList
, Fullscreen
, Scaling
, Supersample
, CacheMode
3935 global TransitionDuration
, MouseHideDelay
, BoxFadeDuration
, ZoomDuration
3936 global ScreenWidth
, ScreenHeight
, MeshResX
, MeshResY
, InitialPage
, Wrap
3937 global AutoAdvance
, RenderToDirectory
, Rotation
, AllowExtensions
, DAR
3938 global BackgroundRendering
, UseAutoScreenSize
, PollInterval
, CacheFileName
3939 global PageRangeStart
, PageRangeEnd
, FontList
, FontSize
, Gamma
, BlackLevel
3940 global EstimatedDuration
, CursorImage
, CursorHotspot
, MinutesOnly
3941 global GhostScriptPath
, pdftoppmPath
, UseGhostScript
, InfoScriptPath
3942 global AutoOverview
, FadeToBlackAtEnd
3944 try: # unused short options: jknqvxyzEHJKNQUVWXY
3945 opts
, args
= getopt
.getopt(argv
, \
3946 "hfg:sc:i:wa:t:lo:r:T:D:B:Z:P:R:eA:mbp:u:F:S:G:d:C:ML:I:O:J", \
3947 ["help", "fullscreen", "geometry=", "scale", "supersample", \
3948 "nocache", "initialpage=", "wrap", "auto", "listtrans", "output=", \
3949 "rotate=", "transition=", "transtime=", "mousedelay=", "boxfade=", \
3950 "zoom=", "gspath=", "meshres=", "noext", "aspect", "memcache", \
3951 "noback", "pages=", "poll=", "font=", "fontsize=", "gamma=",
3952 "duration=", "cursor=", "minutes", "layout=", "script=", "cache=",
3953 "cachefile=", "autooverview=", "fade"])
3954 except getopt
.GetoptError
, message
:
3957 for opt
, arg
in opts
:
3958 if opt
in ("-h", "--help"):
3960 if opt
in ("-l", "--listtrans"):
3962 if opt
in ("-f", "--fullscreen"):
3963 Fullscreen
= not(Fullscreen
)
3964 if opt
in ("-e", "--noext"):
3965 AllowExtensions
= not(AllowExtensions
)
3966 if opt
in ("-s", "--scale"):
3967 Scaling
= not(Scaling
)
3968 if opt
in ("-s", "--supersample"):
3970 if opt
in ("-w", "--wrap"):
3972 if opt
in ("-J", "--fade"):
3973 FadeToBlackAtEnd
= not(FadeToBlackAtEnd
)
3974 if opt
in ("-O", "--autooverview"):
3975 AutoOverview
= ParseAutoOverview(arg
)
3976 if opt
in ("-c", "--cache"):
3977 CacheMode
= ParseCacheMode(arg
)
3978 if opt
== "--nocache":
3979 print >>sys
.stderr
, "Note: The `--nocache' option is deprecated, use `--cache none' instead."
3981 if opt
in ("-m", "--memcache"):
3982 print >>sys
.stderr
, "Note: The `--memcache' option is deprecated, use `--cache memory' instead."
3983 CacheMode
= MemCache
3984 if opt
== "--cachefile":
3986 CacheMode
= PersistentCache
3987 if opt
in ("-M", "--minutes"):
3988 MinutesOnly
= not(MinutesOnly
)
3989 if opt
in ("-b", "--noback"):
3990 BackgroundRendering
= not(BackgroundRendering
)
3991 if opt
in ("-t", "--transition"):
3993 if opt
in ("-L", "--layout"):
3995 if opt
in ("-o", "--output"):
3996 RenderToDirectory
= arg
3997 if opt
in ("-I", "--script"):
3998 InfoScriptPath
= arg
3999 if opt
in ("-F", "--font"):
4001 if opt
in ("-P", "--gspath"):
4002 UseGhostScript
= (arg
.replace("\\", "/").split("/")[-1].lower().find("pdftoppm") < 0)
4004 GhostScriptPath
= arg
4007 if opt
in ("-S", "--fontsize"):
4012 opterr("invalid parameter for --fontsize")
4013 if opt
in ("-i", "--initialpage"):
4015 InitialPage
= int(arg
)
4016 assert InitialPage
> 0
4018 opterr("invalid parameter for --initialpage")
4019 if opt
in ("-d", "--duration"):
4021 EstimatedDuration
= ParseTime(arg
)
4022 assert EstimatedDuration
> 0
4024 opterr("invalid parameter for --duration")
4025 if opt
in ("-a", "--auto"):
4027 AutoAdvance
= int(arg
) * 1000
4028 assert (AutoAdvance
> 0) and (AutoAdvance
<= 86400000)
4030 opterr("invalid parameter for --auto")
4031 if opt
in ("-T", "--transtime"):
4033 TransitionDuration
= int(arg
)
4034 assert (TransitionDuration
>= 0) and (TransitionDuration
< 32768)
4036 opterr("invalid parameter for --transtime")
4037 if opt
in ("-D", "--mousedelay"):
4039 MouseHideDelay
= int(arg
)
4040 assert (MouseHideDelay
>= 0) and (MouseHideDelay
< 32768)
4042 opterr("invalid parameter for --mousedelay")
4043 if opt
in ("-B", "--boxfade"):
4045 BoxFadeDuration
= int(arg
)
4046 assert (BoxFadeDuration
>= 0) and (BoxFadeDuration
< 32768)
4048 opterr("invalid parameter for --boxfade")
4049 if opt
in ("-Z", "--zoom"):
4051 ZoomDuration
= int(arg
)
4052 assert (ZoomDuration
>= 0) and (ZoomDuration
< 32768)
4054 opterr("invalid parameter for --zoom")
4055 if opt
in ("-r", "--rotate"):
4059 opterr("invalid parameter for --rotate")
4060 while Rotation
< 0: Rotation
+= 4
4061 Rotation
= Rotation
& 3
4062 if opt
in ("-u", "--poll"):
4064 PollInterval
= int(arg
)
4065 assert PollInterval
>= 0
4067 opterr("invalid parameter for --poll")
4068 if opt
in ("-g", "--geometry"):
4070 ScreenWidth
, ScreenHeight
= map(int, arg
.split("x"))
4071 assert (ScreenWidth
>= 320) and (ScreenWidth
< 4096)
4072 assert (ScreenHeight
>= 200) and (ScreenHeight
< 4096)
4073 UseAutoScreenSize
= False
4075 opterr("invalid parameter for --geometry")
4076 if opt
in ("-R", "--meshres"):
4078 MeshResX
, MeshResY
= map(int, arg
.split("x"))
4079 assert (MeshResX
> 0) and (MeshResX
<= ScreenWidth
)
4080 assert (MeshResY
> 0) and (MeshResY
<= ScreenHeight
)
4082 opterr("invalid parameter for --meshres")
4083 if opt
in ("-p", "--pages"):
4085 PageRangeStart
, PageRangeEnd
= map(int, arg
.split("-"))
4086 assert PageRangeStart
> 0
4087 assert PageRangeStart
<= PageRangeEnd
4089 opterr("invalid parameter for --pages")
4090 InitialPage
=PageRangeStart
4091 if opt
in ("-A", "--aspect"):
4094 fx
, fy
= map(float, arg
.split(':'))
4100 opterr("invalid parameter for --aspect")
4101 if opt
in ("-G", "--gamma"):
4104 arg
, bl
= arg
.split(':', 1)
4105 BlackLevel
= int(bl
)
4108 assert (BlackLevel
>= 0) and (BlackLevel
< 255)
4110 opterr("invalid parameter for --gamma")
4111 if opt
in ("-C", "--cursor"):
4114 arg
= arg
.split(':')
4116 CursorImage
= ':'.join(arg
[:-1])
4117 CursorHotspot
= map(int, arg
[-1].split(','))
4120 assert (BlackLevel
>= 0) and (BlackLevel
< 255)
4122 opterr("invalid parameter for --cursor")
4127 opterr("no playable files specified")
4130 # glob and filter argument list
4133 files
.extend(glob
.glob(arg
))
4134 files
= list(filter(IsPlayable
, files
))
4136 # if only one argument is specified, use it as the informal file name
4142 # construct final FileList by expanding directories to image file lists
4145 if os
.path
.isdir(item
):
4146 images
= [os
.path
.join(item
, f
) for f
in os
.listdir(item
) if IsImageFileName(f
)]
4147 images
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
4148 FileList
.extend(images
)
4150 FileList
.append(item
)
4153 opterr("no playable files specified")
4156 # use this function if you intend to use Accentuate as a library
4160 except SystemExit, e
:
4163 if __name__
=="__main__":
4164 ParseOptions(sys
.argv
[1:])