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
100 # Support for LIRC remotes
102 import pylirc
, time
, select
103 from threading
import Thread
106 print "LIRC support unavailable."
112 Thread
.__init
__(self
)
115 lirchandle
= pylirc
.init("Accentuate", '/etc/lircrc', 0)
116 print "Succesfully opened lirc, handle: " + str(lirchandle
)
117 print "LIRC event loop started..."
119 # Man this is nasty indentation, but I use 8 space tabs
120 # while apparently most Pythoners use 3 :(
123 s
= pylirc
.nextcode()
130 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 275, 'unicode': u
'', 'mod': 0}))
131 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 275, 'mod': 0}))
132 elif(code
== "prev"):
133 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 276, 'unicode': u
'', 'mod': 0}))
134 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 276, 'mod': 0}))
135 elif(code
== "menu"):
136 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 9, 'unicode': u
'\t', 'mod': 0}))
137 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 9, 'mod': 0}))
138 elif(code
== "play"):
139 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 13, 'unicode': u
'\r', 'mod': 0}))
140 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 13, 'mod': 0}))
141 elif(code
== "volume_down"):
142 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 274, 'unicode': u
'', 'mod': 0}))
143 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 274, 'mod': 0}))
144 elif(code
== "volume_up"):
145 pygame
.event
.post(pygame
.event
.Event(KEYDOWN
, {'key': 273, 'unicode': u
'\r', 'mod': 0}))
146 pygame
.event
.post(pygame
.event
.Event(KEYUP
, {'key': 273, 'mod': 0}))
150 # End LIRC support code
152 # import basic modules
153 import random
, getopt
, os
, types
, re
, codecs
, tempfile
, glob
, StringIO
, md5
, re
157 # initialize some platform-specific settings
159 root
= os
.path
.split(sys
.argv
[0])[0] or "."
160 pdftoppmPath
= os
.path
.join(root
, "pdftoppm.exe")
161 GhostScriptPath
= os
.path
.join(root
, "gs\\gswin32c.exe")
162 GhostScriptPlatformOptions
= ["-I" + os
.path
.join(root
, "gs")]
165 MPlayerPath
= os
.path
.join(root
, "mplayer.exe")
167 dm
= win32api
.EnumDisplaySettings(None, -1) #ENUM_CURRENT_SETTINGS
168 return (int(dm
.PelsWidth
), int(dm
.PelsHeight
))
170 win32api
.ShellExecute(0, "open", url
, "", "", 0)
173 def GetScreenSize(): return pygame
.display
.list_modes()[0]
174 def RunURL(url
): print "Error: cannot run URL `%s'" % url
175 MPlayerPlatformOptions
= [ "-colorkey", "0x000000" ]
176 MPlayerColorKey
= True
177 pdftkPath
= os
.path
.join(root
, "pdftk.exe")
180 if getattr(sys
, "frozen", None):
181 sys
.path
.append(root
)
183 FontList
= ["Verdana.ttf", "Arial.ttf"]
185 pdftoppmPath
= "pdftoppm"
186 GhostScriptPath
= "gs"
187 GhostScriptPlatformOptions
= []
188 MPlayerPath
= "mplayer"
189 MPlayerPlatformOptions
= [ "-vo", "gl" ]
190 MPlayerColorKey
= False
194 FontPath
= ["/usr/share/fonts", "/usr/local/share/fonts", "/usr/X11R6/lib/X11/fonts/TTF"]
195 FontList
= ["DejaVuSans.ttf", "Vera.ttf", "Verdana.ttf"]
198 spawn(os
.P_NOWAIT
, "xdg-open", ["xdg-open", url
])
200 print >>sys
.stderr
, "Error: cannot open URL `%s'" % url
202 res_re
= re
.compile(r
'\s*(\d+)x(\d+)\s+\d+\.\d+\*')
203 for path
in os
.getenv("PATH").split(':'):
204 fullpath
= os
.path
.join(path
, "xrandr")
205 if os
.path
.exists(fullpath
):
208 for line
in os
.popen(fullpath
, "r"):
209 m
= res_re
.match(line
)
211 res
= tuple(map(int, m
.groups()))
216 return pygame
.display
.list_modes()[0]
218 # import special modules
220 from OpenGL
.GL
import *
222 from pygame
.locals import *
223 import Image
, ImageDraw
, ImageFont
, ImageFilter
224 import TiffImagePlugin
, BmpImagePlugin
, JpegImagePlugin
, PngImagePlugin
, PpmImagePlugin
225 except (ValueError, ImportError), err
:
226 print >>sys
.stderr
, "Oops! Cannot load necessary modules:", err
227 print >>sys
.stderr
, """To use Accentuate, you need to install the following Python modules:
228 - PyOpenGL [python-opengl] http://pyopengl.sourceforge.net/
229 - PyGame [python-pygame] http://www.pygame.org/
230 - PIL [python-imaging] http://www.pythonware.com/products/pil/
231 - PyWin32 (OPTIONAL, Win32) http://starship.python.net/crew/mhammond/win32/
232 Additionally, please be sure to have pdftoppm or GhostScript installed if you
233 intend to use PDF input."""
238 EnableBackgroundRendering
= True
239 def create_lock(): return thread
.allocate_lock()
241 EnableBackgroundRendering
= False
243 def __init__(self
): self
.state
= False
244 def acquire(self
, dummy
=0): self
.state
= True
245 def release(self
): self
.state
= False
246 def locked(self
): return self
.state
247 def create_lock(): return pseudolock()
250 ##### TOOL CODE ################################################################
252 # initialize private variables
255 InfoScriptPath
= None
277 ZoomWarningIssued
= False
278 TransitionRunning
= False
280 OverviewNeedUpdate
= False
283 CurrentOSDCaption
= ""
285 CurrentOSDStatus
= ""
286 CurrentOSDComment
= ""
287 Lrender
= create_lock()
288 Lcache
= create_lock()
289 Loverview
= create_lock()
304 # tool constants (used in info scripts)
308 USEREVENT_HIDE_MOUSE
= USEREVENT
309 USEREVENT_PAGE_TIMEOUT
= USEREVENT
+ 1
310 USEREVENT_POLL_FILE
= USEREVENT
+ 2
311 USEREVENT_TIMER_UPDATE
= USEREVENT
+ 3
314 # read and write the PageProps and FileProps meta-dictionaries
315 def GetProp(prop_dict
, key
, prop
, default
=None):
316 if not key
in prop_dict
: return default
317 if type(prop
) == types
.StringType
:
318 return prop_dict
[key
].get(prop
, default
)
321 return prop_dict
[key
][subprop
]
325 def SetProp(prop_dict
, key
, prop
, value
):
326 if not key
in prop_dict
:
327 prop_dict
[key
] = {prop
: value
}
329 prop_dict
[key
][prop
] = value
331 def GetPageProp(page
, prop
, default
=None):
333 return GetProp(PageProps
, page
, prop
, default
)
334 def SetPageProp(page
, prop
, value
):
336 SetProp(PageProps
, page
, prop
, value
)
337 def GetTristatePageProp(page
, prop
, default
=0):
338 res
= GetPageProp(page
, prop
, default
)
339 if res
!= FirstTimeOnly
: return res
340 return (GetPageProp(page
, '_shown', 0) == 1)
342 def GetFileProp(page
, prop
, default
=None):
344 return GetProp(FileProps
, page
, prop
, default
)
345 def SetFileProp(page
, prop
, value
):
347 SetProp(FileProps
, page
, prop
, value
)
349 # the Accentuate logo (256x64 pixels grayscale PNG)
350 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'+ \
351 '\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a'+ \
352 '\x9c\x18\x00\x00\x00\x07tIME\x07\xd8\t\x01\x10\x06\x0bZ\xbb\xec\x88\x00\x00\x00\x19tEXtComment'+ \
353 '\x00Created with GIMPW\x81\x0e\x17\x00\x00\x05TIDATx\xda\xedXkL\\E\x18=\xfb\xa0\xbc\x16\x02\x94W'+ \
354 '\xdd"4\x88\x8d\x85\xb6P\x8b\xa9\xa6\xb5)-\x11\xaa\xa8\xa4\xc4Tc\xd26bc\xd5h\xc4\x1a\x8d\xa6\x06\xac5FM'+ \
355 '\x94\xd8\x88\xb1\xc1\xbe\xb4\xbe\xfda1\xd1\xa8\xd8\x07M\x9a`\x81D\xc4\x18\x0cH\x1f\xb6\xa6\xa5\xd8'+ \
356 '\x07\xb0@w\xaf?\xbeYv\xe6\xee\x9d\xb9+\x90\xf4\xc7\xce\xf9\xc3|\xe7\x9b\xfb\xdds\xcf^\xbe\x99\xb9\x80'+ \
357 '\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86F\xf4b\xb9\x11\xc4\xf2\xe8yh\'7\xde49'+ \
358 '\xdax]\xb40\xf7\xaf[\xa5\xc4+\x93o\xc0\xe5\x84\xa81\x80{\x03j<\x93\xc3\xa4\x9ahl\x01?\x1b!\xb4F\xe1'+ \
359 '\xbf\xc0\xbc\x00g@ /Z\x0cp\x87\x1a\x9f\x03\x000\x1a\x0f\x00\x8e\x8d\xf5\xa6\x89q\x95\xe5\xc5\xb9i'+ \
360 '\xb3|\x83\xa7\xba\xdb[\xfb\xec\xf9\x8c\xeaU\x0b\xbd\x9e\xc0`\xef\xb1/:\xadn\xec}\xac\xf2\xe6\x84\xa1'+ \
361 '\x9eov]\x9e\xfas\xban[\xb3t~\xb6\xc7\x18\xb9\xd0\xd7\xf9C\xab\xdfv\xbeR\x93\xa3\x9f\\\xdbB\x7f\xfa'+ \
362 '\x1cB6\xa1\xfe"\xf7~\x18\xb6|F\x93\x8f\xfb\x7fZ\x18\xfe\xd3l\x1de\xc3s+\x85\x8cX\xcbTV\x0c\x97'+ \
363 '\xbc\'\xdc\xfb\xe4&(*)41\x94Q\xa2\x17=4X\xc5\'\x17\xfdiYR\xca\xaf\xfcG\xa0}\x1bL\x8f\xe0\xdc\x1bJ'+ \
364 '\x8e\xdc:E\x03\xc2\xae\xd8\xe7T\x19 \xd5\xc4\xb0\x9f\xf8z\xbc\xc8\xaaq\xb9\xd2K\xd6\x9e\xca\xf82'+ \
365 '\x9fY\xc3\x06Q\xf4N>\xd7\xe5\x98!\x03\x8cm\n\x03\xe4\x9a\xd8\xc27L\xecM\xc8\xa3f8\x9c\x14z\x9f\xff'+ \
366 '\xb6\xf6T\xc6{\x07\xc34\xf8\x8ad\xa2\r\xc3\xa8\x98)\x03F\xb3\xa4\x95\x14\x9a\x08\xb5D\x1e\x07\xd0F'+ \
367 '\xc3G&s\xcd\xec\x8a\x0b\xdb\x8a\x93]\xde\xfb?\x1a3\xd4\xfc\x01\xf6n7\xdc\x12\x9b\xbc\xb6\x83\x82'+ \
368 '\x9fL\xa2\xf7\x14\xc7\xe5\xbe\xc3\xc6\xef\xdb\x18\x10\x90\x19\xd0\xbd\xe3\xee\xfcDg\xacwu\xd3\x04e'+ \
369 '\xea\xa4\x95\x14\x9a\x08\xc7\x88{\x12\x00k\x83m\xc1\xd4\\V\xbd#\x9b\x119_*\xf9<?\x19\xbc\x0c\x00'+ \
370 '\x10\xdfN\xb3\x8a\x05\x03\x1ax\x07OH\x17/"\xaeY\x1b\xf0\xdd\xed\xdc.\x9e2\xdf\xca*\xa94\x01\x00\n'+ \
371 '\x88\x99\xc8\x000{\x9c\x82\x02\x96\xab\xa3\xf0\xdf\xb9\xa6\xae!\xe3\x9f#\xfe\r\x16\x96S\xf8*\xaf'+ \
372 '\xac\xc7\x05\x00XA\xd1\xa0\x8d\x01\x13\xf6\xcb{\x1ce\xfae\x13U\x9a\x00\x00\xaf\xf1\x0e\x1e\xa4`\x07'+ \
373 '\xcb\xb1\xf0u\xf3Me|\x0b\xf1%\xc1#\x06\x85\x87xeu\x94\x9a#\xfe\xc22\x03\xc6e\x06d?\xba\xfb\xf8\x99a?'+ \
374 '\xb7\xa2\xc8*\xa94\x01\x80\xf341\x0f\x02\x00\xd6Sp\x8a\x9d\x13\xfa(\xbc\xc3\xfc\xa02\xbe\xdf\xb2\xd3'+ \
375 '\x9d\xe1\x95-\x15t\x186\x06\xf8\xac\r\xf0~|\xcd|\x93\x80\xac\x92J\x13\x00T\x08\x87\xc0\x04v,\xbc\x8b'+ \
376 '\x92#\x14\xa5\x9a\x1f\xd4\x867\xf7\\^Y\n\xdb\x86N\xc7\x80\xe2\xf3\x16w\x91URi\x02\x80O\x89\xd8\xcb\xc2}'+ \
377 '\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'+ \
378 '\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'+ \
379 '\x19\x11\x1c\xd3"3\xc0`\xcdh>?\xe1\xb0\xd0N\x93m*\xc959U_\xc0\x88\xff\x8d\x82*sV\xc6\xff\xce\xbe0\xce'+ \
380 '\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'+ \
381 '\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|'+ \
382 '\xed\x99l~\xd6\xe7P\xf1\xf3\xd8\xed_\t\xfdZ\xcd\x13F\x04\x06\xb0\xa3U~H#;\x99\xbe\r\x00xB\xf8\xcf\xbdJ'+ \
383 '\xe3\x87\x00\x00\x0f\x1b6\x95T\x9a\xe0>G\xc9Z\xfe\xe5\'\xea\xac\x0b\x00v\xb3\xea\xe7_Z\xecqeW|0\xc2'+ \
384 '\xae\x94\xf1\x9f1\xfe\xc8\xfa\x9cY1\xde\xd5/\xb7\x87\xa4)\r\xf8\x83\xa2\x96\xa2X\x88\xcbQ`\xbb7\xb6\xf0]'+ \
385 '\xbf\xf0\x94\'h|isz\xcc\x82F\xbfaWI\xa1\tU\xac\x9ffr\x06\xa4\xb3=\xc6=\x00\x90y\xd6\xba{\xca\xf8\x9c\x8b'+ \
386 '\xd2\x86\xab4\xe0@\xd8\x055\xf2\xde\xdd\xa0j\xeb\xe1\x95\x14\x9a\xf0\xb5\xe9\xf0\x03\x008B\xe4W\x00\x80eW'+ \
387 '\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'+ \
388 '\r\x00(\xe9\xb3^?e\xfc\x9d\xe6w\xa3\xbb2\x02\x03b:\xc3j\xad\x18\x0f\x11\xfb]\xc2\xf4u\xdc\x19\xe0P\x9cm%'+ \
389 '\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$'+ \
390 '\xfb\x00\xe4v\x84\xd5\xaa\x08nx}/8L\xd3\xef\x9b\xfc\xc6\xd1\x1c\x17A%\x89&\x07\xba\x16\xd3\xaa^$.f\xdd'+ \
391 '\x85\x00\x80\xae\xe0\x01*~\xed\x9a\x92\xdc4\xf7\xd8\xe0\xc9\x9e\xf6\x1f\xb9\xaf\xbf2>\xf9\xde\xb2E7'+ \
392 '\xa6\xb8\xae\x9e\xfe\xf5h\xcb\x80yawX\x87pWW/\x99\xe3q\xf2djmUa\xcah\xef\xf7M\x03a\xd3S7W\x15&\x8f'+ \
393 '\xfeu\xb4\xf9\x97\x88*\xc94ihhhhhhhhhhhhhhhD\x19\xfe\x03\xec\xf7\xad7pH,\x9f\x00\x00\x00\x00IEND\xaeB`\x82'
395 # determine the next power of two
398 while res
< x
: res
<<= 1
401 # convert boolean value to string
406 # extract a number at the beginning of a string
410 while s
[0] in "0123456789":
418 # get a representative subset of file statistics
419 def my_stat(filename
):
421 s
= os
.stat(filename
)
424 return (s
.st_size
, s
.st_mtime
, s
.st_ctime
, s
.st_mode
)
426 # determine (pagecount,width,height) of a PDF file
427 def analyze_pdf(filename
):
428 f
= file(filename
,"rb")
431 box
= map(float, pdf
.split("/MediaBox",1)[1].split("]",1)[0].split("[",1)[1].strip().split())
432 return (max(map(num
, pdf
.split("/Count")[1:])), box
[2]-box
[0], box
[3]-box
[1])
434 # unescape { literals in PDF files
435 re_unescape
= re
.compile(r
'&#[0-9]+;')
436 def decode_literal(m
):
438 return chr(int(m
.group(0)[2:-1]))
442 return re_unescape
.sub(decode_literal
, s
)
445 def pdftkParse(filename
, page_offset
=0):
446 f
= file(filename
, "r")
451 for line
in f
.xreadlines():
453 key
, value
= [item
.strip() for item
in line
.split(':', 1)]
457 if key
== "numberofpages":
459 elif key
== "infokey":
460 InfoKey
= value
.lower()
461 elif (key
== "infovalue") and (InfoKey
== "title"):
462 Title
= unescape_pdf(value
)
464 elif key
== "bookmarktitle":
465 BookmarkTitle
= unescape_pdf(value
)
466 elif key
== "bookmarkpagenumber" and BookmarkTitle
:
469 if not GetPageProp(page
+ page_offset
, '_title'):
470 SetPageProp(page
+ page_offset
, '_title', BookmarkTitle
)
476 SetPageProp(page_offset
+ 1, '_overview', True)
477 for page
in xrange(page_offset
+ 2, page_offset
+ Pages
):
478 SetPageProp(page
, '_overview', \
479 not(not(GetPageProp(page
+ AutoOverview
- 1, '_title'))))
480 SetPageProp(page_offset
+ Pages
, '_overview', True)
481 return (Title
, Pages
)
483 # translate pixel coordinates to normalized screen coordinates
484 def MouseToScreen(mousepos
):
485 return (ZoomX0
+ mousepos
[0] * ZoomArea
/ ScreenWidth
,
486 ZoomY0
+ mousepos
[1] * ZoomArea
/ ScreenHeight
)
488 # normalize rectangle coordinates so that the upper-left point comes first
489 def NormalizeRect(X0
, Y0
, X1
, Y1
):
490 return (min(X0
, X1
), min(Y0
, Y1
), max(X0
, X1
), max(Y0
, Y1
))
492 # check if a point is inside a box (or a list of boxes)
493 def InsideBox(x
, y
, box
):
494 return (x
>= box
[0]) and (y
>= box
[1]) and (x
< box
[2]) and (y
< box
[3])
495 def FindBox(x
, y
, boxes
):
496 for i
in xrange(len(boxes
)):
497 if InsideBox(x
, y
, boxes
[i
]):
501 # zoom an image size to a destination size, preserving the aspect ratio
502 def ZoomToFit(size
, dest
=None):
504 dest
= (ScreenWidth
, ScreenHeight
)
506 newy
= size
[1] * newx
/ size
[0]
509 newx
= size
[0] * newy
/ size
[1]
512 # get the overlay grid screen coordinates for a specific page
513 def OverviewPos(page
):
515 int(page
% OverviewGridSize
) * OverviewCellX
+ OverviewOfsX
, \
516 int(page
/ OverviewGridSize
) * OverviewCellY
+ OverviewOfsY \
520 global MPlayerPID
, VideoPlaying
521 if not MPlayerPID
: return
524 win32api
.TerminateProcess(MPlayerPID
, 0)
526 os
.kill(MPlayerPID
, 2)
532 def FormatTime(t
, minutes
=False):
533 if minutes
and (t
< 3600):
534 return "%d min" % (t
/ 60)
536 return "%d:%02d" % (t
/ 3600, (t
/ 60) % 60)
538 return "%d:%02d" % (t
/ 60, t
% 60)
541 return "%d:%02d:%02d" % (t
/ 3600, ms
/ 60, ms
% 60)
543 def SafeCall(func
, args
=[], kwargs
={}):
544 if not func
: return None
546 return func(*args
, **kwargs
)
548 print >>sys
.stderr
, "----- Exception in user function ----"
549 traceback
.print_exc(file=sys
.stderr
)
550 print >>sys
.stderr
, "----- End of traceback -----"
553 print >>sys
.stderr
, "Total presentation time: %s." % \
554 FormatTime((pygame
.time
.get_ticks() - StartTime
) / 1000)
558 ##### RENDERING TOOL CODE ######################################################
560 # draw a fullscreen quad
563 glTexCoord2d( 0.0, 0.0); glVertex2i(0, 0)
564 glTexCoord2d(TexMaxS
, 0.0); glVertex2i(1, 0)
565 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2i(1, 1)
566 glTexCoord2d( 0.0, TexMaxT
); glVertex2i(0, 1)
569 # draw a generic 2D quad
570 def DrawQuad(x0
=0.0, y0
=0.0, x1
=1.0, y1
=1.0):
572 glTexCoord2d( 0.0, 0.0); glVertex2d(x0
, y0
)
573 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(x1
, y0
)
574 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(x1
, y1
)
575 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(x0
, y1
)
578 # helper function: draw a translated fullscreen quad
579 def DrawTranslatedFullQuad(dx
, dy
, i
, a
):
580 glColor4d(i
, i
, i
, a
)
582 glTranslated(dx
, dy
, 0.0)
586 # draw a vertex in normalized screen coordinates,
587 # setting texture coordinates appropriately
589 glTexCoord2d(x
*TexMaxS
, y
* TexMaxT
)
591 def DrawPointEx(x
, y
, a
):
592 glColor4d(1.0, 1.0, 1.0, a
)
593 glTexCoord2d(x
* TexMaxS
, y
* TexMaxT
)
596 # a mesh transformation function: it gets the relative transition time (in the
597 # [0.0,0.1) interval) and the normalized 2D screen coordinates, and returns a
598 # 7-tuple containing the desired 3D screen coordinates, 2D texture coordinates,
599 # and intensity/alpha color values.
600 def meshtrans_null(t
, u
, v
):
601 return (u
, v
, 0.0, u
, v
, 1.0, t
)
602 # (x, y, z, s, t, i, a)
604 # draw a quad, applying a mesh transformation function
605 def DrawMeshQuad(time
=0.0, f
=meshtrans_null
):
606 line0
= [f(time
, u
* MeshStepX
, 0.0) for u
in xrange(MeshResX
+ 1)]
607 for v
in xrange(1, MeshResY
+ 1):
608 line1
= [f(time
, u
* MeshStepX
, v
* MeshStepY
) for u
in xrange(MeshResX
+ 1)]
609 glBegin(GL_QUAD_STRIP
)
610 for col
in zip(line0
, line1
):
611 for x
, y
, z
, s
, t
, i
, a
in col
:
612 glColor4d(i
, i
, i
, a
)
613 glTexCoord2d(s
* TexMaxS
, t
* TexMaxT
)
618 def GenerateSpotMesh():
620 rx0
= SpotRadius
* PixelX
621 ry0
= SpotRadius
* PixelY
622 rx1
= (SpotRadius
+ BoxEdgeSize
) * PixelX
623 ry1
= (SpotRadius
+ BoxEdgeSize
) * PixelY
624 steps
= max(6, int(2.0 * pi
* SpotRadius
/ SpotDetail
/ ZoomArea
))
625 SpotMesh
=[(rx0
* sin(a
), ry0
* cos(a
), rx1
* sin(a
), ry1
* cos(a
)) for a
in \
626 [i
* 2.0 * pi
/ steps
for i
in range(steps
+ 1)]]
629 ##### TRANSITIONS ##############################################################
631 # Each transition is represented by a class derived from Accentuate.Transition
632 # The interface consists of only two methods: the __init__ method may perform
633 # some transition-specific initialization, and render() finally renders a frame
634 # of the transition, using the global texture identifierst Tcurrent and Tnext.
636 # Transition itself is an abstract class
637 class AbstractError(StandardError):
645 # an array containing all possible transition classes
648 # a helper function doing the common task of directly blitting a background page
649 def DrawPageDirect(tex
):
651 glBindTexture(TextureTarget
, tex
)
655 # a helper function that enables alpha blending
656 def EnableAlphaBlend():
658 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
661 # Crossfade: one of the simplest transition you can think of :)
662 class Crossfade(Transition
):
663 """simple crossfade"""
665 DrawPageDirect(Tcurrent
)
667 glBindTexture(TextureTarget
, Tnext
)
668 glColor4d(1, 1, 1, t
)
670 AllTransitions
.append(Crossfade
)
673 # Slide: a class of transitions that simply slide the new page in from one side
674 # after an idea from Joachim B Haga
675 class Slide(Transition
):
679 cx
, cy
, nx
, ny
= self
.origin(t
)
680 glBindTexture(TextureTarget
, Tcurrent
)
681 DrawQuad(cx
, cy
, cx
+1.0, cy
+1.0)
682 glBindTexture(TextureTarget
, Tnext
)
683 DrawQuad(nx
, ny
, nx
+1.0, ny
+1.0)
685 class SlideLeft(Slide
):
686 """Slide to the left"""
687 def origin(self
, t
): return (-t
, 0.0, 1.0-t
, 0.0)
688 class SlideRight(Slide
):
689 """Slide to the right"""
690 def origin(self
, t
): return (t
, 0.0, t
-1.0, 0.0)
691 class SlideUp(Slide
):
693 def origin(self
, t
): return (0.0, -t
, 0.0, 1.0-t
)
694 class SlideDown(Slide
):
695 """Slide downwards"""
696 def origin(self
, t
): return (0.0, t
, 0.0, t
-1.0)
697 AllTransitions
.extend([SlideLeft
, SlideRight
, SlideUp
, SlideDown
])
700 # Squeeze: a class of transitions that squeeze the new page in from one size
701 class Squeeze(Transition
):
704 def inv(self
): return 0
706 cx1
, cy1
, nx0
, ny0
= self
.params(t
)
708 t1
, t2
= (Tnext
, Tcurrent
)
710 t1
, t2
= (Tcurrent
, Tnext
)
711 glBindTexture(TextureTarget
, t1
)
712 DrawQuad(0.0, 0.0, cx1
, cy1
)
713 glBindTexture(TextureTarget
, t2
)
714 DrawQuad(nx0
, ny0
, 1.0, 1.0)
715 class SqueezeHorizontal(Squeeze
):
716 def split(self
, t
): raise AbstractError
719 return (t
, 1.0, t
, 0.0)
720 class SqueezeVertical(Squeeze
):
721 def split(self
, t
): raise AbstractError
724 return (1.0, t
, 0.0, t
)
726 class SqueezeLeft(SqueezeHorizontal
):
727 """Squeeze to the left"""
728 def split(self
, t
): return 1.0 - t
729 class SqueezeRight(SqueezeHorizontal
):
730 """Squeeze to the right"""
731 def split(self
, t
): return t
732 def inv(self
): return 1
733 class SqueezeUp(SqueezeVertical
):
734 """Squeeze upwards"""
735 def split(self
, t
): return 1.0 - t
736 class SqueezeDown(SqueezeVertical
):
737 """Squeeze downwards"""
738 def split(self
, t
): return t
739 def inv(self
): return 1
740 AllTransitions
.extend([SqueezeLeft
, SqueezeRight
, SqueezeUp
, SqueezeDown
])
743 # Wipe: a class of transitions that softly "wipe" the new image over the old
744 # one along a path specified by a gradient function that maps normalized screen
745 # coordinates to a number in the range [0.0,1.0]
747 class Wipe(Transition
):
748 def grad(self
, u
, v
):
751 pos
= (g
- self
.Wipe_start
) / WipeWidth
752 return max(min(pos
, 1.0), 0.0)
754 DrawPageDirect(Tnext
)
756 glBindTexture(TextureTarget
, Tcurrent
)
757 self
.Wipe_start
= t
* (1.0 + WipeWidth
) - WipeWidth
758 DrawMeshQuad(t
, lambda t
, u
, v
: \
759 (u
, v
, 0.0, u
,v
, 1.0, self
.afunc(self
.grad(u
, v
))))
761 class WipeDown(Wipe
):
763 def grad(self
, u
, v
): return v
766 def grad(self
, u
, v
): return 1.0 - v
767 class WipeRight(Wipe
):
768 """wipe from left to right"""
769 def grad(self
, u
, v
): return u
770 class WipeLeft(Wipe
):
771 """wipe from right to left"""
772 def grad(self
, u
, v
): return 1.0 - u
773 class WipeDownRight(Wipe
):
774 """wipe from the upper-left to the lower-right corner"""
775 def grad(self
, u
, v
): return 0.5 * (u
+ v
)
776 class WipeUpLeft(Wipe
):
777 """wipe from the lower-right to the upper-left corner"""
778 def grad(self
, u
, v
): return 1.0 - 0.5 * (u
+ v
)
779 class WipeCenterOut(Wipe
):
780 """wipe from the center outwards"""
781 def grad(self
, u
, v
):
784 return sqrt(u
* u
* 1.777 + v
* v
) / 0.833
785 class WipeCenterIn(Wipe
):
786 """wipe from the edges inwards"""
787 def grad(self
, u
, v
):
790 return 1.0 - sqrt(u
* u
* 1.777 + v
* v
) / 0.833
791 AllTransitions
.extend([WipeDown
, WipeUp
, WipeRight
, WipeLeft
, \
792 WipeDownRight
, WipeUpLeft
, WipeCenterOut
, WipeCenterIn
])
794 class WipeBlobs(Wipe
):
795 """wipe using nice \"blob\"-like patterns"""
797 self
.uscale
= (5.0 + random
.random() * 15.0) * 1.333
798 self
.vscale
= 5.0 + random
.random() * 15.0
799 self
.uofs
= random
.random() * 6.2
800 self
.vofs
= random
.random() * 6.2
802 return 0.5 + 0.25 * (cos(self
.uofs
+ u
* self
.uscale
) \
803 + cos(self
.vofs
+ v
* self
.vscale
))
804 AllTransitions
.append(WipeBlobs
)
806 class PagePeel(Transition
):
807 """an unrealistic, but nice page peel effect"""
810 glBindTexture(TextureTarget
, Tnext
)
811 DrawMeshQuad(t
, lambda t
, u
, v
: \
812 (u
, v
, 0.0, u
, v
, 1.0 - 0.5 * (1.0 - u
) * (1.0 - t
), 1.0))
814 glBindTexture(TextureTarget
, Tcurrent
)
815 DrawMeshQuad(t
, lambda t
, u
, v
: \
816 (u
* (1.0 - t
), 0.5 + (v
- 0.5) * (1.0 + u
* t
) * (1.0 + u
* t
), 0.0,
817 u
, v
, 1.0 - u
* t
* t
, 1.0))
818 AllTransitions
.append(PagePeel
)
820 ### additional transition by Ronan Le Hy <rlehy@free.fr> ###
822 class PageTurn(Transition
):
823 """another page peel effect, slower but more realistic than PagePeel"""
825 alpha_square
= alpha
* alpha
827 inv_sqrt_two
= 1. / sqrt(2.)
828 def warp(self
, t
, u
, v
):
829 # distance from the 2d origin to the folding line
830 dpt
= PageTurn
.sqrt_two
* (1.0 - t
)
831 # distance from the 2d origin to the projection of (u,v) on the folding line
832 d
= PageTurn
.inv_sqrt_two
* (u
+ v
)
834 # the smaller rho is, the closer to asymptotes are the x(u) and y(v) curves
835 # ie, smaller rho => neater fold
837 common_sq
= sqrt(4. - 8 * t
- 4.*(u
+v
) + 4.*t
*(t
+ v
+ u
) + (u
+v
)*(u
+v
) + 4 * rho
) / 2.
838 x
= 1. - t
+ 0.5 * (u
- v
) - common_sq
839 y
= 1. - t
+ 0.5 * (v
- u
) - common_sq
840 z
= - 0.5 * (PageTurn
.alpha
* dmdpt
+ sqrt(PageTurn
.alpha_square
* dmdpt
*dmdpt
+ 4))
842 # part of the sheet still flat on the screen: lit and opaque
846 # part of the sheet in the air, after the fold: shadowed and transparent
847 # z goes from -0.8 to -2 approximately
849 alpha
= 0.5 * z
+ 1.5
850 # the corner of the page that you hold between your fingers
851 dthumb
= 0.6 * u
+ 1.4 * v
- 2 * 0.95
858 return (x
,y
,z
, u
,v
, i
, alpha
)
861 glBindTexture(TextureTarget
, Tnext
)
862 DrawMeshQuad(t
,lambda t
, u
, v
: \
863 (u
, v
, 0.0, u
, v
, 1.0 - 0.5 * (1.0 - u
) * (1.0 - t
), 1.0))
865 glBindTexture(TextureTarget
, Tcurrent
)
866 DrawMeshQuad(t
, self
.warp
)
867 AllTransitions
.append(PageTurn
)
869 ##### some additional transitions by Rob Reid <rreid@drao.nrc.ca> #####
871 class Dissipate(Transition
):
872 """Sort of, but not quite, like blowing a bubble."""
874 glBindTexture(TextureTarget
,Tcurrent
)
876 DrawMeshQuad(t
,lambda t
,u
,v
: (u
, v
, t
, 0.5 + scalfact
* (u
- 0.5),\
877 0.5 + scalfact
* (v
- 0.5), 1.0, \
878 scalfact
* scalfact
))
880 glBindTexture(TextureTarget
,Tnext
)
883 AllTransitions
.append(Dissipate
)
885 class OldOutNewIn(Transition
):
886 """Parent class for transitions that do something with the current slide for
887 the first half, and then something with the next slide for the second half."""
888 def ffdmq(self
, t
, u
, v
):
889 """Function for use by DrawMeshQuad()."""
895 glBindTexture(TextureTarget
, Tcurrent
)
897 glBindTexture(TextureTarget
, Tnext
)
898 DrawMeshQuad(t
, self
.ffdmq
)
900 class ZoomOutIn(OldOutNewIn
):
901 """Zooms the current page out, and the next one in."""
902 def ffdmq(self
, t
, u
, v
):
903 scalfact
= abs(1.0 - 2.0 * t
)
904 return (0.5 + scalfact
* (u
- 0.5), 0.5 + scalfact
* (v
- 0.5), 0.0, \
906 AllTransitions
.append(ZoomOutIn
)
908 class SpinOutIn(OldOutNewIn
):
909 """Spins the current page out, and the next one in. Inspired by a classic
911 def ffdmq(self
, t
, u
, v
):
912 scalfact
= abs(1.0 - 2.0 * t
)
913 sa
= scalfact
* sin(16.0 * t
)
914 ca
= scalfact
* cos(16.0 * t
)
915 return (0.5 + ca
* (u
- 0.5) - 0.75 * sa
* (v
- 0.5),\
916 0.5 + 1.333 * sa
* (u
- 0.5) + ca
* (v
- 0.5),\
918 AllTransitions
.append(SpinOutIn
)
920 class SpiralOutIn(OldOutNewIn
):
921 """Flushes the current page away, only to have the next one overflow!"""
922 def ffdmq(self
, t
, u
, v
):
923 scalfact
= abs(1.0 - 2.0 * t
)
924 sa
= scalfact
* sin(16.0 * t
)
925 ca
= scalfact
* cos(16.0 * t
)
926 return (0.5 + sa
+ ca
* (u
- 0.5) - 0.75 * sa
* (v
- 0.5),\
927 0.5 + ca
+ 1.333 * sa
* (u
- 0.5) + ca
* (v
- 0.5),\
929 AllTransitions
.append(SpiralOutIn
)
931 class SlideCarousel(OldOutNewIn
):
932 """Old school transition accidentally made while working on SpinOutIn."""
933 def ffdmq(self
, t
, u
, v
):
936 scalfact
= 0.5 + 0.5 * abs(1.0 - 2.0 * t
)
937 const
= 0.5 - 0.5 * scalfact
938 return (ca
* (const
+ scalfact
* u
) - sa
* (const
+ scalfact
* v
), \
939 sa
* (const
+ scalfact
* u
) + ca
* (const
+ scalfact
* v
), \
941 AllTransitions
.append(SlideCarousel
)
943 class FlipBoardVert(OldOutNewIn
):
944 """Should look something like the spinning bookcase to the secret room..."""
945 def ffdmq(self
, t
, u
, v
):
950 x
= 0.5 + (u
- 0.5) * c
953 x
= 0.5 - (u
- 0.5) * c
956 return (x
, v
, z
, u
, v
, 1.0, 1.0)
957 AllTransitions
.append(FlipBoardVert
)
959 class FlipBoardHori(OldOutNewIn
):
960 """Some blackboards and whiteboards do this..."""
961 def ffdmq(self
, t
, u
, v
):
966 y
= 0.5 + (v
- 0.5) * c
969 y
= 0.5 - (v
- 0.5) * c
972 return (u
, y
, z
, u
, v
, 1.0, 1.0)
973 AllTransitions
.append(FlipBoardHori
)
975 def verticalblindold(t
, u
, v
):
977 umuaxis
= (u
% 0.2) - 0.1
979 return (uaxis
+ umuaxis
* cos(pit
), v
, umuaxis
* sin(pit
), u
, v
, 1.0, 1.0)
981 def verticalblindnew(t
, u
, v
):
982 vmvaxis
= (v
% 0.2) - 0.1
984 return (u
, vaxis
- vmvaxis
* cos(3.1415626 * t
),\
985 -vmvaxis
* sin(3.1415626 * t
), u
, v
, 1.0, 1.0)
987 class VerticalBlinds(Transition
):
988 """Vertical Venetian Blinds"""
993 glBindTexture(TextureTarget
,Tcurrent
)
994 DrawMeshQuad(t
, verticalblindold
)
996 glBindTexture(TextureTarget
,Tnext
)
997 DrawMeshQuad(1.0 - t
, verticalblindold
)
998 AllTransitions
.append(VerticalBlinds
)
1000 # the AvailableTransitions array contains a list of all transition classes that
1001 # can be randomly assigned to pages
1002 AvailableTransitions
=[ # from coolest to lamest
1003 # PagePeel, # deactivated: too intrusive
1005 WipeCenterOut
,WipeCenterIn
,
1006 WipeDownRight
,WipeUpLeft
,WipeDown
,WipeUp
,WipeRight
,WipeLeft
,
1011 ##### OSD FONT RENDERER ########################################################
1013 # force a string or sequence of ordinals into a unicode string
1014 def ForceUnicode(s
, charset
='iso8859-15'):
1015 if type(s
) == types
.UnicodeType
:
1017 if type(s
) == types
.StringType
:
1018 return unicode(s
, charset
, 'ignore')
1019 if type(s
) in (types
.TupleType
, types
.ListType
):
1020 return u
''.join(map(unichr, s
))
1021 raise TypeError, "string argument not convertible to Unicode"
1023 # search a system font path for a font file
1024 def SearchFont(root
, name
):
1025 if not os
.path
.isdir(root
):
1029 while (len(infix
) < 10) and (len(fontfile
) != 1):
1030 fontfile
= filter(os
.path
.isfile
, glob
.glob(root
+ infix
+ name
))
1032 if len(fontfile
) != 1:
1037 # load a system font
1038 def LoadFont(dirs
, name
, size
):
1039 # first try to load the font directly
1041 return ImageFont
.truetype(name
, size
, encoding
='unic')
1044 # no need to search further on Windows
1047 # start search for the font
1049 fontfile
= SearchFont(dir + "/", name
)
1052 return ImageFont
.truetype(fontfile
, size
, encoding
='unic')
1057 # alignment constants
1065 # font renderer class
1067 def __init__(self
, width
, height
, name
, size
, search_path
=[], default_charset
='iso8859-15', extend
=1, blur
=1):
1069 self
.height
= height
1070 self
._i
_extend
= range(extend
)
1071 self
._i
_blur
= range(blur
)
1072 self
.feather
= extend
+ blur
+ 1
1078 self
.line_height
= 0
1079 self
.default_charset
= default_charset
1080 if type(name
) == types
.StringType
:
1081 self
.font
= LoadFont(search_path
, name
, size
)
1083 for check_name
in name
:
1084 self
.font
= LoadFont(search_path
, check_name
, size
)
1087 raise IOError, "font file not found"
1088 self
.img
= Image
.new('LA', (width
, height
))
1089 self
.alpha
= Image
.new('L', (width
, height
))
1090 self
.extend
= ImageFilter
.MaxFilter()
1091 self
.blur
= ImageFilter
.Kernel((3, 3), [1,2,1,2,4,2,1,2,1])
1092 self
.tex
= glGenTextures(1)
1093 glBindTexture(GL_TEXTURE_2D
, self
.tex
)
1094 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
)
1095 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
)
1096 self
.AddString(range(32, 128))
1098 def AddCharacter(self
, c
):
1099 w
, h
= self
.font
.getsize(c
)
1100 self
.line_height
= max(self
.line_height
, h
)
1101 size
= (w
+ 2 * self
.feather
, h
+ 2 * self
.feather
)
1102 glyph
= Image
.new('L', size
)
1103 draw
= ImageDraw
.Draw(glyph
)
1104 draw
.text((self
.feather
, self
.feather
), c
, font
=self
.font
, fill
=255)
1107 box
= self
.AllocateGlyphBox(*size
)
1108 self
.img
.paste(glyph
, (box
.orig_x
, box
.orig_y
))
1110 for i
in self
._i
_extend
: glyph
= glyph
.filter(self
.extend
)
1111 for i
in self
._i
_blur
: glyph
= glyph
.filter(self
.blur
)
1112 self
.alpha
.paste(glyph
, (box
.orig_x
, box
.orig_y
))
1118 def AddString(self
, s
, charset
=None, fail_silently
=False):
1121 for c
in ForceUnicode(s
, self
.GetCharset(charset
)):
1122 if c
in self
.widths
:
1124 self
.AddCharacter(c
)
1131 if not update_count
: return
1132 self
.img
.putalpha(self
.alpha
)
1133 glBindTexture(GL_TEXTURE_2D
, self
.tex
)
1134 glTexImage2D(GL_TEXTURE_2D
, 0, GL_LUMINANCE_ALPHA
, \
1135 self
.width
, self
.height
, 0, \
1136 GL_LUMINANCE_ALPHA
, GL_UNSIGNED_BYTE
, self
.img
.tostring())
1138 def AllocateGlyphBox(self
, w
, h
):
1139 if self
.current_x
+ w
> self
.width
:
1141 self
.current_y
+= self
.max_height
1143 if self
.current_y
+ h
> self
.height
:
1144 raise ValueError, "bitmap too small for all the glyphs"
1145 box
= self
.GlyphBox()
1146 box
.orig_x
= self
.current_x
1147 box
.orig_y
= self
.current_y
1150 box
.x0
= self
.current_x
/ float(self
.width
)
1151 box
.y0
= self
.current_y
/ float(self
.height
)
1152 box
.x1
= (self
.current_x
+ w
) / float(self
.width
)
1153 box
.y1
= (self
.current_y
+ h
) / float(self
.height
)
1154 box
.dsx
= w
* PixelX
1155 box
.dsy
= h
* PixelY
1157 self
.max_height
= max(self
.max_height
, h
)
1160 def GetCharset(self
, charset
=None):
1161 if charset
: return charset
1162 return self
.default_charset
1164 def SplitText(self
, s
, charset
=None):
1165 return ForceUnicode(s
, self
.GetCharset(charset
)).split(u
'\n')
1167 def GetLineHeight(self
):
1168 return self
.line_height
1170 def GetTextWidth(self
, s
, charset
=None):
1171 return max([self
.GetTextWidthEx(line
) for line
in self
.SplitText(s
, charset
)])
1173 def GetTextHeight(self
, s
, charset
=None):
1174 return len(self
.SplitText(s
, charset
)) * self
.line_height
1176 def GetTextSize(self
, s
, charset
=None):
1177 lines
= self
.SplitText(s
, charset
)
1178 return (max([self
.GetTextWidthEx(line
) for line
in lines
]), len(lines
) * self
.line_height
)
1180 def GetTextWidthEx(self
, u
):
1181 if u
: return sum([self
.widths
.get(c
, 0) for c
in u
])
1184 def GetTextHeightEx(self
, u
=[]):
1185 return self
.line_height
1187 def AlignTextEx(self
, x
, u
, align
=Left
):
1188 if not align
: return x
1189 return x
- (self
.GetTextWidthEx(u
) / align
)
1191 def Draw(self
, origin
, text
, charset
=None, align
=Left
, color
=(1.0, 1.0, 1.0), alpha
=1.0, beveled
=True):
1192 lines
= self
.SplitText(text
, charset
)
1196 glEnable(GL_TEXTURE_2D
)
1198 glBindTexture(GL_TEXTURE_2D
, self
.tex
)
1200 glBlendFunc(GL_ZERO
, GL_ONE_MINUS_SRC_ALPHA
)
1201 glColor4d(0.0, 0.0, 0.0, alpha
)
1202 self
.DrawLinesEx(x0
, y0
, lines
, align
)
1203 glBlendFunc(GL_ONE
, GL_ONE
)
1204 glColor3d(color
[0] * alpha
, color
[1] * alpha
, color
[2] * alpha
)
1205 self
.DrawLinesEx(x0
, y0
, lines
, align
)
1207 glDisable(GL_TEXTURE_2D
)
1209 def DrawLinesEx(self
, x0
, y
, lines
, align
=Left
):
1210 global PixelX
, PixelY
1214 x
= self
.AlignTextEx(x0
, line
, align
)
1216 if not c
in self
.widths
: continue
1217 self
.boxes
[c
].render(x
* PixelX
, sy
)
1219 y
+= self
.line_height
1223 def render(self
, sx
=0.0, sy
=0.0):
1224 glTexCoord2d(self
.x0
, self
.y0
); glVertex2d(sx
, sy
)
1225 glTexCoord2d(self
.x0
, self
.y1
); glVertex2d(sx
, sy
+self
.dsy
)
1226 glTexCoord2d(self
.x1
, self
.y1
); glVertex2d(sx
+self
.dsx
, sy
+self
.dsy
)
1227 glTexCoord2d(self
.x1
, self
.y0
); glVertex2d(sx
+self
.dsx
, sy
)
1229 # high-level draw function
1230 def DrawOSD(x
, y
, text
, halign
=Auto
, valign
=Auto
, alpha
=1.0):
1231 if not(OSDFont
) or not(text
) or (alpha
<= 0.004): return
1232 if alpha
> 1.0: alpha
= 1.0
1246 y
-= OSDFont
.GetLineHeight() / valign
1247 if TextureTarget
!= GL_TEXTURE_2D
:
1248 glDisable(TextureTarget
)
1249 OSDFont
.Draw((x
, y
), text
, align
=halign
, alpha
=alpha
)
1251 # very high-level draw function
1252 def DrawOSDEx(position
, text
, alpha_factor
=1.0):
1253 xpos
= position
>> 1
1254 y
= (1 - 2 * (position
& 1)) * OSDMargin
1256 x
= (1 - 2 * xpos
) * OSDMargin
1261 DrawOSD(x
, y
, text
, halign
, alpha
= OSDAlpha
* alpha_factor
)
1264 ##### PDF PARSER ###############################################################
1266 class PDFError(Exception):
1270 def __init__(self
, ref
):
1273 return "PDFref(%d)" % self
.ref
1275 re_pdfstring
= re
.compile(r
'\(\)|\(.*?[^\\]\)')
1276 pdfstringrepl
= [("\\"+x
[0], x
[1:]) for x
in "(( )) n\n r\r t\t".split(" ")]
1277 def pdf_maskstring(s
):
1279 for a
, b
in pdfstringrepl
:
1281 return " <" + "".join(["%02X"%ord(c
) for c
in s
]) + "> "
1282 def pdf_mask_all_strings(s
):
1283 return re_pdfstring
.sub(lambda x
: pdf_maskstring(x
.group(0)), s
)
1284 def pdf_unmaskstring(s
):
1285 return "".join([chr(int(s
[i
:i
+2], 16)) for i
in xrange(1, len(s
)-1, 2)])
1288 def __init__(self
, filename
):
1289 self
.f
= file(filename
, "rb")
1291 # find the first cross-reference table
1293 filesize
= self
.f
.tell()
1294 self
.f
.seek(filesize
- 128)
1295 trailer
= self
.f
.read()
1296 i
= trailer
.rfind("startxref")
1298 raise PDFError
, "cross-reference table offset missing"
1300 offset
= int(trailer
[i
:].split("\n")[1].strip())
1301 except (IndexError, ValueError):
1302 raise PDFError
, "malformed cross-reference table offset"
1304 # follow the trailer chain
1308 self
.xref
, rootref
, offset
= self
.parse_trailer(offset
)
1309 self
.xref
.update(newxref
)
1311 # scan the page tree
1317 root
= self
.getobj(rootref
, 'Catalog')
1319 self
.scan_page_tree(root
['Pages'].ref
)
1321 raise PDFError
, "root page tree node missing"
1325 line
= self
.f
.readline().strip()
1326 if line
: return line
1328 def find_length(self
, tokens
, begin
, end
):
1330 for i
in xrange(1, len(tokens
)):
1331 if tokens
[i
] == begin
: level
+= 1
1332 if tokens
[i
] == end
: level
-= 1
1336 def parse_tokens(self
, tokens
, want_list
=False):
1342 if (len(tokens
) >= 3) and (tokens
[2] == 'R'):
1346 tlen
= self
.find_length(tokens
, "<<", ">>")
1347 v
= self
.parse_tokens(tokens
[1 : tlen
- 1], True)
1348 v
= dict(zip(v
[::2], v
[1::2]))
1350 tlen
= self
.find_length(tokens
, "[", "]")
1351 v
= self
.parse_tokens(tokens
[1 : tlen
- 1], True)
1352 elif not(t
) or (t
[0] == "null"):
1354 elif (t
[0] == '<') and (t
[-1] == '>'):
1355 v
= pdf_unmaskstring(t
)
1376 def parse(self
, data
):
1377 data
= pdf_mask_all_strings(data
)
1378 data
= data
.replace("<<", " << ").replace("[", " [ ").replace("(", " (")
1379 data
= data
.replace(">>", " >> ").replace("]", " ] ").replace(")", ") ")
1380 data
= data
.replace("/", " /")
1381 return self
.parse_tokens(filter(None, data
.split()))
1383 def getobj(self
, obj
, force_type
=None):
1384 offset
= self
.xref
.get(obj
, 0)
1386 raise PDFError
, "referenced non-existing PDF object"
1388 header
= self
.getline().split(None, 2)
1389 if (header
[-1] != "obj") or (header
[0] != str(obj
)):
1390 raise PDFError
, "object does not start where it's supposed to"
1393 line
= self
.getline()
1394 if line
in ("endobj", "stream"): break
1396 data
= self
.parse(" ".join(data
))
1400 except (KeyError, IndexError, ValueError):
1403 raise PDFError
, "object does not match the intended type"
1406 def parse_xref_section(self
, start
, count
):
1408 for obj
in xrange(start
, start
+ count
):
1409 line
= self
.getline()
1413 xref
[obj
] = int(line
[:10], 10)
1416 def parse_trailer(self
, offset
):
1421 if self
.getline() != "xref":
1422 raise PDFError
, "cross-reference table does not start where it's supposed to"
1423 return (xref
, rootref
, offset
) # no xref table found, abort
1424 # parse xref sections
1426 line
= self
.getline()
1427 if line
== "trailer": break
1428 start
, count
= map(int, line
.split())
1429 xref
.update(self
.parse_xref_section(start
, count
))
1432 line
= self
.getline()
1433 if line
in ("startxref", "%%EOF"): break
1434 if line
[0] != '/': continue
1435 parts
= line
[1:].split()
1436 if parts
[0] == 'Prev':
1437 offset
= int(parts
[1])
1438 if parts
[0] == 'Root':
1439 if (len(parts
) != 4) or (parts
[3] != 'R'):
1440 raise PDFError
, "root catalog entry is not a reference"
1441 rootref
= int(parts
[1])
1442 return (xref
, rootref
, offset
)
1444 def scan_page_tree(self
, obj
, mbox
=None, cbox
=None):
1445 node
= self
.getobj(obj
)
1446 if node
['Type'] == 'Pages':
1447 for kid
in node
['Kids']:
1448 self
.scan_page_tree(kid
.ref
, node
.get('MediaBox', mbox
), node
.get('CropBox', cbox
))
1450 page
= self
.page_count
+ 1
1451 self
.page_count
= page
1452 self
.obj2page
[obj
] = page
1453 self
.page2obj
[page
] = obj
1454 self
.annots
[page
] = [a
.ref
for a
in node
.get('Annots', [])]
1455 self
.box
[page
] = node
.get('CropBox', cbox
) or node
.get('MediaBox', mbox
)
1457 def dest2page(self
, dest
):
1458 if type(dest
) != types
.ListType
:
1460 elif dest
[0].__class
__ == PDFref
:
1461 return self
.obj2page
.get(dest
[0].ref
, None)
1465 def get_href(self
, obj
):
1466 node
= self
.getobj(obj
, 'Annot')
1467 if node
['Subtype'] != 'Link': return None
1470 dest
= self
.dest2page(node
['Dest'])
1472 action
= node
['A']['S']
1474 dest
= node
['A'].get('URI', None)
1475 elif action
== 'GoTo':
1476 dest
= self
.dest2page(node
['A'].get('D', None))
1478 return tuple(node
['Rect'] + [dest
])
1480 def GetHyperlinks(self
):
1482 for page
in self
.annots
:
1483 a
= filter(None, map(self
.get_href
, self
.annots
[page
]))
1488 def AddHyperlink(page_offset
, page
, target
, linkbox
, pagebox
):
1490 if type(target
) == types
.IntType
:
1491 target
+= page_offset
1492 w
= 1.0 / (pagebox
[2] - pagebox
[0])
1493 h
= 1.0 / (pagebox
[3] - pagebox
[1])
1495 # Hack to fix problems with landscape/powerdot files with hyperlinks
1502 x0
= (linkbox
[0] - pagebox
[0]) * w
1503 y0
= (pagebox
[3] - linkbox
[3]) * h
1504 x1
= (linkbox
[2] - pagebox
[0]) * w
1505 y1
= (pagebox
[3] - linkbox
[1]) * h
1507 href
= (0, target
, x0
, y0
, x1
, y1
)
1508 if GetPageProp(page
, '_href'):
1509 PageProps
[page
]['_href'].append(href
)
1511 SetPageProp(page
, '_href', [href
])
1514 def FixHyperlinks(page
):
1515 if not(GetPageProp(page
, '_box')) or not(GetPageProp(page
, '_href')):
1516 return # no hyperlinks or unknown page size
1517 bx0
, by0
, bx1
, by1
= GetPageProp(page
, '_box')
1521 for fixed
, target
, x0
, y0
, x1
, y1
in GetPageProp(page
, '_href'):
1523 href
.append((1, target
, x0
, y0
, x1
, y1
))
1525 href
.append((1, target
, \
1526 int(bx0
+ bdx
* x0
), int(by0
+ bdy
* y0
), \
1527 int(bx0
+ bdx
* x1
), int(by0
+ bdy
* y1
)))
1528 SetPageProp(page
, '_href', href
)
1531 def ParsePDF(filename
):
1533 assert 0 == spawn(os
.P_WAIT
, pdftkPath
, \
1534 ["pdftk", FileNameEscape
+ filename
+ FileNameEscape
, \
1535 "output", FileNameEscape
+ TempFileName
+ ".pdf" + FileNameEscape
,
1538 print >>sys
.stderr
, "Note: pdftk not found, hyperlinks disabled."
1540 except AssertionError:
1541 print >>sys
.stderr
, "Note: pdftk failed, hyperlinks disabled."
1547 pdf
= PDFParser(TempFileName
+ ".pdf")
1548 for page
, annots
in pdf
.GetHyperlinks().iteritems():
1549 for page_offset
in FileProps
[filename
]['offsets']:
1551 AddHyperlink(page_offset
, page
, a
[4], a
[:4], pdf
.box
[page
])
1552 count
+= len(annots
)
1557 print >>sys
.stderr
, "Note: file produced by pdftk not readable, hyperlinks disabled."
1559 print >>sys
.stderr
, "Note: error in file produced by pdftk, hyperlinks disabled."
1560 print >>sys
.stderr
, " PDF parser error message:", e
1563 os
.remove(TempFileName
+ ".pdf")
1568 ##### PAGE CACHE MANAGEMENT ####################################################
1570 # helper class that allows PIL to write and read image files with an offset
1572 def __init__(self
, f
, offset
=0):
1574 self
.offset
= offset
1576 def read(self
, count
=None):
1578 return self
.f
.read()
1580 return self
.f
.read(count
)
1581 def write(self
, data
):
1583 def seek(self
, pos
, whence
=0):
1584 assert(whence
in (0, 1))
1588 self
.f
.seek(pos
+ self
.offset
)
1590 return self
.f
.tell() - self
.offset
1592 # generate a "magic number" that is used to identify persistent cache files
1593 def UpdateCacheMagic():
1595 pool
= [PageCount
, ScreenWidth
, ScreenHeight
, b2s(Scaling
), b2s(Supersample
), b2s(Rotation
)]
1596 flist
= list(FileProps
.keys())
1597 flist
.sort(lambda a
,b
: cmp(a
.lower(), b
.lower()))
1600 pool
.extend(list(GetFileProp(f
, 'stat', [])))
1601 CacheMagic
= md5
.new("\0".join(map(str, pool
))).hexdigest()
1603 # set the persistent cache file position to the current end of the file
1604 def UpdatePCachePos():
1606 CacheFile
.seek(0, 2)
1607 CacheFilePos
= CacheFile
.tell()
1609 # rewrite the header of the persistent cache
1610 def WritePCacheHeader(reset
=False):
1611 pages
= ["%08x" % PageCache
.get(page
, 0) for page
in range(1, PageCount
+1)]
1613 CacheFile
.write(CacheMagic
+ "".join(pages
))
1615 CacheFile
.truncate()
1618 # return an image from the persistent cache or None if none is available
1619 def GetPCacheImage(page
):
1620 if CacheMode
!= PersistentCache
:
1621 return # not applicable if persistent cache isn't used
1624 if page
in PageCache
:
1625 img
= Image
.open(IOWrapper(CacheFile
, PageCache
[page
]))
1631 # returns an image from the non-persistent cache or None if none is available
1632 def GetCacheImage(page
):
1633 if CacheMode
in (NoCache
, PersistentCache
):
1634 return # not applicable in uncached or persistent-cache mode
1637 if page
in PageCache
:
1638 if CacheMode
== FileCache
:
1639 CacheFile
.seek(PageCache
[page
])
1640 return CacheFile
.read(TexSize
)
1642 return PageCache
[page
]
1646 # adds an image to the persistent cache
1647 def AddToPCache(page
, img
):
1648 if CacheMode
!= PersistentCache
:
1649 return # not applicable if persistent cache isn't used
1652 if page
in PageCache
:
1653 return # page is already cached and we can't update it safely
1654 # -> stop here (the new image will be identical to the old
1656 img
.save(IOWrapper(CacheFile
, CacheFilePos
), "ppm")
1657 PageCache
[page
] = CacheFilePos
1662 # adds an image to the non-persistent cache
1663 def AddToCache(page
, data
):
1665 if CacheMode
in (NoCache
, PersistentCache
):
1666 return # not applicable in uncached or persistent-cache mode
1669 if CacheMode
== FileCache
:
1670 if not(page
in PageCache
):
1671 PageCache
[page
] = CacheFilePos
1672 CacheFilePos
+= len(data
)
1673 CacheFile
.seek(PageCache
[page
])
1674 CacheFile
.write(data
)
1676 PageCache
[page
] = data
1680 # invalidates the whole cache
1681 def InvalidateCache():
1682 global PageCache
, CacheFilePos
1686 if CacheMode
== PersistentCache
:
1688 WritePCacheHeader(True)
1694 # initialize the persistent cache
1696 global CacheFile
, CacheMode
1698 # try to open the pre-existing cache file
1700 CacheFile
= file(CacheFileName
, "rb+")
1704 # check the cache magic
1706 if CacheFile
and (CacheFile
.read(32) != CacheMagic
):
1707 print >>sys
.stderr
, "Cache file mismatch, recreating cache."
1712 # if the magic was valid, import cache data
1713 print >>sys
.stderr
, "Using already existing persistent cache file."
1714 for page
in range(1, PageCount
+1):
1715 offset
= int(CacheFile
.read(8), 16)
1717 PageCache
[page
] = offset
1720 # if the magic was invalid or the file didn't exist, (re-)create it
1722 CacheFile
= file(CacheFileName
, "wb+")
1724 print >>sys
.stderr
, "Error: cannot write the persistent cache file (`%s')" % CacheFileName
1725 print >>sys
.stderr
, "Falling back to temporary file cache."
1726 CacheMode
= FileCache
1730 ##### PAGE RENDERING ###########################################################
1732 # generate a dummy image
1734 img
= Image
.new('RGB', (ScreenWidth
, ScreenHeight
))
1735 img
.paste(LogoImage
, ((ScreenWidth
- LogoImage
.size
[0]) / 2,
1736 (ScreenHeight
- LogoImage
.size
[1]) / 2))
1739 # load a page from a PDF file
1740 def RenderPDF(page
, MayAdjustResolution
, ZoomMode
):
1741 global UseGhostScript
1742 UseGhostScriptOnce
= False
1744 SourceFile
= GetPageProp(page
, '_file')
1745 Resolution
= GetFileProp(SourceFile
, 'res', 96)
1746 RealPage
= GetPageProp(page
, '_page')
1748 if Supersample
and not(ZoomMode
):
1749 UseRes
= int(0.5 + Resolution
) * Supersample
1752 UseRes
= int(0.5 + Resolution
)
1757 # call pdftoppm to generate the page image
1758 if not UseGhostScript
:
1759 renderer
= "pdftoppm"
1761 assert 0 == spawn(os
.P_WAIT
, \
1762 pdftoppmPath
, ["pdftoppm", "-q"] + [ \
1763 "-f", str(RealPage
), "-l", str(RealPage
),
1764 "-r", str(int(UseRes
)),
1765 FileNameEscape
+ SourceFile
+ FileNameEscape
,
1767 # determine output filename
1768 digits
= GetFileProp(SourceFile
, 'digits', 6)
1769 imgfile
= TempFileName
+ ("-%%0%dd.ppm" % digits
) % RealPage
1770 if not os
.path
.exists(imgfile
):
1771 for digits
in xrange(6, 0, -1):
1772 imgfile
= TempFileName
+ ("-%%0%dd.ppm" % digits
) % RealPage
1773 if os
.path
.exists(imgfile
): break
1774 SetFileProp(SourceFile
, 'digits', digits
)
1775 except OSError, (errcode
, errmsg
):
1776 print >>sys
.stderr
, "Warning: Cannot start pdftoppm -", errmsg
1777 print >>sys
.stderr
, "Falling back to GhostScript (permanently)."
1778 UseGhostScript
= True
1779 except AssertionError:
1780 print >>sys
.stderr
, "There was an error while rendering page %d" % page
1781 print >>sys
.stderr
, "Falling back to GhostScript for this page."
1782 UseGhostScriptOnce
= True
1784 # fallback to GhostScript
1785 if UseGhostScript
or UseGhostScriptOnce
:
1786 imgfile
= TempFileName
+ ".tif"
1787 renderer
= "GhostScript"
1789 assert 0 == spawn(os
.P_WAIT
, \
1790 GhostScriptPath
, ["gs", "-q"] + GhostScriptPlatformOptions
+ [ \
1791 "-dBATCH", "-dNOPAUSE", "-sDEVICE=tiff24nc", "-dUseCropBox",
1792 "-sOutputFile=" + imgfile
, \
1793 "-dFirstPage=%d" % RealPage
, "-dLastPage=%d" % RealPage
,
1794 "-r%dx%d" % (UseRes
, int(UseRes
* PAR
)), \
1795 "-dTextAlphaBits=%d" % AlphaBits
, \
1796 "-dGraphicsAlphaBits=%s" % AlphaBits
, \
1797 FileNameEscape
+ SourceFile
+ FileNameEscape
])
1798 except OSError, (errcode
, errmsg
):
1799 print >>sys
.stderr
, "Error: Cannot start GhostScript -", errmsg
1801 except AssertionError:
1802 print >>sys
.stderr
, "There was an error while rendering page %d" % page
1805 # open the page image file with PIL
1807 img
= Image
.open(imgfile
)
1809 print >>sys
.stderr
, "Error: %s produced an unreadable file (page %d)" % (renderer
, page
)
1812 # try to delete the file again (this constantly fails on Win32 ...)
1819 rot
= GetPageProp(page
, 'rotate')
1823 img
= img
.rotate(90 * (4 - rot
))
1825 # determine real display size (don't care for ZoomMode, DisplayWidth and
1826 # DisplayHeight are only used for Supersample and AdjustResolution anyway)
1828 DisplayWidth
= img
.size
[0] / Supersample
1829 DisplayHeight
= img
.size
[1] / Supersample
1831 DisplayWidth
= img
.size
[0]
1832 DisplayHeight
= img
.size
[1]
1834 # if the image size is strange, re-adjust the rendering resolution
1835 if MayAdjustResolution \
1836 and ((abs(ScreenWidth
- DisplayWidth
) > 4) \
1837 or (abs(ScreenHeight
- DisplayHeight
) > 4)):
1838 newsize
= ZoomToFit((DisplayWidth
,DisplayHeight
))
1839 NewResolution
= newsize
[0] * Resolution
/DisplayWidth
1840 if abs(1.0 - NewResolution
/ Resolution
) > 0.05:
1841 # only modify anything if the resolution deviation is large enough
1842 SetFileProp(SourceFile
, 'res', NewResolution
)
1843 return RenderPDF(page
, False, ZoomMode
)
1845 # downsample a supersampled image
1846 if Supersample
and not(ZoomMode
):
1847 return img
.resize((DisplayWidth
, DisplayHeight
), Image
.ANTIALIAS
)
1852 # load a page from an image file
1853 def LoadImage(page
, ZoomMode
):
1854 # open the image file with PIL
1856 img
= Image
.open(GetPageProp(page
, '_file'))
1858 print >>sys
.stderr
, "Image file `%s' is broken." % (FileList
[page
- 1])
1862 rot
= GetPageProp(page
, 'rotate')
1866 img
= img
.rotate(90 * (4 - rot
))
1868 # determine destination size
1869 newsize
= ZoomToFit(img
.size
)
1870 # don't scale if the source size is too close to the destination size
1871 if abs(newsize
[0] - img
.size
[0]) < 2: newsize
= img
.size
1872 # don't scale if the source is smaller than the destination
1873 if not(Scaling
) and (newsize
> img
.size
): newsize
= img
.size
1874 # zoom up (if wanted)
1875 if ZoomMode
: newsize
=(2 * newsize
[0], 2 * newsize
[1])
1876 # skip processing if there was no change
1877 if newsize
== img
.size
: return img
1879 # select a nice filter and resize the image
1880 if newsize
> img
.size
:
1881 filter = Image
.BICUBIC
1883 filter = Image
.ANTIALIAS
1884 return img
.resize(newsize
, filter)
1887 # render a page to an OpenGL texture
1888 def PageImage(page
, ZoomMode
=False, RenderMode
=False):
1889 global OverviewNeedUpdate
1890 EnableCacheRead
= not(ZoomMode
or RenderMode
)
1891 EnableCacheWrite
= EnableCacheRead
and \
1892 (page
>= PageRangeStart
) and (page
<= PageRangeEnd
)
1894 # check for the image in the cache
1896 data
= GetCacheImage(page
)
1897 if data
: return data
1899 # if it's not in the temporary cache, render it
1902 # retrieve the image from the persistent cache or fully re-render it
1904 img
= GetPCacheImage(page
)
1908 if GetPageProp(page
, '_page'):
1909 img
= RenderPDF(page
, not(ZoomMode
), ZoomMode
)
1911 img
= LoadImage(page
, ZoomMode
)
1912 if EnableCacheWrite
:
1913 AddToPCache(page
, img
)
1915 # create black background image to paste real image onto
1917 TextureImage
= Image
.new('RGB', (2 * TexWidth
, 2 * TexHeight
))
1918 TextureImage
.paste(img
, ((2 * ScreenWidth
- img
.size
[0]) / 2, \
1919 (2 * ScreenHeight
- img
.size
[1]) / 2))
1921 TextureImage
= Image
.new('RGB', (TexWidth
, TexHeight
))
1922 x0
= (ScreenWidth
- img
.size
[0]) / 2
1923 y0
= (ScreenHeight
- img
.size
[1]) / 2
1924 TextureImage
.paste(img
, (x0
, y0
))
1925 SetPageProp(page
, '_box', (x0
, y0
, x0
+ img
.size
[0], y0
+ img
.size
[1]))
1928 # paste thumbnail into overview image
1929 if GetPageProp(page
, ('overview', '_overview'), True) \
1930 and (page
>= PageRangeStart
) and (page
<= PageRangeEnd
) \
1931 and not(GetPageProp(page
, '_overview_rendered')) \
1932 and not(RenderMode
):
1933 pos
= OverviewPos(OverviewPageMapInv
[page
])
1936 # first, fill the underlying area with black (i.e. remove the dummy logo)
1937 blackness
= Image
.new('RGB', (OverviewCellX
- OverviewBorder
, \
1938 OverviewCellY
- OverviewBorder
))
1939 OverviewImage
.paste(blackness
, (pos
[0] + OverviewBorder
/ 2, \
1940 pos
[1] + OverviewBorder
))
1942 # then, scale down the original image and paste it
1943 img
.thumbnail((OverviewCellX
- 2 * OverviewBorder
, \
1944 OverviewCellY
- 2 * OverviewBorder
), \
1946 OverviewImage
.paste(img
, \
1947 (pos
[0] + (OverviewCellX
- img
.size
[0]) / 2, \
1948 pos
[1] + (OverviewCellY
- img
.size
[1]) / 2))
1951 SetPageProp(page
, '_overview_rendered', True)
1952 OverviewNeedUpdate
= True
1955 # return texture data
1958 data
=TextureImage
.tostring()
1963 # finally add it back into the cache and return it
1964 if EnableCacheWrite
:
1965 AddToCache(page
, data
)
1968 # render a page to an OpenGL texture
1969 def RenderPage(page
, target
):
1970 glBindTexture(TextureTarget
,target
)
1972 glTexImage2D(TextureTarget
, 0, 3, TexWidth
, TexHeight
, 0,\
1973 GL_RGB
, GL_UNSIGNED_BYTE
, PageImage(page
))
1975 print >>sys
.stderr
, "I'm sorry, but your graphics card is not capable of rendering presentations"
1976 print >>sys
.stderr
, "in this resolution. Either the texture memory is exhausted, or there is no"
1977 print >>sys
.stderr
, "support for large textures (%dx%d). Please try to run Accentuate in a" % (TexWidth
, TexHeight
)
1978 print >>sys
.stderr
, "smaller resolution using the -g command-line option."
1981 # background rendering thread
1982 def RenderThread(p1
, p2
):
1983 global RTrunning
, RTrestart
1988 for pdf
in FileProps
:
1989 if not pdf
.lower().endswith(".pdf"): continue
1992 if RTrestart
: continue
1993 for page
in xrange(1, PageCount
+ 1):
1995 if (page
!= p1
) and (page
!= p2
) \
1996 and (page
>= PageRangeStart
) and (page
<= PageRangeEnd
):
1999 if CacheMode
>= FileCache
:
2000 print >>sys
.stderr
, "Background rendering finished, used %.1f MiB of disk space." %\
2001 (CacheFilePos
/ 1048576.0)
2004 ##### RENDER MODE ##############################################################
2007 global TexWidth
, TexHeight
2008 TexWidth
= ScreenWidth
2009 TexHeight
= ScreenHeight
2010 if os
.path
.exists(RenderToDirectory
):
2011 print >>sys
.stderr
, "Destination directory `%s' already exists," % RenderToDirectory
2012 print >>sys
.stderr
, "refusing to overwrite anything."
2015 os
.mkdir(RenderToDirectory
)
2017 print >>sys
.stderr
, "Cannot create destination directory `%s':" % RenderToDirectory
2018 print >>sys
.stderr
, e
.strerror
2020 print >>sys
.stderr
, "Rendering presentation into `%s'" % RenderToDirectory
2021 for page
in xrange(1, PageCount
+ 1):
2022 PageImage(page
, RenderMode
=True).save("%s/page%04d.png" % (RenderToDirectory
, page
))
2023 sys
.stdout
.write("[%d] " % page
)
2026 print >>sys
.stderr
, "Done."
2030 ##### INFO SCRIPT I/O ##########################################################
2032 # info script reader
2033 def LoadInfoScript():
2036 OldPageProps
= PageProps
2037 execfile(InfoScriptPath
, globals())
2038 NewPageProps
= PageProps
2039 PageProps
= OldPageProps
2041 for page
in NewPageProps
:
2042 for prop
in NewPageProps
[page
]:
2043 SetPageProp(page
, prop
, NewPageProps
[page
][prop
])
2048 print >>sys
.stderr
, "----- Exception in info script ----"
2049 traceback
.print_exc(file=sys
.stderr
)
2050 print >>sys
.stderr
, "----- End of traceback -----"
2052 # we can't save lamba expressions, so we need to warn the user
2053 # in every possible way
2054 ScriptTainted
= False
2055 LambdaWarning
= False
2056 def here_was_a_lambda_expression_that_could_not_be_saved():
2057 global LambdaWarning
2058 if not LambdaWarning
:
2059 print >>sys
.stderr
, "WARNING: The info script for the current file contained lambda expressions that"
2060 print >>sys
.stderr
, " were removed during the a save operation."
2061 LambdaWarning
= True
2063 # "clean" a PageProps entry so that only 'public' properties are left
2064 def GetPublicProps(props
):
2065 props
= props
.copy()
2066 # delete private (underscore) props
2067 for prop
in list(props
.keys()):
2068 if str(prop
)[0] == '_':
2070 # clean props to default values
2071 if props
.get('overview', False):
2072 del props
['overview']
2073 if not props
.get('skip', True):
2075 if ('boxes' in props
) and not(props
['boxes']):
2079 # Generate a string representation of a property value. Mainly this converts
2080 # classes or instances to the name of the class.
2081 def PropValueRepr(value
):
2082 global ScriptTainted
2083 if type(value
) == types
.FunctionType
:
2084 if value
.__name
__ != "<lambda>":
2085 return value
.__name
__
2086 if not ScriptTainted
:
2087 print >>sys
.stderr
, "WARNING: The info script contains lambda expressions, which cannot be saved"
2088 print >>sys
.stderr
, " back. The modifed script will be written into a separate file to"
2089 print >>sys
.stderr
, " minimize data loss."
2090 ScriptTainted
= True
2091 return "here_was_a_lambda_expression_that_could_not_be_saved"
2092 elif type(value
) == types
.ClassType
:
2093 return value
.__name
__
2094 elif type(value
) == types
.InstanceType
:
2095 return value
.__class
__.__name
__
2096 elif type(value
) == types
.DictType
:
2097 return "{ " + ", ".join([PropValueRepr(k
) + ": " + PropValueRepr(value
[k
]) for k
in value
]) + " }"
2101 # generate a nicely formatted string representation of a page's properties
2102 def SinglePagePropRepr(page
):
2103 props
= GetPublicProps(PageProps
[page
])
2104 if not props
: return None
2105 return "\n%3d: {%s\n }" % (page
, \
2106 ",".join(["\n " + repr(prop
) + ": " + PropValueRepr(props
[prop
]) for prop
in props
]))
2108 # generate a nicely formatted string representation of all page properties
2110 pages
= PageProps
.keys()
2112 return "PageProps = {%s\n}" % (",".join(filter(None, map(SinglePagePropRepr
, pages
))))
2114 # count the characters of a python dictionary source code, correctly handling
2115 # embedded strings and comments, and nested dictionaries
2116 def CountDictChars(s
, start
=0):
2119 for i
in xrange(start
, len(s
)):
2122 if c
== '{': level
+= 1
2123 if c
== '}': level
-= 1
2124 if c
== '#': context
= '#'
2125 if c
== '"': context
= '"'
2126 if c
== "'": context
= "'"
2127 elif context
[0] == "\\":
2129 elif context
== '#':
2130 if c
in "\r\n": context
= None
2131 elif context
== '"':
2132 if c
== "\\": context
= "\\\""
2133 if c
== '"': context
= None
2134 elif context
== "'":
2135 if c
== "\\": context
= "\\'"
2136 if c
== "'": context
= None
2137 if level
< 0: return i
2138 raise ValueError, "the dictionary never ends"
2140 # modify and save a file's info script
2141 def SaveInfoScript(filename
):
2142 # read the old info script
2144 f
= file(filename
, "r")
2150 script
= "# -*- coding: iso-8859-1 -*-\n"
2152 # replace the PageProps of the old info script with the current ones
2154 m
= re
.search("^.*(PageProps)\s*=\s*(\{).*$", script
,re
.MULTILINE
)
2156 script
= script
[:m
.start(1)] + PagePropRepr() + \
2157 script
[CountDictChars(script
, m
.end(2)) + 1 :]
2159 script
+= "\n" + PagePropRepr() + "\n"
2160 except (AttributeError, ValueError):
2164 filename
+= ".modified"
2166 # write the script back
2168 f
= file(filename
, "w")
2172 print >>sys
.stderr
, "Oops! Could not write info script!"
2175 ##### OPENGL RENDERING #########################################################
2179 reltime
= pygame
.time
.get_ticks() - StartTime
2180 if EstimatedDuration
and (OverviewMode
or GetPageProp(Pcurrent
, 'progress', True)):
2181 rel
= (0.001 * reltime
) / EstimatedDuration
2182 x
= int(ScreenWidth
* rel
)
2183 y
= 1.0 - ProgressBarSize
* PixelX
2184 a
= min(255, max(0, x
- ScreenWidth
))
2185 b
= min(255, max(0, x
- ScreenWidth
- 256))
2189 glDisable(TextureTarget
)
2190 glDisable(GL_TEXTURE_2D
)
2192 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
2194 glColor4ub(r
, g
, b
, 0)
2197 glColor4ub(r
, g
, b
, ProgressBarAlpha
)
2198 glVertex2d(rel
, 1.0)
2203 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
)
2206 DrawOSDEx(OSDTimePos
, FormatTime(t
, MinutesOnly
))
2207 if CurrentOSDComment
and (OverviewMode
or not(TransitionRunning
)):
2208 DrawOSD(ScreenWidth
/2, \
2209 ScreenHeight
- 3*OSDMargin
- FontSize
, \
2210 CurrentOSDComment
, Center
, Up
)
2211 if CursorImage
and CursorVisible
:
2212 x
, y
= pygame
.mouse
.get_pos()
2213 x
-= CursorHotspot
[0]
2214 y
-= CursorHotspot
[1]
2219 glDisable(TextureTarget
)
2220 glEnable(GL_TEXTURE_2D
)
2221 glBindTexture(GL_TEXTURE_2D
, CursorTexture
)
2223 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
2224 glColor4ub(255, 255, 255, 255)
2226 glTexCoord2d(0.0, 0.0); glVertex2d(X0
, Y0
)
2227 glTexCoord2d(CursorTX
, 0.0); glVertex2d(X1
, Y0
)
2228 glTexCoord2d(CursorTX
, CursorTY
); glVertex2d(X1
, Y1
)
2229 glTexCoord2d(0.0, CursorTY
); glVertex2d(X0
, Y1
)
2232 glDisable(GL_TEXTURE_2D
)
2234 # draw the complete image of the current page
2235 def DrawCurrentPage(dark
=1.0, do_flip
=True):
2236 if VideoPlaying
: return
2237 boxes
= GetPageProp(Pcurrent
, 'boxes')
2238 glClear(GL_COLOR_BUFFER_BIT
)
2240 # pre-transform for zoom
2242 glOrtho(ZoomX0
, ZoomX0
+ ZoomArea
, ZoomY0
+ ZoomArea
, ZoomY0
, -10.0, 10.0)
2244 # background layer -- the page's image, darkened if it has boxes
2246 glEnable(TextureTarget
)
2247 glBindTexture(TextureTarget
, Tcurrent
)
2248 if boxes
or Tracing
:
2249 light
= 1.0 - 0.25 * dark
2252 glColor3d(light
, light
, light
)
2255 if boxes
or Tracing
:
2256 # alpha-blend the same image some times to blur it
2258 DrawTranslatedFullQuad(+PixelX
* ZoomArea
, 0.0, light
, dark
/ 2)
2259 DrawTranslatedFullQuad(-PixelX
* ZoomArea
, 0.0, light
, dark
/ 3)
2260 DrawTranslatedFullQuad(0.0, +PixelY
* ZoomArea
, light
, dark
/ 4)
2261 DrawTranslatedFullQuad(0.0, -PixelY
* ZoomArea
, light
, dark
/ 5)
2264 # draw outer box fade
2266 for X0
, Y0
, X1
, Y1
in boxes
:
2267 glBegin(GL_QUAD_STRIP
)
2268 DrawPointEx(X0
, Y0
, 1); DrawPointEx(X0
- EdgeX
, Y0
- EdgeY
, 0)
2269 DrawPointEx(X1
, Y0
, 1); DrawPointEx(X1
+ EdgeX
, Y0
- EdgeY
, 0)
2270 DrawPointEx(X1
, Y1
, 1); DrawPointEx(X1
+ EdgeX
, Y1
+ EdgeY
, 0)
2271 DrawPointEx(X0
, Y1
, 1); DrawPointEx(X0
- EdgeX
, Y1
+ EdgeY
, 0)
2272 DrawPointEx(X0
, Y0
, 1); DrawPointEx(X0
- EdgeX
, Y0
- EdgeY
, 0)
2278 for X0
, Y0
, X1
, Y1
in boxes
:
2286 x
, y
= MouseToScreen(pygame
.mouse
.get_pos())
2289 glBegin(GL_TRIANGLE_STRIP
)
2290 for x0
, y0
, x1
, y1
in SpotMesh
:
2291 DrawPointEx(x
+ x0
, y
+ y0
, 1)
2292 DrawPointEx(x
+ x1
, y
+ y1
, 0)
2296 glBegin(GL_TRIANGLE_FAN
)
2298 for x0
, y0
, x1
, y1
in SpotMesh
:
2299 DrawPoint(x
+ x0
, y
+ y0
)
2303 # soft alpha-blended rectangle
2304 glDisable(TextureTarget
)
2305 glColor4d(*MarkColor
)
2308 glVertex2d(MarkUL
[0], MarkUL
[1])
2309 glVertex2d(MarkLR
[0], MarkUL
[1])
2310 glVertex2d(MarkLR
[0], MarkLR
[1])
2311 glVertex2d(MarkUL
[0], MarkLR
[1])
2315 glBegin(GL_LINE_STRIP
)
2316 glVertex2d(MarkUL
[0], MarkUL
[1])
2317 glVertex2d(MarkLR
[0], MarkUL
[1])
2318 glVertex2d(MarkLR
[0], MarkLR
[1])
2319 glVertex2d(MarkUL
[0], MarkLR
[1])
2320 glVertex2d(MarkUL
[0], MarkUL
[1])
2322 glEnable(TextureTarget
)
2324 # unapply the zoom transform
2326 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
2331 pygame
.display
.flip()
2333 # draw a black screen with the Accentuate logo at the center
2335 glClear(GL_COLOR_BUFFER_BIT
)
2336 glColor3ub(255, 255, 255)
2337 if TextureTarget
!= GL_TEXTURE_2D
:
2338 glDisable(TextureTarget
)
2339 glEnable(GL_TEXTURE_2D
)
2340 glBindTexture(GL_TEXTURE_2D
, LogoTexture
)
2342 glTexCoord2d(0, 0); glVertex2d(0.5 - 128.0 / ScreenWidth
, 0.5 - 32.0 / ScreenHeight
)
2343 glTexCoord2d(1, 0); glVertex2d(0.5 + 128.0 / ScreenWidth
, 0.5 - 32.0 / ScreenHeight
)
2344 glTexCoord2d(1, 1); glVertex2d(0.5 + 128.0 / ScreenWidth
, 0.5 + 32.0 / ScreenHeight
)
2345 glTexCoord2d(0, 1); glVertex2d(0.5 - 128.0 / ScreenWidth
, 0.5 + 32.0 / ScreenHeight
)
2348 OSDFont
.Draw((ScreenWidth
/ 2, ScreenHeight
/ 2 + 48), \
2349 __version__
, align
=Center
, alpha
=0.25)
2350 glDisable(GL_TEXTURE_2D
)
2352 # draw the prerender progress bar
2353 def DrawProgress(position
):
2354 glDisable(TextureTarget
)
2357 x1
= position
* x2
+ (1.0 - position
) * x0
2359 y0
= y1
- 16.0 / ScreenHeight
2361 glColor3ub( 64, 64, 64); glVertex2d(x0
, y0
); glVertex2d(x2
, y0
)
2362 glColor3ub(128, 128, 128); glVertex2d(x2
, y1
); glVertex2d(x0
, y1
)
2363 glColor3ub( 64, 128, 255); glVertex2d(x0
, y0
); glVertex2d(x1
, y0
)
2364 glColor3ub( 8, 32, 128); glVertex2d(x1
, y1
); glVertex2d(x0
, y1
)
2366 glEnable(TextureTarget
)
2369 def DrawFadeMode(intensity
, alpha
):
2370 if VideoPlaying
: return
2371 DrawCurrentPage(do_flip
=False)
2372 glDisable(TextureTarget
)
2374 glColor4d(intensity
, intensity
, intensity
, alpha
)
2376 glEnable(TextureTarget
)
2377 pygame
.display
.flip()
2379 def FadeMode(intensity
):
2380 t0
= pygame
.time
.get_ticks()
2382 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2383 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BlankFadeDuration
2385 DrawFadeMode(intensity
, t
)
2386 DrawFadeMode(intensity
, 1.0)
2389 event
= pygame
.event
.wait()
2390 if event
.type == QUIT
:
2393 elif event
.type == VIDEOEXPOSE
:
2394 DrawFadeMode(intensity
, 1.0)
2395 elif event
.type == MOUSEBUTTONUP
:
2397 elif event
.type == KEYDOWN
:
2398 if event
.unicode == u
'q':
2399 pygame
.event
.post(pygame
.event
.Event(QUIT
))
2403 t0
= pygame
.time
.get_ticks()
2405 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2406 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BlankFadeDuration
2408 DrawFadeMode(intensity
, 1.0 - t
)
2412 def SetGamma(new_gamma
=None, new_black
=None, force
=False):
2413 global Gamma
, BlackLevel
2414 if new_gamma
is None: new_gamma
= Gamma
2415 if new_gamma
< 0.1: new_gamma
= 0.1
2416 if new_gamma
> 10.0: new_gamma
= 10.0
2417 if new_black
is None: new_black
= BlackLevel
2418 if new_black
< 0: new_black
= 0
2419 if new_black
> 254: new_black
= 254
2420 if not(force
) and (abs(Gamma
- new_gamma
) < 0.01) and (new_black
== BlackLevel
):
2423 BlackLevel
= new_black
2424 scale
= 1.0 / (255 - BlackLevel
)
2426 ramp
= [int(65535.0 * ((max(0, x
- BlackLevel
) * scale
) ** power
)) for x
in range(256)]
2427 return pygame
.display
.set_gamma_ramp(ramp
, ramp
, ramp
)
2430 def PrepareCustomCursor(cimg
):
2431 global CursorTexture
, CursorSX
, CursorSY
, CursorTX
, CursorTY
2433 tw
, th
= map(npot
, cimg
.size
)
2434 if (tw
> 256) or (th
> 256):
2435 print >>sys
.stderr
, "Custom cursor is rediculously large, reverting to normal one."
2437 img
= Image
.new('RGBA', (tw
, th
))
2438 img
.paste(cimg
, (0, 0))
2439 CursorTexture
= glGenTextures(1)
2440 glBindTexture(GL_TEXTURE_2D
, CursorTexture
)
2441 glTexImage2D(GL_TEXTURE_2D
, 0, GL_RGBA
, tw
, th
, 0, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.tostring())
2442 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
)
2443 CursorSX
= w
* PixelX
2444 CursorSY
= h
* PixelY
2445 CursorTX
= w
/ float(tw
)
2446 CursorTY
= h
/ float(th
)
2450 ##### CONTROL AND NAVIGATION ###################################################
2452 # update the applications' title bar
2453 def UpdateCaption(page
=0, force
=False):
2454 global CurrentCaption
, CurrentOSDCaption
, CurrentOSDPage
, CurrentOSDStatus
2455 global CurrentOSDComment
2456 if (page
== CurrentCaption
) and not(force
):
2458 CurrentCaption
= page
2461 caption
+= " - " + DocumentTitle
2463 CurrentOSDCaption
= ""
2465 CurrentOSDStatus
= ""
2466 CurrentOSDComment
= ""
2467 pygame
.display
.set_caption(caption
, __title__
)
2469 CurrentOSDPage
= "%d/%d" % (page
, PageCount
)
2470 caption
= "%s (%s)" % (caption
, CurrentOSDPage
)
2471 title
= GetPageProp(page
, 'title') or GetPageProp(page
, '_title')
2473 caption
+= ": %s" % title
2474 CurrentOSDCaption
= title
2476 CurrentOSDCaption
= ""
2478 if GetPageProp(page
, 'skip', False):
2479 status
.append("skipped: yes")
2480 if not GetPageProp(page
, ('overview', '_overview'), True):
2481 status
.append("on overview page: no")
2482 CurrentOSDStatus
= ", ".join(status
)
2483 CurrentOSDComment
= GetPageProp(page
, 'comment')
2484 pygame
.display
.set_caption(caption
, __title__
)
2486 # get next/previous page
2487 def GetNextPage(page
, direction
, keypressed
=0):
2490 try_page
+= direction
2491 if try_page
== page
:
2492 return 0 # tried all pages, but none found
2494 if try_page
< 1: try_page
= PageCount
2495 if try_page
> PageCount
: try_page
= 1
2496 elif (FadeToBlackAtEnd
and keypressed
):
2497 if try_page
> PageCount
:
2500 if try_page
< 1 or try_page
> PageCount
:
2501 return 0 # start or end of presentation
2502 if not GetPageProp(try_page
, 'skip', False):
2505 # pre-load the following page into Pnext/Tnext
2506 def PreloadNextPage(page
):
2508 if (page
< 1) or (page
> PageCount
):
2513 RenderPage(page
, Tnext
)
2517 # perform box fading; the fade animation time is mapped through func()
2519 t0
= pygame
.time
.get_ticks()
2521 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2522 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BoxFadeDuration
2524 DrawCurrentPage(func(t
))
2525 DrawCurrentPage(func(1.0))
2530 global StartTime
, PageEnterTime
2531 if TimeTracking
and not(FirstPage
):
2532 print "--- timer was reset here ---"
2533 StartTime
= pygame
.time
.get_ticks()
2536 # start video playback
2537 def PlayVideo(video
):
2538 global MPlayerPID
, VideoPlaying
2539 if not video
: return
2542 MPlayerPID
= spawn(os
.P_NOWAIT
, \
2543 MPlayerPath
, [MPlayerPath
, "-quiet", \
2544 "-monitorpixelaspect", "1:1", "-autosync", "100"] + \
2545 MPlayerPlatformOptions
+ [ "-slave", \
2546 "-wid", str(pygame
.display
.get_wm_info()['window']), \
2547 FileNameEscape
+ video
+ FileNameEscape
])
2549 glClear(GL_COLOR_BUFFER_BIT
)
2550 pygame
.display
.flip()
2555 # called each time a page is entered
2556 def PageEntered(update_time
=True):
2557 global PageEnterTime
, MPlayerPID
, IsZoomed
, WantStatus
2559 PageEnterTime
= pygame
.time
.get_ticks() - StartTime
2560 IsZoomed
= False # no, we don't have a pre-zoomed image right now
2561 WantStatus
= False # don't show status unless it's changed interactively
2562 timeout
= AutoAdvance
2563 shown
= GetPageProp(Pcurrent
, '_shown', 0)
2565 timeout
= GetPageProp(Pcurrent
, 'timeout', timeout
)
2566 video
= GetPageProp(Pcurrent
, 'video')
2567 sound
= GetPageProp(Pcurrent
, 'sound')
2569 if sound
and not(video
):
2572 MPlayerPID
= spawn(os
.P_NOWAIT
, \
2573 MPlayerPath
, [MPlayerPath
, "-quiet", "-really-quiet", \
2574 FileNameEscape
+ sound
+ FileNameEscape
])
2577 SafeCall(GetPageProp(Pcurrent
, 'OnEnterOnce'))
2578 SafeCall(GetPageProp(Pcurrent
, 'OnEnter'))
2579 if timeout
: pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, timeout
)
2580 SetPageProp(Pcurrent
, '_shown', shown
+ 1)
2582 # called each time a page is left
2583 def PageLeft(overview
=False):
2584 global FirstPage
, LastPage
, WantStatus
2587 if GetTristatePageProp(Pcurrent
, 'reset'):
2591 if GetPageProp(Pcurrent
, '_shown', 0) == 1:
2592 SafeCall(GetPageProp(Pcurrent
, 'OnLeaveOnce'))
2593 SafeCall(GetPageProp(Pcurrent
, 'OnLeave'))
2595 t1
= pygame
.time
.get_ticks() - StartTime
2596 dt
= (t1
- PageEnterTime
+ 500) / 1000
2600 p
= "%4d" % Pcurrent
2601 print "%s%9s%9s%9s" % (p
, FormatTime(dt
), \
2602 FormatTime(PageEnterTime
/ 1000), \
2603 FormatTime(t1
/ 1000))
2605 # perform a transition to a specified page
2606 def TransitionTo(page
):
2607 global Pcurrent
, Pnext
, Tcurrent
, Tnext
2608 global PageCount
, Marking
, Tracing
, Panning
, TransitionRunning
2610 # first, stop the auto-timer
2611 pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, 0)
2613 # invalid page? go away
2614 if not PreloadNextPage(page
):
2617 # notify that the page has been left
2621 if GetPageProp(Pcurrent
, 'boxes') or Tracing
:
2622 skip
= BoxFade(lambda t
: 1.0 - t
)
2631 # check if the transition is valid
2632 tpage
= min(Pcurrent
, Pnext
)
2633 if 'transition' in PageProps
[tpage
]:
2636 tkey
= '_transition'
2637 trans
= PageProps
[tpage
][tkey
]
2641 transtime
= GetPageProp(tpage
, 'transtime', TransitionDuration
)
2643 dummy
= trans
.__class
__
2644 except AttributeError:
2645 # ah, gotcha! the transition is not yet intantiated!
2647 PageProps
[tpage
][tkey
] = trans
2649 # backward motion? then swap page buffers now
2650 backward
= (Pnext
< Pcurrent
)
2652 Pcurrent
, Pnext
= (Pnext
, Pcurrent
)
2653 Tcurrent
, Tnext
= (Tnext
, Tcurrent
)
2655 # transition animation
2656 if not(skip
) and transtime
:
2657 transtime
= 1.0 / transtime
2658 TransitionRunning
= True
2659 t0
= pygame
.time
.get_ticks()
2660 while not(VideoPlaying
):
2661 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]):
2664 t
= (pygame
.time
.get_ticks() - t0
) * transtime
2666 if backward
: t
= 1.0 - t
2667 glEnable(TextureTarget
)
2670 pygame
.display
.flip()
2671 TransitionRunning
= False
2673 # forward motion => swap page buffers now
2675 Pcurrent
, Pnext
= (Pnext
, Pcurrent
)
2676 Tcurrent
, Tnext
= (Tnext
, Tcurrent
)
2679 if not(skip
) and GetPageProp(Pcurrent
, 'boxes'): BoxFade(lambda t
: t
)
2681 # finally update the screen and preload the next page
2682 DrawCurrentPage() # I do that twice because for some strange reason, the
2684 if not PreloadNextPage(GetNextPage(Pcurrent
, 1)):
2685 PreloadNextPage(GetNextPage(Pcurrent
, -1))
2688 # zoom mode animation
2689 def ZoomAnimation(targetx
, targety
, func
):
2690 global ZoomX0
, ZoomY0
, ZoomArea
2691 t0
= pygame
.time
.get_ticks()
2693 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2694 t
= (pygame
.time
.get_ticks() - t0
)* 1.0 / ZoomDuration
2698 ZoomX0
= targetx
* t
2699 ZoomY0
= targety
* t
2700 ZoomArea
= 1.0 - 0.5 * t
2703 ZoomX0
= targetx
* t
2704 ZoomY0
= targety
* t
2705 ZoomArea
= 1.0 - 0.5 * t
2710 def EnterZoomMode(targetx
, targety
):
2711 global ZoomMode
, IsZoomed
, ZoomWarningIssued
2712 ZoomAnimation(targetx
, targety
, lambda t
: t
)
2714 if TextureTarget
!= GL_TEXTURE_2D
:
2715 if not ZoomWarningIssued
:
2716 print >>sys
.stderr
, "Sorry, but I can't increase the detail level in zoom mode any further when"
2717 print >>sys
.stderr
, "GL_ARB_texture_rectangle is used. Please try running Accentuate with the"
2718 print >>sys
.stderr
, "'-e' parameter. If a modern nVidia or ATI graphics card is used, a driver"
2719 print >>sys
.stderr
, "update may also fix the problem."
2720 ZoomWarningIssued
= True
2723 glBindTexture(TextureTarget
, Tcurrent
)
2725 glTexImage2D(TextureTarget
, 0, 3, TexWidth
* 2, TexHeight
* 2, 0, \
2726 GL_RGB
, GL_UNSIGNED_BYTE
, PageImage(Pcurrent
, True))
2728 if not ZoomWarningIssued
:
2729 print >>sys
.stderr
, "Sorry, but I can't increase the detail level in zoom mode any further, because"
2730 print >>sys
.stderr
, "your OpenGL implementation does not support that. Either the texture memory is"
2731 print >>sys
.stderr
, "exhausted, or there is no support for large textures (%dx%d). If you really" % (TexWidth
* 2, TexHeight
* 2)
2732 print >>sys
.stderr
, "need high-res zooming, please try to run Accentuate in a smaller resolution"
2733 print >>sys
.stderr
, "using the -g command-line option."
2734 ZoomWarningIssued
= True
2739 # leave zoom mode (if enabled)
2740 def LeaveZoomMode():
2742 if not ZoomMode
: return
2743 ZoomAnimation(ZoomX0
, ZoomY0
, lambda t
: 1.0 - t
)
2747 # increment/decrement spot radius
2748 def IncrementSpotSize(delta
):
2752 SpotRadius
= max(SpotRadius
+ delta
, 8)
2756 # post-initialize the page transitions
2757 def PrepareTransitions():
2758 Unspecified
= 0xAFFED00F
2759 # STEP 1: randomly assign transitions where the user didn't specify them
2760 cnt
= sum([1 for page
in xrange(1, PageCount
+ 1) \
2761 if GetPageProp(page
, 'transition', Unspecified
) == Unspecified
])
2762 newtrans
= ((cnt
/ len(AvailableTransitions
) + 1) * AvailableTransitions
)[:cnt
]
2763 random
.shuffle(newtrans
)
2764 for page
in xrange(1, PageCount
+ 1):
2765 if GetPageProp(page
, 'transition', Unspecified
) == Unspecified
:
2766 SetPageProp(page
, '_transition', newtrans
.pop())
2767 # STEP 2: instantiate transitions
2768 for page
in PageProps
:
2769 for key
in ('transition', '_transition'):
2770 if not key
in PageProps
[page
]:
2772 trans
= PageProps
[page
][key
]
2773 if trans
is not None:
2774 PageProps
[page
][key
] = trans()
2776 # update timer values and screen timer
2778 global CurrentTime
, ProgressBarPos
2780 newtime
= (pygame
.time
.get_ticks() - StartTime
) * 0.001
2781 if EstimatedDuration
:
2782 newpos
= int(ScreenWidth
* newtime
/ EstimatedDuration
)
2783 if newpos
!= ProgressBarPos
:
2785 ProgressBarPos
= newpos
2786 newtime
= int(newtime
)
2787 if TimeDisplay
and (CurrentTime
!= newtime
):
2789 CurrentTime
= newtime
2792 # set cursor visibility
2793 def SetCursor(visible
):
2794 global CursorVisible
2795 CursorVisible
= visible
2797 pygame
.mouse
.set_visible(visible
)
2800 def IsValidShortcutKey(key
):
2801 return ((key
>= K_a
) and (key
<= K_z
)) \
2802 or ((key
>= K_0
) and (key
<= K_9
)) \
2803 or ((key
>= K_F1
) and (key
<= K_F12
))
2804 def FindShortcut(shortcut
):
2805 for page
, props
in PageProps
.iteritems():
2807 check
= props
['shortcut']
2808 if type(check
) != types
.StringType
:
2810 elif (len(check
) > 1) and (check
[0] in "Ff"):
2811 check
= K_F1
- 1 + int(check
[1:])
2813 check
= ord(check
.lower())
2814 except (KeyError, TypeError, ValueError):
2816 if check
== shortcut
:
2819 def AssignShortcut(page
, key
):
2820 old_page
= FindShortcut(key
)
2822 del PageProps
[old_page
]['shortcut']
2825 elif (key
>= K_F1
) and (key
<= K_F15
):
2826 shortcut
= "F%d" % (key
- K_F1
+ 1)
2829 SetPageProp(page
, 'shortcut', shortcut
)
2832 ##### OVERVIEW MODE ############################################################
2834 def UpdateOverviewTexture():
2835 global OverviewNeedUpdate
2836 glBindTexture(TextureTarget
, Tnext
)
2839 glTexImage2D(TextureTarget
, 0, 3, TexWidth
, TexHeight
, 0, \
2840 GL_RGB
, GL_UNSIGNED_BYTE
, OverviewImage
.tostring())
2843 OverviewNeedUpdate
= False
2845 # draw the overview page
2847 if VideoPlaying
: return
2848 glClear(GL_COLOR_BUFFER_BIT
)
2850 glEnable(TextureTarget
)
2851 glBindTexture(TextureTarget
, Tnext
)
2852 glColor3ub(192, 192, 192)
2855 pos
= OverviewPos(OverviewSelection
)
2856 X0
= PixelX
* pos
[0]
2857 Y0
= PixelY
* pos
[1]
2858 X1
= PixelX
* (pos
[0] + OverviewCellX
)
2859 Y1
= PixelY
* (pos
[1] + OverviewCellY
)
2860 glColor3d(1.0, 1.0, 1.0)
2868 DrawOSDEx(OSDTitlePos
, CurrentOSDCaption
)
2869 DrawOSDEx(OSDPagePos
, CurrentOSDPage
)
2870 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
)
2872 pygame
.display
.flip()
2874 # overview zoom effect, time mapped through func
2875 def OverviewZoom(func
):
2876 global TransitionRunning
2877 pos
= OverviewPos(OverviewSelection
)
2878 X0
= PixelX
* (pos
[0] + OverviewBorder
)
2879 Y0
= PixelY
* (pos
[1] + OverviewBorder
)
2880 X1
= PixelX
* (pos
[0] - OverviewBorder
+ OverviewCellX
)
2881 Y1
= PixelY
* (pos
[1] - OverviewBorder
+ OverviewCellY
)
2883 TransitionRunning
= True
2884 t0
= pygame
.time
.get_ticks()
2885 while not(VideoPlaying
):
2886 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / ZoomDuration
2892 zoom
= (t
* (X1
- X0
) + t1
) / (X1
- X0
)
2893 OX
= zoom
* (t
* X0
- X0
) - (zoom
- 1.0) * t
* X0
2894 OY
= zoom
* (t
* Y0
- Y0
) - (zoom
- 1.0) * t
* Y0
2895 OX
= t
* X0
- zoom
* X0
2896 OY
= t
* Y0
- zoom
* Y0
2899 glEnable(TextureTarget
)
2900 glBindTexture(TextureTarget
, Tnext
)
2902 glColor3ub(192, 192, 192)
2903 glTexCoord2d( 0.0, 0.0); glVertex2d(OX
, OY
)
2904 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(OX
+ zoom
, OY
)
2905 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(OX
+ zoom
, OY
+ zoom
)
2906 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(OX
, OY
+ zoom
)
2907 glColor3ub(255, 255, 255)
2908 glTexCoord2d(X0
* TexMaxS
, Y0
* TexMaxT
); glVertex2d(OX
+ X0
*zoom
, OY
+ Y0
* zoom
)
2909 glTexCoord2d(X1
* TexMaxS
, Y0
* TexMaxT
); glVertex2d(OX
+ X1
*zoom
, OY
+ Y0
* zoom
)
2910 glTexCoord2d(X1
* TexMaxS
, Y1
* TexMaxT
); glVertex2d(OX
+ X1
*zoom
, OY
+ Y1
* zoom
)
2911 glTexCoord2d(X0
* TexMaxS
, Y1
* TexMaxT
); glVertex2d(OX
+ X0
*zoom
, OY
+ Y1
* zoom
)
2915 glBindTexture(TextureTarget
, Tcurrent
)
2916 glColor4d(1.0, 1.0, 1.0, 1.0 - t
* t
* t
)
2918 glTexCoord2d( 0.0, 0.0); glVertex2d(t
* X0
, t
* Y0
)
2919 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(t
* X1
+ t1
, t
* Y0
)
2920 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(t
* X1
+ t1
, t
* Y1
+ t1
)
2921 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(t
* X0
, t
* Y1
+ t1
)
2924 DrawOSDEx(OSDTitlePos
, CurrentOSDCaption
, alpha_factor
=t
)
2925 DrawOSDEx(OSDPagePos
, CurrentOSDPage
, alpha_factor
=t
)
2926 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
, alpha_factor
=t
)
2928 pygame
.display
.flip()
2929 TransitionRunning
= False
2931 # overview keyboard navigation
2932 def OverviewKeyboardNav(delta
):
2933 global OverviewSelection
2934 dest
= OverviewSelection
+ delta
2935 if (dest
>= OverviewPageCount
) or (dest
< 0):
2937 OverviewSelection
= dest
2938 x
, y
= OverviewPos(OverviewSelection
)
2939 pygame
.mouse
.set_pos((x
+ (OverviewCellX
/ 2), y
+ (OverviewCellY
/ 2)))
2941 # overview mode PageProp toggle
2942 def OverviewTogglePageProp(prop
, default
):
2943 if (OverviewSelection
< 0) or (OverviewSelection
>= len(OverviewPageMap
)):
2945 page
= OverviewPageMap
[OverviewSelection
]
2946 SetPageProp(page
, prop
, not(GetPageProp(page
, prop
, default
)))
2947 UpdateCaption(page
, force
=True)
2950 # overview event handler
2951 def HandleOverviewEvent(event
):
2952 global OverviewSelection
, TimeDisplay
2954 if event
.type == QUIT
:
2955 PageLeft(overview
=True)
2957 elif event
.type == VIDEOEXPOSE
:
2960 elif event
.type == KEYDOWN
:
2961 if (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
2962 pygame
.event
.post(pygame
.event
.Event(QUIT
))
2963 elif event
.unicode == u
'f':
2964 SetFullscreen(not Fullscreen
)
2965 elif event
.unicode == u
't':
2966 TimeDisplay
= not(TimeDisplay
)
2968 elif event
.unicode == u
'r':
2970 if TimeDisplay
: DrawOverview()
2971 elif event
.unicode == u
's':
2972 SaveInfoScript(InfoScriptPath
)
2973 elif event
.unicode == u
'o':
2974 OverviewTogglePageProp('overview', GetPageProp(Pcurrent
, '_overview', True))
2975 elif event
.unicode == u
'i':
2976 OverviewTogglePageProp('skip', False)
2977 elif event
.key
== K_UP
: OverviewKeyboardNav(-OverviewGridSize
)
2978 elif event
.key
== K_LEFT
: OverviewKeyboardNav(-1)
2979 elif event
.key
== K_RIGHT
: OverviewKeyboardNav(+1)
2980 elif event
.key
== K_DOWN
: OverviewKeyboardNav(+OverviewGridSize
)
2981 elif event
.key
== K_TAB
:
2982 OverviewSelection
= -1
2984 elif event
.key
in (K_RETURN
, K_KP_ENTER
):
2986 elif IsValidShortcutKey(event
.key
):
2987 if event
.mod
& KMOD_SHIFT
:
2989 AssignShortcut(OverviewPageMap
[OverviewSelection
], event
.key
)
2991 pass # no valid page selected
2994 page
= FindShortcut(event
.key
)
2996 OverviewSelection
= OverviewPageMapInv
[page
]
2997 x
, y
= OverviewPos(OverviewSelection
)
2998 pygame
.mouse
.set_pos((x
+ (OverviewCellX
/ 2), \
2999 y
+ (OverviewCellY
/ 2)))
3002 elif event
.type == MOUSEBUTTONUP
:
3003 if event
.button
== 1:
3005 elif event
.button
in (2, 3):
3006 OverviewSelection
= -1
3009 elif event
.type == MOUSEMOTION
:
3010 pygame
.event
.clear(MOUSEMOTION
)
3011 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3013 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3015 # determine highlighted page
3016 OverviewSelection
= \
3017 int((event
.pos
[0] - OverviewOfsX
) / OverviewCellX
) + \
3018 int((event
.pos
[1] - OverviewOfsY
) / OverviewCellY
) * OverviewGridSize
3019 if (OverviewSelection
< 0) or (OverviewSelection
>= len(OverviewPageMap
)):
3022 UpdateCaption(OverviewPageMap
[OverviewSelection
])
3025 elif event
.type == USEREVENT_HIDE_MOUSE
:
3026 # mouse timer event -> hide fullscreen cursor
3027 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3033 # overview mode entry/loop/exit function
3035 global Pcurrent
, Pnext
, Tcurrent
, Tnext
, Tracing
, OverviewSelection
3036 global PageEnterTime
, OverviewMode
3038 pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, 0)
3040 UpdateOverviewTexture()
3042 if GetPageProp(Pcurrent
, 'boxes') or Tracing
:
3043 BoxFade(lambda t
: 1.0 - t
)
3045 OverviewSelection
= OverviewPageMapInv
[Pcurrent
]
3048 OverviewZoom(lambda t
: 1.0 - t
)
3050 PageEnterTime
= pygame
.time
.get_ticks() - StartTime
3052 event
= pygame
.event
.poll()
3053 if event
.type == NOEVENT
:
3054 force_update
= OverviewNeedUpdate
3055 if OverviewNeedUpdate
:
3056 UpdateOverviewTexture()
3057 if TimerTick() or force_update
:
3059 pygame
.time
.wait(20)
3060 elif not HandleOverviewEvent(event
):
3062 PageLeft(overview
=True)
3064 if (OverviewSelection
< 0) or (OverviewSelection
>= OverviewPageCount
):
3065 OverviewSelection
= OverviewPageMapInv
[Pcurrent
]
3068 Pnext
= OverviewPageMap
[OverviewSelection
]
3069 if Pnext
!= Pcurrent
:
3071 RenderPage(Pcurrent
, Tcurrent
)
3072 UpdateCaption(Pcurrent
)
3073 OverviewZoom(lambda t
: t
)
3074 OverviewMode
= False
3077 if GetPageProp(Pcurrent
, 'boxes'):
3078 BoxFade(lambda t
: t
)
3080 if not PreloadNextPage(GetNextPage(Pcurrent
, 1)):
3081 PreloadNextPage(GetNextPage(Pcurrent
, -1))
3084 ##### EVENT HANDLING ###########################################################
3086 # set fullscreen mode
3087 def SetFullscreen(fs
, do_init
=True):
3090 # let pygame do the real work
3092 if fs
== Fullscreen
: return
3093 if not pygame
.display
.toggle_fullscreen(): return
3096 # redraw the current page (pygame is too lazy to send an expose event ...)
3099 # show cursor and set auto-hide timer
3101 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3103 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3107 def TogglePageProp(prop
, default
):
3109 SetPageProp(Pcurrent
, prop
, not(GetPageProp(Pcurrent
, prop
, default
)))
3110 UpdateCaption(Pcurrent
, force
=True)
3114 # main event handling function
3115 def HandleEvent(event
):
3116 global HaveMark
, ZoomMode
, Marking
, Tracing
, Panning
, SpotRadius
, FileStats
3117 global MarkUL
, MarkLR
, MouseDownX
, MouseDownY
, PanAnchorX
, PanAnchorY
3118 global ZoomX0
, ZoomY0
, RTrunning
, RTrestart
, StartTime
, PageEnterTime
3119 global CurrentTime
, TimeDisplay
, TimeTracking
, ProgressBarPos
3121 if event
.type == QUIT
:
3124 elif event
.type == VIDEOEXPOSE
:
3127 elif event
.type == KEYDOWN
:
3131 elif (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
3132 pygame
.event
.post(pygame
.event
.Event(QUIT
))
3133 elif event
.unicode == u
'f':
3134 SetFullscreen(not Fullscreen
)
3135 elif (event
.key
== K_TAB
) and (event
.mod
& KMOD_ALT
) and Fullscreen
:
3136 SetFullscreen(False)
3137 elif event
.unicode == u
's':
3138 SaveInfoScript(InfoScriptPath
)
3139 elif event
.unicode == u
'z': # handle QWERTY and QWERTZ keyboards
3143 tx
, ty
= MouseToScreen(pygame
.mouse
.get_pos())
3144 EnterZoomMode(0.5 * tx
, 0.5 * ty
)
3145 elif event
.unicode == u
'b':
3147 elif event
.unicode == u
'w':
3149 elif event
.unicode == u
't':
3150 TimeDisplay
= not(TimeDisplay
)
3152 if TimeDisplay
and not(TimeTracking
) and FirstPage
:
3153 print >>sys
.stderr
, "Time tracking mode enabled."
3155 print "page duration enter leave"
3156 print "---- -------- -------- --------"
3157 elif event
.unicode == u
'r':
3159 if TimeDisplay
: DrawCurrentPage()
3160 elif event
.unicode == u
'l':
3161 TransitionTo(LastPage
)
3162 elif event
.unicode == u
'o':
3163 TogglePageProp('overview', GetPageProp(Pcurrent
, '_overview', True))
3164 elif event
.unicode == u
'i':
3165 TogglePageProp('skip', False)
3166 elif event
.key
== K_TAB
:
3169 elif event
.key
in (32, K_DOWN
, K_RIGHT
, K_PAGEDOWN
):
3171 TransitionTo(GetNextPage(Pcurrent
, 1, 1))
3172 elif event
.key
in (K_BACKSPACE
, K_UP
, K_LEFT
, K_PAGEUP
):
3174 TransitionTo(GetNextPage(Pcurrent
, -1, 1))
3175 elif event
.key
== K_HOME
:
3178 elif event
.key
== K_END
:
3179 if Pcurrent
!= PageCount
:
3180 TransitionTo(PageCount
)
3181 elif event
.key
in (K_RETURN
, K_KP_ENTER
):
3182 if not(GetPageProp(Pcurrent
, 'boxes')) and Tracing
:
3183 BoxFade(lambda t
: 1.0 - t
)
3184 Tracing
= not(Tracing
)
3185 if not(GetPageProp(Pcurrent
, 'boxes')) and Tracing
:
3186 BoxFade(lambda t
: t
)
3187 elif event
.unicode == u
'+':
3188 IncrementSpotSize(+8)
3189 elif event
.unicode == u
'-':
3190 IncrementSpotSize(-8)
3191 elif event
.unicode == u
'[':
3192 SetGamma(new_gamma
=Gamma
/ GammaStep
)
3193 elif event
.unicode == u
']':
3194 SetGamma(new_gamma
=Gamma
* GammaStep
)
3195 elif event
.unicode == u
'{':
3196 SetGamma(new_black
=BlackLevel
- BlackLevelStep
)
3197 elif event
.unicode == u
'}':
3198 SetGamma(new_black
=BlackLevel
+ BlackLevelStep
)
3199 elif event
.unicode == u
'\\':
3202 keyfunc
= GetPageProp(Pcurrent
, 'keys', {}).get(event
.unicode, None)
3205 elif IsValidShortcutKey(event
.key
):
3206 if event
.mod
& KMOD_SHIFT
:
3207 AssignShortcut(Pcurrent
, event
.key
)
3209 # load keyboard shortcut
3210 page
= FindShortcut(event
.key
)
3211 if page
and (page
!= Pcurrent
):
3214 elif event
.type == MOUSEBUTTONDOWN
:
3219 MouseDownX
, MouseDownY
= event
.pos
3220 if event
.button
== 1:
3221 MarkUL
= MarkLR
= MouseToScreen(event
.pos
)
3222 elif (event
.button
== 3) and ZoomMode
:
3225 elif event
.button
== 4:
3226 IncrementSpotSize(+8)
3227 elif event
.button
== 5:
3228 IncrementSpotSize(-8)
3230 elif event
.type == MOUSEBUTTONUP
:
3237 if event
.button
== 2:
3241 if event
.button
== 1:
3243 # left mouse button released in marking mode -> stop box marking
3245 # reject too small boxes
3246 if (abs(MarkUL
[0] - MarkLR
[0]) > 0.04) \
3247 and (abs(MarkUL
[1] - MarkLR
[1]) > 0.03):
3248 boxes
= GetPageProp(Pcurrent
, 'boxes', [])
3249 oldboxcount
= len(boxes
)
3250 boxes
.append(NormalizeRect(MarkUL
[0], MarkUL
[1], MarkLR
[0], MarkLR
[1]))
3251 SetPageProp(Pcurrent
, 'boxes', boxes
)
3252 if not(oldboxcount
) and not(Tracing
):
3253 BoxFade(lambda t
: t
)
3256 # left mouse button released, but no marking
3258 dest
= GetNextPage(Pcurrent
, 1)
3260 for valid
, target
, x0
, y0
, x1
, y1
in GetPageProp(Pcurrent
, '_href', []):
3261 if valid
and (x
>= x0
) and (x
< x1
) and (y
>= y0
) and (y
< y1
):
3264 if type(dest
) == types
.IntType
:
3268 if (event
.button
== 3) and not(Panning
):
3269 # right mouse button -> check if a box has to be killed
3270 boxes
= GetPageProp(Pcurrent
, 'boxes', [])
3271 x
, y
= MouseToScreen(event
.pos
)
3273 # if a box is already present around the clicked position, kill it
3274 idx
= FindBox(x
, y
, boxes
)
3275 if (len(boxes
) == 1) and not(Tracing
):
3276 BoxFade(lambda t
: 1.0 - t
)
3278 SetPageProp(Pcurrent
, 'boxes', boxes
)
3281 # no box present -> go to previous page
3283 TransitionTo(GetNextPage(Pcurrent
, -1))
3286 elif event
.type == MOUSEMOTION
:
3287 pygame
.event
.clear(MOUSEMOTION
)
3288 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3290 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3292 # don't react on mouse input during video playback
3293 if VideoPlaying
: return
3294 # activate marking if mouse is moved away far enough
3295 if event
.buttons
[0] and not(Marking
):
3297 if (abs(x
- MouseDownX
) > 4) and (abs(y
- MouseDownY
) > 4):
3299 # mouse move while marking -> update marking box
3301 MarkLR
= MouseToScreen(event
.pos
)
3302 # mouse move while RMB is pressed -> panning
3303 if event
.buttons
[2] and ZoomMode
:
3305 if not(Panning
) and (abs(x
- MouseDownX
) > 4) and (abs(y
- MouseDownY
) > 4):
3307 ZoomX0
= PanAnchorX
+ (MouseDownX
- x
) * ZoomArea
/ ScreenWidth
3308 ZoomY0
= PanAnchorY
+ (MouseDownY
- y
) * ZoomArea
/ ScreenHeight
3309 ZoomX0
= min(max(ZoomX0
, 0.0), 1.0 - ZoomArea
)
3310 ZoomY0
= min(max(ZoomY0
, 0.0), 1.0 - ZoomArea
)
3311 # if anything changed, redraw the page
3312 if Marking
or Tracing
or event
.buttons
[2] or (CursorImage
and CursorVisible
):
3315 elif event
.type == USEREVENT_HIDE_MOUSE
:
3316 # mouse timer event -> hide fullscreen cursor
3317 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3321 elif event
.type == USEREVENT_PAGE_TIMEOUT
:
3322 TransitionTo(GetNextPage(Pcurrent
, 1))
3324 elif event
.type == USEREVENT_POLL_FILE
:
3327 if my_stat(f
) != GetFileProp(f
, 'stat'):
3331 # first, check if the new file is valid
3332 if not os
.path
.isfile(GetPageProp(Pcurrent
, '_file')):
3334 # invalidate everything we used to know about the input files
3336 for props
in PageProps
.itervalues():
3337 for prop
in ('_overview_rendered', '_box', '_href'):
3338 if prop
in props
: del props
[prop
]
3340 # force a transition to the current page, reloading it
3342 TransitionTo(Pcurrent
)
3343 # restart the background renderer thread. this is not completely safe,
3344 # i.e. there's a small chance that we fail to restart the thread, but
3345 # this isn't critical
3346 if CacheMode
and BackgroundRendering
:
3351 thread
.start_new_thread(RenderThread
, (Pcurrent
, Pnext
))
3353 elif event
.type == USEREVENT_TIMER_UPDATE
:
3358 ##### FILE LIST GENERATION #####################################################
3360 def IsImageFileName(name
):
3361 return os
.path
.splitext(name
)[1].lower() in \
3362 (".jpg", ".jpeg", ".png", ".tif", ".tiff", ".bmp", ".ppm", ".pgm")
3363 def IsPlayable(name
):
3364 return IsImageFileName(name
) or name
.lower().endswith(".pdf") or os
.path
.isdir(name
)
3366 def AddFile(name
, title
=None):
3367 global FileList
, FileName
3369 if os
.path
.isfile(name
):
3370 FileList
.append(name
)
3371 if title
: SetFileProp(name
, 'title', title
)
3373 elif os
.path
.isdir(name
):
3374 images
= [os
.path
.join(name
, f
) for f
in os
.listdir(name
) if IsImageFileName(f
)]
3375 images
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
3377 print >>sys
.stderr
, "Warning: no image files in directory `%s'" % name
3378 for img
in images
: AddFile(img
)
3380 elif name
.startswith('@') and os
.path
.isfile(name
[1:]):
3382 dirname
= os
.path
.dirname(name
)
3387 line
= [part
.strip() for part
in line
.split('#', 1)]
3392 subfile
, title
= line
3394 AddFile(os
.path
.normpath(os
.path
.join(dirname
, subfile
)), title
)
3397 print >>sys
.stderr
, "Error: cannot read list file `%s'" % name
3404 files
= list(filter(IsPlayable
, glob
.glob(name
)))
3406 for f
in files
: AddFile(f
)
3408 print >>sys
.stderr
, "Error: input file `%s' not found" % name
3411 ##### INITIALIZATION ###########################################################
3414 global ScreenWidth
, ScreenHeight
, TexWidth
, TexHeight
, TexSize
, LogoImage
3415 global TexMaxS
, TexMaxT
, MeshStepX
, MeshStepY
, EdgeX
, EdgeY
, PixelX
, PixelY
3416 global OverviewGridSize
, OverviewCellX
, OverviewCellY
3417 global OverviewOfsX
, OverviewOfsY
, OverviewImage
, OverviewPageCount
3418 global OverviewPageMap
, OverviewPageMapInv
, FileName
, FileList
, PageCount
3419 global DocumentTitle
, PageProps
, LogoTexture
, OSDFont
3420 global Pcurrent
, Pnext
, Tcurrent
, Tnext
, InitialPage
3421 global CacheFile
, CacheFileName
3422 global Extensions
, AllowExtensions
, TextureTarget
, PAR
, DAR
, TempFileName
3423 global BackgroundRendering
, FileStats
, RTrunning
, RTrestart
, StartTime
3424 global CursorImage
, CursorVisible
, InfoScriptPath
3426 # allocate temporary file
3427 TempFileName
= tempfile
.mktemp(prefix
="Accentuate-", suffix
="_tmp")
3429 # some input guesswork
3430 DocumentTitle
= os
.path
.splitext(os
.path
.split(FileName
)[1])[0]
3431 if FileName
and not(FileList
):
3433 if not(FileName
) and (len(FileList
) == 1):
3434 FileName
= FileList
[0]
3436 # fill the page list
3438 for name
in FileList
:
3439 ispdf
= name
.lower().endswith(".pdf")
3441 # PDF input -> try to pre-parse the PDF file
3443 # phase 1: internal PDF parser
3445 pages
, pdf_width
, pdf_height
= analyze_pdf(name
)
3447 pdf_width
, pdf_height
= (pdf_height
, pdf_width
)
3448 res
= min(ScreenWidth
* 72.0 / pdf_width
, \
3449 ScreenHeight
* 72.0 / pdf_height
)
3453 # phase 2: use pdftk
3455 assert 0 == spawn(os
.P_WAIT
, pdftkPath
, \
3456 ["pdftk", FileNameEscape
+ name
+ FileNameEscape
, \
3457 "dump_data", "output", TempFileName
+ ".txt"])
3458 title
, pages
= pdftkParse(TempFileName
+ ".txt", PageCount
)
3459 if DocumentTitle
and title
: DocumentTitle
= title
3465 SetPageProp(PageCount
+ 1, '_title', os
.path
.split(name
)[-1])
3469 print >>sys
.stderr
, "Warning: The input file `%s' could not be analyzed." % name
3472 # add pages and files into PageProps and FileProps
3473 pagerange
= list(range(PageCount
+ 1, PageCount
+ pages
+ 1))
3474 for page
in pagerange
:
3475 SetPageProp(page
, '_file', name
)
3476 if ispdf
: SetPageProp(page
, '_page', page
- PageCount
)
3477 title
= GetFileProp(name
, 'title')
3478 if title
: SetPageProp(page
, '_title', title
)
3479 SetFileProp(name
, 'pages', GetFileProp(name
, 'pages', []) + pagerange
)
3480 SetFileProp(name
, 'offsets', GetFileProp(name
, 'offsets', []) + [PageCount
])
3481 if not GetFileProp(name
, 'stat'): SetFileProp(name
, 'stat', my_stat(name
))
3482 if ispdf
: SetFileProp(name
, 'res', res
)
3485 # no pages? strange ...
3487 print >>sys
.stderr
, "The presentation doesn't have any pages, quitting."
3490 # if rendering is wanted, do it NOW
3491 if RenderToDirectory
:
3492 sys
.exit(DoRender())
3494 # load and execute info script
3495 if not InfoScriptPath
:
3496 InfoScriptPath
= FileName
+ ".info"
3499 # initialize graphics
3501 if Fullscreen
and UseAutoScreenSize
:
3502 size
= GetScreenSize()
3504 ScreenWidth
, ScreenHeight
= size
3505 print >>sys
.stderr
, "Detected screen size: %dx%d pixels" % (ScreenWidth
, ScreenHeight
)
3506 flags
= OPENGL|DOUBLEBUF
3510 pygame
.display
.set_mode((ScreenWidth
, ScreenHeight
), flags
)
3512 print >>sys
.stderr
, "FATAL: cannot create rendering surface in the desired resolution (%dx%d)" % (ScreenWidth
, ScreenHeight
)
3514 pygame
.display
.set_caption(__title__
)
3515 pygame
.key
.set_repeat(500, 30)
3517 pygame
.mouse
.set_visible(False)
3518 CursorVisible
= False
3519 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
3520 if (Gamma
<> 1.0) or (BlackLevel
<> 0):
3521 SetGamma(force
=True)
3523 # check if graphics are unaccelerated
3524 renderer
= glGetString(GL_RENDERER
)
3525 print >>sys
.stderr
, "OpenGL renderer:", renderer
3526 if renderer
.lower() in ("mesa glx indirect", "gdi generic"):
3527 print >>sys
.stderr
, "WARNING: Using an OpenGL software renderer. Accentuate will work, but it will"
3528 print >>sys
.stderr
, " very likely be too slow to be usable."
3530 # setup the OpenGL texture mode
3531 Extensions
= dict([(ext
.split('_', 2)[-1], None) for ext
in \
3532 glGetString(GL_EXTENSIONS
).split()])
3533 if AllowExtensions
and ("texture_non_power_of_two" in Extensions
):
3534 print >>sys
.stderr
, "Using GL_ARB_texture_non_power_of_two."
3535 TextureTarget
= GL_TEXTURE_2D
3536 TexWidth
= ScreenWidth
3537 TexHeight
= ScreenHeight
3540 elif AllowExtensions
and ("texture_rectangle" in Extensions
):
3541 print >>sys
.stderr
, "Using GL_ARB_texture_rectangle."
3542 TextureTarget
= 0x84F5 # GL_TEXTURE_RECTANGLE_ARB
3543 TexWidth
= ScreenWidth
3544 TexHeight
= ScreenHeight
3545 TexMaxS
= ScreenWidth
3546 TexMaxT
= ScreenHeight
3548 print >>sys
.stderr
, "Using conventional power-of-two textures with padding."
3549 TextureTarget
= GL_TEXTURE_2D
3550 TexWidth
= npot(ScreenWidth
)
3551 TexHeight
= npot(ScreenHeight
)
3552 TexMaxS
= ScreenWidth
* 1.0 / TexWidth
3553 TexMaxT
= ScreenHeight
* 1.0 / TexHeight
3554 TexSize
= TexWidth
* TexHeight
* 3
3556 # set up some variables
3558 PAR
= DAR
/ float(ScreenWidth
) * float(ScreenHeight
)
3559 MeshStepX
= 1.0 / MeshResX
3560 MeshStepY
= 1.0 / MeshResY
3561 PixelX
= 1.0 / ScreenWidth
3562 PixelY
= 1.0 / ScreenHeight
3563 EdgeX
= BoxEdgeSize
* 1.0 / ScreenWidth
3564 EdgeY
= BoxEdgeSize
* 1.0 / ScreenHeight
3565 if InitialPage
is None:
3566 InitialPage
= GetNextPage(0, 1)
3567 Pcurrent
= InitialPage
3569 # prepare logo image
3570 LogoImage
= Image
.open(StringIO
.StringIO(LOGO
))
3571 LogoTexture
= glGenTextures(1)
3572 glBindTexture(GL_TEXTURE_2D
, LogoTexture
)
3573 glTexImage2D(GL_TEXTURE_2D
, 0, 1, 256, 64, 0, GL_LUMINANCE
, GL_UNSIGNED_BYTE
, LogoImage
.tostring())
3574 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
)
3576 pygame
.display
.flip()
3578 # initialize OSD font
3580 OSDFont
= GLFont(FontTextureWidth
, FontTextureHeight
, FontList
, FontSize
, search_path
=FontPath
)
3583 for key
in ('title', '_title'):
3584 titles
.extend([p
[key
] for p
in PageProps
.itervalues() if key
in p
])
3586 OSDFont
.AddString("".join(titles
))
3588 print >>sys
.stderr
, "The OSD font size is too large, the OSD will be rendered incompletely."
3590 print >>sys
.stderr
, "Could not open OSD font file, disabling OSD."
3591 except (NameError, AttributeError, TypeError):
3592 print >>sys
.stderr
, "Your version of PIL is too old or incomplete, disabling OSD."
3594 # initialize mouse cursor
3597 CursorImage
= PrepareCustomCursor(Image
.open(CursorImage
))
3599 print >>sys
.stderr
, "Could not open the mouse cursor image, using standard cursor."
3603 if CacheMode
== PersistentCache
:
3604 if not CacheFileName
:
3605 CacheFileName
= FileName
+ ".cache"
3607 if CacheMode
== FileCache
:
3608 CacheFile
= tempfile
.TemporaryFile(prefix
="Accentuate-", suffix
=".cache")
3610 # initialize overview metadata
3611 OverviewPageMap
=[i
for i
in xrange(1, PageCount
+ 1) \
3612 if GetPageProp(i
, ('overview', '_overview'), True) \
3613 and (i
>= PageRangeStart
) and (i
<= PageRangeEnd
)]
3614 OverviewPageCount
= max(len(OverviewPageMap
), 1)
3615 OverviewPageMapInv
= {}
3616 for page
in xrange(1, PageCount
+ 1):
3617 OverviewPageMapInv
[page
] = len(OverviewPageMap
) - 1
3618 for i
in xrange(len(OverviewPageMap
)):
3619 if OverviewPageMap
[i
] >= page
:
3620 OverviewPageMapInv
[page
] = i
3623 # initialize overview page geometry
3624 OverviewGridSize
= 1
3625 while OverviewPageCount
> OverviewGridSize
* OverviewGridSize
:
3626 OverviewGridSize
+= 1
3627 OverviewCellX
= int(ScreenWidth
/ OverviewGridSize
)
3628 OverviewCellY
= int(ScreenHeight
/ OverviewGridSize
)
3629 OverviewOfsX
= int((ScreenWidth
- OverviewCellX
* OverviewGridSize
)/2)
3630 OverviewOfsY
= int((ScreenHeight
- OverviewCellY
* \
3631 int((OverviewPageCount
+ OverviewGridSize
- 1) / OverviewGridSize
)) / 2)
3632 OverviewImage
= Image
.new('RGB', (TexWidth
, TexHeight
))
3634 # fill overlay "dummy" images
3635 dummy
= LogoImage
.copy()
3636 maxsize
= (OverviewCellX
- 2 * OverviewBorder
, OverviewCellY
- 2 * OverviewBorder
)
3637 if (dummy
.size
[0] > maxsize
[0]) or (dummy
.size
[1] > maxsize
[1]):
3638 dummy
.thumbnail(ZoomToFit(dummy
.size
, maxsize
), Image
.ANTIALIAS
)
3639 margX
= int((OverviewCellX
- dummy
.size
[0]) / 2)
3640 margY
= int((OverviewCellY
- dummy
.size
[1]) / 2)
3641 dummy
= dummy
.convert(mode
='RGB')
3642 for page
in range(OverviewPageCount
):
3643 pos
= OverviewPos(page
)
3644 OverviewImage
.paste(dummy
, (pos
[0] + margX
, pos
[1] + margY
))
3647 # set up background rendering
3648 if not EnableBackgroundRendering
:
3649 print >>sys
.stderr
, "Background rendering isn't available on this platform."
3650 BackgroundRendering
= False
3652 # if caching is enabled, pre-render all pages
3653 if CacheMode
and not(BackgroundRendering
):
3656 pygame
.display
.flip()
3657 for pdf
in FileProps
:
3658 if pdf
.lower().endswith(".pdf"):
3662 for page
in range(InitialPage
, PageCount
+ 1) + range(1, InitialPage
):
3663 event
= pygame
.event
.poll()
3664 while event
.type != NOEVENT
:
3665 if event
.type == KEYDOWN
:
3666 if (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
3669 elif event
.type == MOUSEBUTTONUP
:
3671 event
= pygame
.event
.poll()
3673 if (page
>= PageRangeStart
) and (page
<= PageRangeEnd
):
3676 progress
+= 1.0 / PageCount
;
3677 DrawProgress(progress
)
3678 pygame
.display
.flip()
3680 # create buffer textures
3682 pygame
.display
.flip()
3683 glEnable(TextureTarget
)
3684 Tcurrent
= glGenTextures(1)
3685 Tnext
= glGenTextures(1)
3686 for T
in (Tcurrent
, Tnext
):
3687 glBindTexture(TextureTarget
, T
)
3688 glTexParameteri(TextureTarget
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
)
3689 glTexParameteri(TextureTarget
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
)
3690 glTexParameteri(TextureTarget
, GL_TEXTURE_WRAP_S
, GL_CLAMP
)
3691 glTexParameteri(TextureTarget
, GL_TEXTURE_WRAP_T
, GL_CLAMP
)
3693 # prebuffer current and next page
3695 RenderPage(Pcurrent
, Tcurrent
)
3696 PageEntered(update_time
=False)
3697 PreloadNextPage(GetNextPage(Pcurrent
, 1))
3699 # some other preparations
3700 PrepareTransitions()
3703 pygame
.time
.set_timer(USEREVENT_POLL_FILE
, PollInterval
* 1000)
3705 # start the background rendering thread
3706 if CacheMode
and BackgroundRendering
:
3708 thread
.start_new_thread(RenderThread
, (Pcurrent
, Pnext
))
3710 # start output and enter main loop
3711 StartTime
= pygame
.time
.get_ticks()
3712 pygame
.time
.set_timer(USEREVENT_TIMER_UPDATE
, 100)
3713 if not(Fullscreen
) and CursorImage
:
3714 pygame
.mouse
.set_visible(False)
3716 UpdateCaption(Pcurrent
)
3718 # Kick off LIRC thread
3719 if(UseLIRC
is True):
3724 HandleEvent(pygame
.event
.wait())
3727 # wrapper around main() that ensures proper uninitialization
3734 # ensure that background rendering is halted
3737 # remove all temp files
3738 if 'CacheFile' in globals():
3740 for tmp
in glob
.glob(TempFileName
+ "*"):
3746 if(UseLIRC
is True):
3750 ##### COMMAND-LINE PARSER AND HELP #############################################
3752 def if_op(cond
, res_then
, res_else
):
3753 if cond
: return res_then
3754 else: return res_else
3756 def HelpExit(code
=0):
3757 print """A nice presentation tool.
3759 Usage: """+os
.path
.basename(sys
.argv
[0])+""" [OPTION...] <INPUT(S)...>
3761 You may either play a PDF file, a directory containing image files or
3762 individual image files.
3765 -r, --rotate <n> rotate pages clockwise in 90-degree steps
3766 --scale scale images to fit screen (not used in PDF mode)
3767 --supersample use supersampling (only used in PDF mode)
3768 -s --supersample for PDF files, --scale for image files
3769 -I, --script <path> set the path of the info script
3770 -u, --poll <seconds> check periodically if the source file has been
3771 updated and reload it if it did
3772 -o, --output <dir> don't display the presentation, only render to .png
3773 -h, --help show this help text and exit
3776 -f, --fullscreen """+if_op(Fullscreen
,"do NOT ","")+"""start in fullscreen mode
3777 -g, --geometry <WxH> set window size or fullscreen resolution
3778 -A, --aspect <X:Y> adjust for a specific display aspect ratio (e.g. 5:4)
3779 -G, --gamma <G[:BL]> specify startup gamma and black level
3782 -i, --initialpage <n> start with page <n>
3783 -p, --pages <A-B> only cache pages in the specified range;
3784 implicitly sets -i <A>
3785 -w, --wrap go back to the first page after the last page
3786 -J, --fade fade to black after the last page
3787 -H, --hyperlink attempt to compensate for landscaped hyperlinks (hack)
3788 -a, --auto <seconds> automatically advance to next page after some seconds
3789 -O, --autooverview <x> automatically derive page visibility on overview page
3790 -O first = show pages with captions
3791 -O last = show pages before pages with captions
3794 -t, --transition <trans[,trans2...]>
3795 force a specific transitions or set of transitions
3796 -l, --listtrans print a list of available transitions and exit
3797 -F, --font <file> use a specific TrueType font file for the OSD
3798 -S, --fontsize <px> specify the OSD font size in pixels
3799 -C, --cursor <F[:X,Y]> use a .png image as the mouse cursor
3800 -L, --layout <spec> set the OSD layout (please read the documentation)
3803 -M, --minutes display time in minutes, not seconds
3804 -d, --duration <time> set the desired duration of the presentation and show
3805 a progress bar at the bottom of the screen
3806 -T, --transtime <ms> set transition duration in milliseconds
3807 -D, --mousedelay <ms> set mouse hide delay for fullscreen mode (in ms)
3808 -B, --boxfade <ms> set highlight box fade duration in milliseconds
3809 -Z, --zoom <ms> set zoom duration in milliseconds
3812 -c, --cache <mode> set page cache mode:
3813 -c none = disable caching completely
3814 -c memory = store cache in RAM
3815 -c disk = store cache on disk temporarily
3816 -c persistent = store cache on disk persistently
3817 --cachefile <path> set the persistent cache file path (implies -cp)
3818 -b, --noback don't pre-render images in the background
3819 -P, --gspath <path> set path to GhostScript or pdftoppm executable
3820 -R, --meshres <XxY> set mesh resolution for effects (default: 48x36)
3821 -e, --noext don't use OpenGL texture size extensions
3823 For detailed information, visit""", __website__
3826 def ListTransitions():
3827 print "Available transitions:"
3828 standard
= dict([(tc
.__name
__, None) for tc
in AvailableTransitions
])
3829 trans
= [(tc
.__name
__, tc
.__doc
__) for tc
in AllTransitions
]
3830 trans
.append(('None', "no transition"))
3832 maxlen
= max([len(item
[0]) for item
in trans
])
3833 for name
, desc
in trans
:
3834 if name
in standard
:
3838 print star
, name
.ljust(maxlen
), '-', desc
3839 print "(transitions with * are enabled by default)"
3842 def TryTime(s
, regexp
, func
):
3843 m
= re
.match(regexp
, s
, re
.I
)
3845 return func(map(int, m
.groups()))
3847 return TryTime(s
, r
'([0-9]+)s?$', lambda m
: m
[0]) \
3848 or TryTime(s
, r
'([0-9]+)m$', lambda m
: m
[0] * 60) \
3849 or TryTime(s
, r
'([0-9]+)[m:]([0-9]+)[ms]?$', lambda m
: m
[0] * 60 + m
[1]) \
3850 or TryTime(s
, r
'([0-9]+)[h:]([0-9]+)[hm]?$', lambda m
: m
[0] * 3600 + m
[1] * 60) \
3851 or TryTime(s
, r
'([0-9]+)[h:]([0-9]+)[m:]([0-9]+)s?$', lambda m
: m
[0] * 3600 + m
[1] * 60 + m
[2])
3854 print >>sys
.stderr
, "command line parse error:", msg
3855 print >>sys
.stderr
, "use `%s -h' to get help" % sys
.argv
[0]
3856 print >>sys
.stderr
, "or visit", __website__
, "for full documentation"
3859 def SetTransitions(list):
3860 global AvailableTransitions
3861 index
= dict([(tc
.__name
__.lower(), tc
) for tc
in AllTransitions
])
3862 index
['none'] = None
3863 AvailableTransitions
=[]
3864 for trans
in list.split(','):
3866 AvailableTransitions
.append(index
[trans
.lower()])
3868 opterr("unknown transition `%s'" % trans
)
3870 def ParseLayoutPosition(value
):
3873 for c
in value
.strip().lower():
3874 if c
== 't': ypos
.append(0)
3875 elif c
== 'b': ypos
.append(1)
3876 elif c
== 'l': xpos
.append(0)
3877 elif c
== 'r': xpos
.append(1)
3878 elif c
== 'c': xpos
.append(2)
3879 else: opterr("invalid position specification `%s'" % value
)
3880 if not xpos
: opterr("position `%s' lacks X component" % value
)
3881 if not ypos
: opterr("position `%s' lacks Y component" % value
)
3882 if len(xpos
)>1: opterr("position `%s' has multiple X components" % value
)
3883 if len(ypos
)>1: opterr("position `%s' has multiple Y components" % value
)
3884 return (xpos
[0] << 1) | ypos
[0]
3885 def SetLayoutSubSpec(key
, value
):
3886 global OSDTimePos
, OSDTitlePos
, OSDPagePos
, OSDStatusPos
3887 global OSDAlpha
, OSDMargin
3888 lkey
= key
.strip().lower()
3889 if lkey
in ('a', 'alpha', 'opacity'):
3891 OSDAlpha
= float(value
)
3893 opterr("invalid alpha value `%s'" % value
)
3895 OSDAlpha
*= 0.01 # accept percentages, too
3896 if (OSDAlpha
< 0.0) or (OSDAlpha
> 1.0):
3897 opterr("alpha value %s out of range" % value
)
3898 elif lkey
in ('margin', 'dist', 'distance'):
3900 OSDMargin
= float(value
)
3902 opterr("invalid margin value `%s'" % value
)
3904 opterr("margin value %s out of range" % value
)
3905 elif lkey
in ('t', 'time'):
3906 OSDTimePos
= ParseLayoutPosition(value
)
3907 elif lkey
in ('title', 'caption'):
3908 OSDTitlePos
= ParseLayoutPosition(value
)
3909 elif lkey
in ('page', 'number'):
3910 OSDPagePos
= ParseLayoutPosition(value
)
3911 elif lkey
in ('status', 'info'):
3912 OSDStatusPos
= ParseLayoutPosition(value
)
3914 opterr("unknown layout element `%s'" % key
)
3915 def SetLayout(spec
):
3916 for sub
in spec
.replace(':', '=').split(','):
3918 key
, value
= sub
.split('=')
3920 opterr("invalid layout spec `%s'" % sub
)
3921 SetLayoutSubSpec(key
, value
)
3923 def ParseCacheMode(arg
):
3924 arg
= arg
.strip().lower()
3925 if "none".startswith(arg
): return NoCache
3926 if "off".startswith(arg
): return NoCache
3927 if "memory".startswith(arg
): return MemCache
3928 if "disk".startswith(arg
): return FileCache
3929 if "file".startswith(arg
): return FileCache
3930 if "persistent".startswith(arg
): return PersistentCache
3931 opterr("invalid cache mode `%s'" % arg
)
3933 def ParseAutoOverview(arg
):
3934 arg
= arg
.strip().lower()
3935 if "off".startswith(arg
): return Off
3936 if "first".startswith(arg
): return First
3937 if "last".startswith(arg
): return Last
3940 assert (i
>= Off
) and (i
<= Last
)
3942 opterr("invalid auto-overview mode `%s'" % arg
)
3944 def ParseOptions(argv
):
3945 global FileName
, FileList
, Fullscreen
, Scaling
, Supersample
, CacheMode
3946 global TransitionDuration
, MouseHideDelay
, BoxFadeDuration
, ZoomDuration
3947 global ScreenWidth
, ScreenHeight
, MeshResX
, MeshResY
, InitialPage
, Wrap
3948 global AutoAdvance
, RenderToDirectory
, Rotation
, AllowExtensions
, DAR
3949 global BackgroundRendering
, UseAutoScreenSize
, PollInterval
, CacheFileName
3950 global PageRangeStart
, PageRangeEnd
, FontList
, FontSize
, Gamma
, BlackLevel
3951 global EstimatedDuration
, CursorImage
, CursorHotspot
, MinutesOnly
3952 global GhostScriptPath
, pdftoppmPath
, UseGhostScript
, InfoScriptPath
3953 global AutoOverview
, FadeToBlackAtEnd
3955 try: # unused short options: jknqvxyzEKNQUVWXY
3956 opts
, args
= getopt
.getopt(argv
, \
3957 "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:H", \
3958 ["help", "fullscreen", "geometry=", "scale", "supersample", \
3959 "nocache", "initialpage=", "wrap", "auto", "listtrans", "output=", \
3960 "rotate=", "transition=", "transtime=", "mousedelay=", "boxfade=", \
3961 "zoom=", "gspath=", "meshres=", "noext", "aspect", "memcache", \
3962 "noback", "pages=", "poll=", "font=", "fontsize=", "gamma=",
3963 "duration=", "cursor=", "minutes", "layout=", "script=", "cache=",
3964 "cachefile=", "autooverview=", "fade", "hyperlink"])
3965 except getopt
.GetoptError
, message
:
3968 for opt
, arg
in opts
:
3969 if opt
in ("-h", "--help"):
3971 if opt
in ("-l", "--listtrans"):
3973 if opt
in ("-f", "--fullscreen"):
3974 Fullscreen
= not(Fullscreen
)
3975 if opt
in ("-e", "--noext"):
3976 AllowExtensions
= not(AllowExtensions
)
3977 if opt
in ("-s", "--scale"):
3978 Scaling
= not(Scaling
)
3979 if opt
in ("-s", "--supersample"):
3981 if opt
in ("-w", "--wrap"):
3983 if opt
in ("-J", "--fade"):
3984 FadeToBlackAtEnd
= not(FadeToBlackAtEnd
)
3985 if opt
in ("-H", "--hyperlink"):
3986 PowerdotFix
= not(PowerdotFix
)
3987 if opt
in ("-O", "--autooverview"):
3988 AutoOverview
= ParseAutoOverview(arg
)
3989 if opt
in ("-c", "--cache"):
3990 CacheMode
= ParseCacheMode(arg
)
3991 if opt
== "--nocache":
3992 print >>sys
.stderr
, "Note: The `--nocache' option is deprecated, use `--cache none' instead."
3994 if opt
in ("-m", "--memcache"):
3995 print >>sys
.stderr
, "Note: The `--memcache' option is deprecated, use `--cache memory' instead."
3996 CacheMode
= MemCache
3997 if opt
== "--cachefile":
3999 CacheMode
= PersistentCache
4000 if opt
in ("-M", "--minutes"):
4001 MinutesOnly
= not(MinutesOnly
)
4002 if opt
in ("-b", "--noback"):
4003 BackgroundRendering
= not(BackgroundRendering
)
4004 if opt
in ("-t", "--transition"):
4006 if opt
in ("-L", "--layout"):
4008 if opt
in ("-o", "--output"):
4009 RenderToDirectory
= arg
4010 if opt
in ("-I", "--script"):
4011 InfoScriptPath
= arg
4012 if opt
in ("-F", "--font"):
4014 if opt
in ("-P", "--gspath"):
4015 UseGhostScript
= (arg
.replace("\\", "/").split("/")[-1].lower().find("pdftoppm") < 0)
4017 GhostScriptPath
= arg
4020 if opt
in ("-S", "--fontsize"):
4025 opterr("invalid parameter for --fontsize")
4026 if opt
in ("-i", "--initialpage"):
4028 InitialPage
= int(arg
)
4029 assert InitialPage
> 0
4031 opterr("invalid parameter for --initialpage")
4032 if opt
in ("-d", "--duration"):
4034 EstimatedDuration
= ParseTime(arg
)
4035 assert EstimatedDuration
> 0
4037 opterr("invalid parameter for --duration")
4038 if opt
in ("-a", "--auto"):
4040 AutoAdvance
= int(arg
) * 1000
4041 assert (AutoAdvance
> 0) and (AutoAdvance
<= 86400000)
4043 opterr("invalid parameter for --auto")
4044 if opt
in ("-T", "--transtime"):
4046 TransitionDuration
= int(arg
)
4047 assert (TransitionDuration
>= 0) and (TransitionDuration
< 32768)
4049 opterr("invalid parameter for --transtime")
4050 if opt
in ("-D", "--mousedelay"):
4052 MouseHideDelay
= int(arg
)
4053 assert (MouseHideDelay
>= 0) and (MouseHideDelay
< 32768)
4055 opterr("invalid parameter for --mousedelay")
4056 if opt
in ("-B", "--boxfade"):
4058 BoxFadeDuration
= int(arg
)
4059 assert (BoxFadeDuration
>= 0) and (BoxFadeDuration
< 32768)
4061 opterr("invalid parameter for --boxfade")
4062 if opt
in ("-Z", "--zoom"):
4064 ZoomDuration
= int(arg
)
4065 assert (ZoomDuration
>= 0) and (ZoomDuration
< 32768)
4067 opterr("invalid parameter for --zoom")
4068 if opt
in ("-r", "--rotate"):
4072 opterr("invalid parameter for --rotate")
4073 while Rotation
< 0: Rotation
+= 4
4074 Rotation
= Rotation
& 3
4075 if opt
in ("-u", "--poll"):
4077 PollInterval
= int(arg
)
4078 assert PollInterval
>= 0
4080 opterr("invalid parameter for --poll")
4081 if opt
in ("-g", "--geometry"):
4083 ScreenWidth
, ScreenHeight
= map(int, arg
.split("x"))
4084 assert (ScreenWidth
>= 320) and (ScreenWidth
< 4096)
4085 assert (ScreenHeight
>= 200) and (ScreenHeight
< 4096)
4086 UseAutoScreenSize
= False
4088 opterr("invalid parameter for --geometry")
4089 if opt
in ("-R", "--meshres"):
4091 MeshResX
, MeshResY
= map(int, arg
.split("x"))
4092 assert (MeshResX
> 0) and (MeshResX
<= ScreenWidth
)
4093 assert (MeshResY
> 0) and (MeshResY
<= ScreenHeight
)
4095 opterr("invalid parameter for --meshres")
4096 if opt
in ("-p", "--pages"):
4098 PageRangeStart
, PageRangeEnd
= map(int, arg
.split("-"))
4099 assert PageRangeStart
> 0
4100 assert PageRangeStart
<= PageRangeEnd
4102 opterr("invalid parameter for --pages")
4103 InitialPage
=PageRangeStart
4104 if opt
in ("-A", "--aspect"):
4107 fx
, fy
= map(float, arg
.split(':'))
4113 opterr("invalid parameter for --aspect")
4114 if opt
in ("-G", "--gamma"):
4117 arg
, bl
= arg
.split(':', 1)
4118 BlackLevel
= int(bl
)
4121 assert (BlackLevel
>= 0) and (BlackLevel
< 255)
4123 opterr("invalid parameter for --gamma")
4124 if opt
in ("-C", "--cursor"):
4127 arg
= arg
.split(':')
4129 CursorImage
= ':'.join(arg
[:-1])
4130 CursorHotspot
= map(int, arg
[-1].split(','))
4133 assert (BlackLevel
>= 0) and (BlackLevel
< 255)
4135 opterr("invalid parameter for --cursor")
4140 opterr("no playable files specified")
4143 # glob and filter argument list
4146 files
.extend(glob
.glob(arg
))
4147 files
= list(filter(IsPlayable
, files
))
4149 # if only one argument is specified, use it as the informal file name
4155 # construct final FileList by expanding directories to image file lists
4158 if os
.path
.isdir(item
):
4159 images
= [os
.path
.join(item
, f
) for f
in os
.listdir(item
) if IsImageFileName(f
)]
4160 images
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
4161 FileList
.extend(images
)
4163 FileList
.append(item
)
4166 opterr("no playable files specified")
4169 # use this function if you intend to use Accentuate as a library
4173 except SystemExit, e
:
4176 if __name__
=="__main__":
4177 ParseOptions(sys
.argv
[1:])