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
):
1491 if type(target
) == types
.IntType
:
1492 target
+= page_offset
1493 w
= 1.0 / (pagebox
[2] - pagebox
[0])
1494 h
= 1.0 / (pagebox
[3] - pagebox
[1])
1496 # Hack to fix problems with landscape/powerdot files with hyperlinks
1497 if(PowerdotFix
is True):
1503 x0
= (linkbox
[0] - pagebox
[0]) * w
1504 y0
= (pagebox
[3] - linkbox
[3]) * h
1505 x1
= (linkbox
[2] - pagebox
[0]) * w
1506 y1
= (pagebox
[3] - linkbox
[1]) * h
1508 href
= (0, target
, x0
, y0
, x1
, y1
)
1509 if GetPageProp(page
, '_href'):
1510 PageProps
[page
]['_href'].append(href
)
1512 SetPageProp(page
, '_href', [href
])
1515 def FixHyperlinks(page
):
1516 if not(GetPageProp(page
, '_box')) or not(GetPageProp(page
, '_href')):
1517 return # no hyperlinks or unknown page size
1518 bx0
, by0
, bx1
, by1
= GetPageProp(page
, '_box')
1522 for fixed
, target
, x0
, y0
, x1
, y1
in GetPageProp(page
, '_href'):
1524 href
.append((1, target
, x0
, y0
, x1
, y1
))
1526 href
.append((1, target
, \
1527 int(bx0
+ bdx
* x0
), int(by0
+ bdy
* y0
), \
1528 int(bx0
+ bdx
* x1
), int(by0
+ bdy
* y1
)))
1529 SetPageProp(page
, '_href', href
)
1532 def ParsePDF(filename
):
1534 assert 0 == spawn(os
.P_WAIT
, pdftkPath
, \
1535 ["pdftk", FileNameEscape
+ filename
+ FileNameEscape
, \
1536 "output", FileNameEscape
+ TempFileName
+ ".pdf" + FileNameEscape
,
1539 print >>sys
.stderr
, "Note: pdftk not found, hyperlinks disabled."
1541 except AssertionError:
1542 print >>sys
.stderr
, "Note: pdftk failed, hyperlinks disabled."
1548 pdf
= PDFParser(TempFileName
+ ".pdf")
1549 for page
, annots
in pdf
.GetHyperlinks().iteritems():
1550 for page_offset
in FileProps
[filename
]['offsets']:
1552 AddHyperlink(page_offset
, page
, a
[4], a
[:4], pdf
.box
[page
])
1553 count
+= len(annots
)
1558 print >>sys
.stderr
, "Note: file produced by pdftk not readable, hyperlinks disabled."
1560 print >>sys
.stderr
, "Note: error in file produced by pdftk, hyperlinks disabled."
1561 print >>sys
.stderr
, " PDF parser error message:", e
1564 os
.remove(TempFileName
+ ".pdf")
1569 ##### PAGE CACHE MANAGEMENT ####################################################
1571 # helper class that allows PIL to write and read image files with an offset
1573 def __init__(self
, f
, offset
=0):
1575 self
.offset
= offset
1577 def read(self
, count
=None):
1579 return self
.f
.read()
1581 return self
.f
.read(count
)
1582 def write(self
, data
):
1584 def seek(self
, pos
, whence
=0):
1585 assert(whence
in (0, 1))
1589 self
.f
.seek(pos
+ self
.offset
)
1591 return self
.f
.tell() - self
.offset
1593 # generate a "magic number" that is used to identify persistent cache files
1594 def UpdateCacheMagic():
1596 pool
= [PageCount
, ScreenWidth
, ScreenHeight
, b2s(Scaling
), b2s(Supersample
), b2s(Rotation
)]
1597 flist
= list(FileProps
.keys())
1598 flist
.sort(lambda a
,b
: cmp(a
.lower(), b
.lower()))
1601 pool
.extend(list(GetFileProp(f
, 'stat', [])))
1602 CacheMagic
= md5
.new("\0".join(map(str, pool
))).hexdigest()
1604 # set the persistent cache file position to the current end of the file
1605 def UpdatePCachePos():
1607 CacheFile
.seek(0, 2)
1608 CacheFilePos
= CacheFile
.tell()
1610 # rewrite the header of the persistent cache
1611 def WritePCacheHeader(reset
=False):
1612 pages
= ["%08x" % PageCache
.get(page
, 0) for page
in range(1, PageCount
+1)]
1614 CacheFile
.write(CacheMagic
+ "".join(pages
))
1616 CacheFile
.truncate()
1619 # return an image from the persistent cache or None if none is available
1620 def GetPCacheImage(page
):
1621 if CacheMode
!= PersistentCache
:
1622 return # not applicable if persistent cache isn't used
1625 if page
in PageCache
:
1626 img
= Image
.open(IOWrapper(CacheFile
, PageCache
[page
]))
1632 # returns an image from the non-persistent cache or None if none is available
1633 def GetCacheImage(page
):
1634 if CacheMode
in (NoCache
, PersistentCache
):
1635 return # not applicable in uncached or persistent-cache mode
1638 if page
in PageCache
:
1639 if CacheMode
== FileCache
:
1640 CacheFile
.seek(PageCache
[page
])
1641 return CacheFile
.read(TexSize
)
1643 return PageCache
[page
]
1647 # adds an image to the persistent cache
1648 def AddToPCache(page
, img
):
1649 if CacheMode
!= PersistentCache
:
1650 return # not applicable if persistent cache isn't used
1653 if page
in PageCache
:
1654 return # page is already cached and we can't update it safely
1655 # -> stop here (the new image will be identical to the old
1657 img
.save(IOWrapper(CacheFile
, CacheFilePos
), "ppm")
1658 PageCache
[page
] = CacheFilePos
1663 # adds an image to the non-persistent cache
1664 def AddToCache(page
, data
):
1666 if CacheMode
in (NoCache
, PersistentCache
):
1667 return # not applicable in uncached or persistent-cache mode
1670 if CacheMode
== FileCache
:
1671 if not(page
in PageCache
):
1672 PageCache
[page
] = CacheFilePos
1673 CacheFilePos
+= len(data
)
1674 CacheFile
.seek(PageCache
[page
])
1675 CacheFile
.write(data
)
1677 PageCache
[page
] = data
1681 # invalidates the whole cache
1682 def InvalidateCache():
1683 global PageCache
, CacheFilePos
1687 if CacheMode
== PersistentCache
:
1689 WritePCacheHeader(True)
1695 # initialize the persistent cache
1697 global CacheFile
, CacheMode
1699 # try to open the pre-existing cache file
1701 CacheFile
= file(CacheFileName
, "rb+")
1705 # check the cache magic
1707 if CacheFile
and (CacheFile
.read(32) != CacheMagic
):
1708 print >>sys
.stderr
, "Cache file mismatch, recreating cache."
1713 # if the magic was valid, import cache data
1714 print >>sys
.stderr
, "Using already existing persistent cache file."
1715 for page
in range(1, PageCount
+1):
1716 offset
= int(CacheFile
.read(8), 16)
1718 PageCache
[page
] = offset
1721 # if the magic was invalid or the file didn't exist, (re-)create it
1723 CacheFile
= file(CacheFileName
, "wb+")
1725 print >>sys
.stderr
, "Error: cannot write the persistent cache file (`%s')" % CacheFileName
1726 print >>sys
.stderr
, "Falling back to temporary file cache."
1727 CacheMode
= FileCache
1731 ##### PAGE RENDERING ###########################################################
1733 # generate a dummy image
1735 img
= Image
.new('RGB', (ScreenWidth
, ScreenHeight
))
1736 img
.paste(LogoImage
, ((ScreenWidth
- LogoImage
.size
[0]) / 2,
1737 (ScreenHeight
- LogoImage
.size
[1]) / 2))
1740 # load a page from a PDF file
1741 def RenderPDF(page
, MayAdjustResolution
, ZoomMode
):
1742 global UseGhostScript
1743 UseGhostScriptOnce
= False
1745 SourceFile
= GetPageProp(page
, '_file')
1746 Resolution
= GetFileProp(SourceFile
, 'res', 96)
1747 RealPage
= GetPageProp(page
, '_page')
1749 if Supersample
and not(ZoomMode
):
1750 UseRes
= int(0.5 + Resolution
) * Supersample
1753 UseRes
= int(0.5 + Resolution
)
1758 # call pdftoppm to generate the page image
1759 if not UseGhostScript
:
1760 renderer
= "pdftoppm"
1762 assert 0 == spawn(os
.P_WAIT
, \
1763 pdftoppmPath
, ["pdftoppm", "-q"] + [ \
1764 "-f", str(RealPage
), "-l", str(RealPage
),
1765 "-r", str(int(UseRes
)),
1766 FileNameEscape
+ SourceFile
+ FileNameEscape
,
1768 # determine output filename
1769 digits
= GetFileProp(SourceFile
, 'digits', 6)
1770 imgfile
= TempFileName
+ ("-%%0%dd.ppm" % digits
) % RealPage
1771 if not os
.path
.exists(imgfile
):
1772 for digits
in xrange(6, 0, -1):
1773 imgfile
= TempFileName
+ ("-%%0%dd.ppm" % digits
) % RealPage
1774 if os
.path
.exists(imgfile
): break
1775 SetFileProp(SourceFile
, 'digits', digits
)
1776 except OSError, (errcode
, errmsg
):
1777 print >>sys
.stderr
, "Warning: Cannot start pdftoppm -", errmsg
1778 print >>sys
.stderr
, "Falling back to GhostScript (permanently)."
1779 UseGhostScript
= True
1780 except AssertionError:
1781 print >>sys
.stderr
, "There was an error while rendering page %d" % page
1782 print >>sys
.stderr
, "Falling back to GhostScript for this page."
1783 UseGhostScriptOnce
= True
1785 # fallback to GhostScript
1786 if UseGhostScript
or UseGhostScriptOnce
:
1787 imgfile
= TempFileName
+ ".tif"
1788 renderer
= "GhostScript"
1790 assert 0 == spawn(os
.P_WAIT
, \
1791 GhostScriptPath
, ["gs", "-q"] + GhostScriptPlatformOptions
+ [ \
1792 "-dBATCH", "-dNOPAUSE", "-sDEVICE=tiff24nc", "-dUseCropBox",
1793 "-sOutputFile=" + imgfile
, \
1794 "-dFirstPage=%d" % RealPage
, "-dLastPage=%d" % RealPage
,
1795 "-r%dx%d" % (UseRes
, int(UseRes
* PAR
)), \
1796 "-dTextAlphaBits=%d" % AlphaBits
, \
1797 "-dGraphicsAlphaBits=%s" % AlphaBits
, \
1798 FileNameEscape
+ SourceFile
+ FileNameEscape
])
1799 except OSError, (errcode
, errmsg
):
1800 print >>sys
.stderr
, "Error: Cannot start GhostScript -", errmsg
1802 except AssertionError:
1803 print >>sys
.stderr
, "There was an error while rendering page %d" % page
1806 # open the page image file with PIL
1808 img
= Image
.open(imgfile
)
1810 print >>sys
.stderr
, "Error: %s produced an unreadable file (page %d)" % (renderer
, page
)
1813 # try to delete the file again (this constantly fails on Win32 ...)
1820 rot
= GetPageProp(page
, 'rotate')
1824 img
= img
.rotate(90 * (4 - rot
))
1826 # determine real display size (don't care for ZoomMode, DisplayWidth and
1827 # DisplayHeight are only used for Supersample and AdjustResolution anyway)
1829 DisplayWidth
= img
.size
[0] / Supersample
1830 DisplayHeight
= img
.size
[1] / Supersample
1832 DisplayWidth
= img
.size
[0]
1833 DisplayHeight
= img
.size
[1]
1835 # if the image size is strange, re-adjust the rendering resolution
1836 if MayAdjustResolution \
1837 and ((abs(ScreenWidth
- DisplayWidth
) > 4) \
1838 or (abs(ScreenHeight
- DisplayHeight
) > 4)):
1839 newsize
= ZoomToFit((DisplayWidth
,DisplayHeight
))
1840 NewResolution
= newsize
[0] * Resolution
/DisplayWidth
1841 if abs(1.0 - NewResolution
/ Resolution
) > 0.05:
1842 # only modify anything if the resolution deviation is large enough
1843 SetFileProp(SourceFile
, 'res', NewResolution
)
1844 return RenderPDF(page
, False, ZoomMode
)
1846 # downsample a supersampled image
1847 if Supersample
and not(ZoomMode
):
1848 return img
.resize((DisplayWidth
, DisplayHeight
), Image
.ANTIALIAS
)
1853 # load a page from an image file
1854 def LoadImage(page
, ZoomMode
):
1855 # open the image file with PIL
1857 img
= Image
.open(GetPageProp(page
, '_file'))
1859 print >>sys
.stderr
, "Image file `%s' is broken." % (FileList
[page
- 1])
1863 rot
= GetPageProp(page
, 'rotate')
1867 img
= img
.rotate(90 * (4 - rot
))
1869 # determine destination size
1870 newsize
= ZoomToFit(img
.size
)
1871 # don't scale if the source size is too close to the destination size
1872 if abs(newsize
[0] - img
.size
[0]) < 2: newsize
= img
.size
1873 # don't scale if the source is smaller than the destination
1874 if not(Scaling
) and (newsize
> img
.size
): newsize
= img
.size
1875 # zoom up (if wanted)
1876 if ZoomMode
: newsize
=(2 * newsize
[0], 2 * newsize
[1])
1877 # skip processing if there was no change
1878 if newsize
== img
.size
: return img
1880 # select a nice filter and resize the image
1881 if newsize
> img
.size
:
1882 filter = Image
.BICUBIC
1884 filter = Image
.ANTIALIAS
1885 return img
.resize(newsize
, filter)
1888 # render a page to an OpenGL texture
1889 def PageImage(page
, ZoomMode
=False, RenderMode
=False):
1890 global OverviewNeedUpdate
1891 EnableCacheRead
= not(ZoomMode
or RenderMode
)
1892 EnableCacheWrite
= EnableCacheRead
and \
1893 (page
>= PageRangeStart
) and (page
<= PageRangeEnd
)
1895 # check for the image in the cache
1897 data
= GetCacheImage(page
)
1898 if data
: return data
1900 # if it's not in the temporary cache, render it
1903 # retrieve the image from the persistent cache or fully re-render it
1905 img
= GetPCacheImage(page
)
1909 if GetPageProp(page
, '_page'):
1910 img
= RenderPDF(page
, not(ZoomMode
), ZoomMode
)
1912 img
= LoadImage(page
, ZoomMode
)
1913 if EnableCacheWrite
:
1914 AddToPCache(page
, img
)
1916 # create black background image to paste real image onto
1918 TextureImage
= Image
.new('RGB', (2 * TexWidth
, 2 * TexHeight
))
1919 TextureImage
.paste(img
, ((2 * ScreenWidth
- img
.size
[0]) / 2, \
1920 (2 * ScreenHeight
- img
.size
[1]) / 2))
1922 TextureImage
= Image
.new('RGB', (TexWidth
, TexHeight
))
1923 x0
= (ScreenWidth
- img
.size
[0]) / 2
1924 y0
= (ScreenHeight
- img
.size
[1]) / 2
1925 TextureImage
.paste(img
, (x0
, y0
))
1926 SetPageProp(page
, '_box', (x0
, y0
, x0
+ img
.size
[0], y0
+ img
.size
[1]))
1929 # paste thumbnail into overview image
1930 if GetPageProp(page
, ('overview', '_overview'), True) \
1931 and (page
>= PageRangeStart
) and (page
<= PageRangeEnd
) \
1932 and not(GetPageProp(page
, '_overview_rendered')) \
1933 and not(RenderMode
):
1934 pos
= OverviewPos(OverviewPageMapInv
[page
])
1937 # first, fill the underlying area with black (i.e. remove the dummy logo)
1938 blackness
= Image
.new('RGB', (OverviewCellX
- OverviewBorder
, \
1939 OverviewCellY
- OverviewBorder
))
1940 OverviewImage
.paste(blackness
, (pos
[0] + OverviewBorder
/ 2, \
1941 pos
[1] + OverviewBorder
))
1943 # then, scale down the original image and paste it
1944 img
.thumbnail((OverviewCellX
- 2 * OverviewBorder
, \
1945 OverviewCellY
- 2 * OverviewBorder
), \
1947 OverviewImage
.paste(img
, \
1948 (pos
[0] + (OverviewCellX
- img
.size
[0]) / 2, \
1949 pos
[1] + (OverviewCellY
- img
.size
[1]) / 2))
1952 SetPageProp(page
, '_overview_rendered', True)
1953 OverviewNeedUpdate
= True
1956 # return texture data
1959 data
=TextureImage
.tostring()
1964 # finally add it back into the cache and return it
1965 if EnableCacheWrite
:
1966 AddToCache(page
, data
)
1969 # render a page to an OpenGL texture
1970 def RenderPage(page
, target
):
1971 glBindTexture(TextureTarget
,target
)
1973 glTexImage2D(TextureTarget
, 0, 3, TexWidth
, TexHeight
, 0,\
1974 GL_RGB
, GL_UNSIGNED_BYTE
, PageImage(page
))
1976 print >>sys
.stderr
, "I'm sorry, but your graphics card is not capable of rendering presentations"
1977 print >>sys
.stderr
, "in this resolution. Either the texture memory is exhausted, or there is no"
1978 print >>sys
.stderr
, "support for large textures (%dx%d). Please try to run Accentuate in a" % (TexWidth
, TexHeight
)
1979 print >>sys
.stderr
, "smaller resolution using the -g command-line option."
1982 # background rendering thread
1983 def RenderThread(p1
, p2
):
1984 global RTrunning
, RTrestart
1989 for pdf
in FileProps
:
1990 if not pdf
.lower().endswith(".pdf"): continue
1993 if RTrestart
: continue
1994 for page
in xrange(1, PageCount
+ 1):
1996 if (page
!= p1
) and (page
!= p2
) \
1997 and (page
>= PageRangeStart
) and (page
<= PageRangeEnd
):
2000 if CacheMode
>= FileCache
:
2001 print >>sys
.stderr
, "Background rendering finished, used %.1f MiB of disk space." %\
2002 (CacheFilePos
/ 1048576.0)
2005 ##### RENDER MODE ##############################################################
2008 global TexWidth
, TexHeight
2009 TexWidth
= ScreenWidth
2010 TexHeight
= ScreenHeight
2011 if os
.path
.exists(RenderToDirectory
):
2012 print >>sys
.stderr
, "Destination directory `%s' already exists," % RenderToDirectory
2013 print >>sys
.stderr
, "refusing to overwrite anything."
2016 os
.mkdir(RenderToDirectory
)
2018 print >>sys
.stderr
, "Cannot create destination directory `%s':" % RenderToDirectory
2019 print >>sys
.stderr
, e
.strerror
2021 print >>sys
.stderr
, "Rendering presentation into `%s'" % RenderToDirectory
2022 for page
in xrange(1, PageCount
+ 1):
2023 PageImage(page
, RenderMode
=True).save("%s/page%04d.png" % (RenderToDirectory
, page
))
2024 sys
.stdout
.write("[%d] " % page
)
2027 print >>sys
.stderr
, "Done."
2031 ##### INFO SCRIPT I/O ##########################################################
2033 # info script reader
2034 def LoadInfoScript():
2037 OldPageProps
= PageProps
2038 execfile(InfoScriptPath
, globals())
2039 NewPageProps
= PageProps
2040 PageProps
= OldPageProps
2042 for page
in NewPageProps
:
2043 for prop
in NewPageProps
[page
]:
2044 SetPageProp(page
, prop
, NewPageProps
[page
][prop
])
2049 print >>sys
.stderr
, "----- Exception in info script ----"
2050 traceback
.print_exc(file=sys
.stderr
)
2051 print >>sys
.stderr
, "----- End of traceback -----"
2053 # we can't save lamba expressions, so we need to warn the user
2054 # in every possible way
2055 ScriptTainted
= False
2056 LambdaWarning
= False
2057 def here_was_a_lambda_expression_that_could_not_be_saved():
2058 global LambdaWarning
2059 if not LambdaWarning
:
2060 print >>sys
.stderr
, "WARNING: The info script for the current file contained lambda expressions that"
2061 print >>sys
.stderr
, " were removed during the a save operation."
2062 LambdaWarning
= True
2064 # "clean" a PageProps entry so that only 'public' properties are left
2065 def GetPublicProps(props
):
2066 props
= props
.copy()
2067 # delete private (underscore) props
2068 for prop
in list(props
.keys()):
2069 if str(prop
)[0] == '_':
2071 # clean props to default values
2072 if props
.get('overview', False):
2073 del props
['overview']
2074 if not props
.get('skip', True):
2076 if ('boxes' in props
) and not(props
['boxes']):
2080 # Generate a string representation of a property value. Mainly this converts
2081 # classes or instances to the name of the class.
2082 def PropValueRepr(value
):
2083 global ScriptTainted
2084 if type(value
) == types
.FunctionType
:
2085 if value
.__name
__ != "<lambda>":
2086 return value
.__name
__
2087 if not ScriptTainted
:
2088 print >>sys
.stderr
, "WARNING: The info script contains lambda expressions, which cannot be saved"
2089 print >>sys
.stderr
, " back. The modifed script will be written into a separate file to"
2090 print >>sys
.stderr
, " minimize data loss."
2091 ScriptTainted
= True
2092 return "here_was_a_lambda_expression_that_could_not_be_saved"
2093 elif type(value
) == types
.ClassType
:
2094 return value
.__name
__
2095 elif type(value
) == types
.InstanceType
:
2096 return value
.__class
__.__name
__
2097 elif type(value
) == types
.DictType
:
2098 return "{ " + ", ".join([PropValueRepr(k
) + ": " + PropValueRepr(value
[k
]) for k
in value
]) + " }"
2102 # generate a nicely formatted string representation of a page's properties
2103 def SinglePagePropRepr(page
):
2104 props
= GetPublicProps(PageProps
[page
])
2105 if not props
: return None
2106 return "\n%3d: {%s\n }" % (page
, \
2107 ",".join(["\n " + repr(prop
) + ": " + PropValueRepr(props
[prop
]) for prop
in props
]))
2109 # generate a nicely formatted string representation of all page properties
2111 pages
= PageProps
.keys()
2113 return "PageProps = {%s\n}" % (",".join(filter(None, map(SinglePagePropRepr
, pages
))))
2115 # count the characters of a python dictionary source code, correctly handling
2116 # embedded strings and comments, and nested dictionaries
2117 def CountDictChars(s
, start
=0):
2120 for i
in xrange(start
, len(s
)):
2123 if c
== '{': level
+= 1
2124 if c
== '}': level
-= 1
2125 if c
== '#': context
= '#'
2126 if c
== '"': context
= '"'
2127 if c
== "'": context
= "'"
2128 elif context
[0] == "\\":
2130 elif context
== '#':
2131 if c
in "\r\n": context
= None
2132 elif context
== '"':
2133 if c
== "\\": context
= "\\\""
2134 if c
== '"': context
= None
2135 elif context
== "'":
2136 if c
== "\\": context
= "\\'"
2137 if c
== "'": context
= None
2138 if level
< 0: return i
2139 raise ValueError, "the dictionary never ends"
2141 # modify and save a file's info script
2142 def SaveInfoScript(filename
):
2143 # read the old info script
2145 f
= file(filename
, "r")
2151 script
= "# -*- coding: iso-8859-1 -*-\n"
2153 # replace the PageProps of the old info script with the current ones
2155 m
= re
.search("^.*(PageProps)\s*=\s*(\{).*$", script
,re
.MULTILINE
)
2157 script
= script
[:m
.start(1)] + PagePropRepr() + \
2158 script
[CountDictChars(script
, m
.end(2)) + 1 :]
2160 script
+= "\n" + PagePropRepr() + "\n"
2161 except (AttributeError, ValueError):
2165 filename
+= ".modified"
2167 # write the script back
2169 f
= file(filename
, "w")
2173 print >>sys
.stderr
, "Oops! Could not write info script!"
2176 ##### OPENGL RENDERING #########################################################
2180 reltime
= pygame
.time
.get_ticks() - StartTime
2181 if EstimatedDuration
and (OverviewMode
or GetPageProp(Pcurrent
, 'progress', True)):
2182 rel
= (0.001 * reltime
) / EstimatedDuration
2183 x
= int(ScreenWidth
* rel
)
2184 y
= 1.0 - ProgressBarSize
* PixelX
2185 a
= min(255, max(0, x
- ScreenWidth
))
2186 b
= min(255, max(0, x
- ScreenWidth
- 256))
2190 glDisable(TextureTarget
)
2191 glDisable(GL_TEXTURE_2D
)
2193 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
2195 glColor4ub(r
, g
, b
, 0)
2198 glColor4ub(r
, g
, b
, ProgressBarAlpha
)
2199 glVertex2d(rel
, 1.0)
2204 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
)
2207 DrawOSDEx(OSDTimePos
, FormatTime(t
, MinutesOnly
))
2208 if CurrentOSDComment
and (OverviewMode
or not(TransitionRunning
)):
2209 DrawOSD(ScreenWidth
/2, \
2210 ScreenHeight
- 3*OSDMargin
- FontSize
, \
2211 CurrentOSDComment
, Center
, Up
)
2212 if CursorImage
and CursorVisible
:
2213 x
, y
= pygame
.mouse
.get_pos()
2214 x
-= CursorHotspot
[0]
2215 y
-= CursorHotspot
[1]
2220 glDisable(TextureTarget
)
2221 glEnable(GL_TEXTURE_2D
)
2222 glBindTexture(GL_TEXTURE_2D
, CursorTexture
)
2224 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
)
2225 glColor4ub(255, 255, 255, 255)
2227 glTexCoord2d(0.0, 0.0); glVertex2d(X0
, Y0
)
2228 glTexCoord2d(CursorTX
, 0.0); glVertex2d(X1
, Y0
)
2229 glTexCoord2d(CursorTX
, CursorTY
); glVertex2d(X1
, Y1
)
2230 glTexCoord2d(0.0, CursorTY
); glVertex2d(X0
, Y1
)
2233 glDisable(GL_TEXTURE_2D
)
2235 # draw the complete image of the current page
2236 def DrawCurrentPage(dark
=1.0, do_flip
=True):
2237 if VideoPlaying
: return
2238 boxes
= GetPageProp(Pcurrent
, 'boxes')
2239 glClear(GL_COLOR_BUFFER_BIT
)
2241 # pre-transform for zoom
2243 glOrtho(ZoomX0
, ZoomX0
+ ZoomArea
, ZoomY0
+ ZoomArea
, ZoomY0
, -10.0, 10.0)
2245 # background layer -- the page's image, darkened if it has boxes
2247 glEnable(TextureTarget
)
2248 glBindTexture(TextureTarget
, Tcurrent
)
2249 if boxes
or Tracing
:
2250 light
= 1.0 - 0.25 * dark
2253 glColor3d(light
, light
, light
)
2256 if boxes
or Tracing
:
2257 # alpha-blend the same image some times to blur it
2259 DrawTranslatedFullQuad(+PixelX
* ZoomArea
, 0.0, light
, dark
/ 2)
2260 DrawTranslatedFullQuad(-PixelX
* ZoomArea
, 0.0, light
, dark
/ 3)
2261 DrawTranslatedFullQuad(0.0, +PixelY
* ZoomArea
, light
, dark
/ 4)
2262 DrawTranslatedFullQuad(0.0, -PixelY
* ZoomArea
, light
, dark
/ 5)
2265 # draw outer box fade
2267 for X0
, Y0
, X1
, Y1
in boxes
:
2268 glBegin(GL_QUAD_STRIP
)
2269 DrawPointEx(X0
, Y0
, 1); DrawPointEx(X0
- EdgeX
, Y0
- EdgeY
, 0)
2270 DrawPointEx(X1
, Y0
, 1); DrawPointEx(X1
+ EdgeX
, Y0
- EdgeY
, 0)
2271 DrawPointEx(X1
, Y1
, 1); DrawPointEx(X1
+ EdgeX
, Y1
+ EdgeY
, 0)
2272 DrawPointEx(X0
, Y1
, 1); DrawPointEx(X0
- EdgeX
, Y1
+ EdgeY
, 0)
2273 DrawPointEx(X0
, Y0
, 1); DrawPointEx(X0
- EdgeX
, Y0
- EdgeY
, 0)
2279 for X0
, Y0
, X1
, Y1
in boxes
:
2287 x
, y
= MouseToScreen(pygame
.mouse
.get_pos())
2290 glBegin(GL_TRIANGLE_STRIP
)
2291 for x0
, y0
, x1
, y1
in SpotMesh
:
2292 DrawPointEx(x
+ x0
, y
+ y0
, 1)
2293 DrawPointEx(x
+ x1
, y
+ y1
, 0)
2297 glBegin(GL_TRIANGLE_FAN
)
2299 for x0
, y0
, x1
, y1
in SpotMesh
:
2300 DrawPoint(x
+ x0
, y
+ y0
)
2304 # soft alpha-blended rectangle
2305 glDisable(TextureTarget
)
2306 glColor4d(*MarkColor
)
2309 glVertex2d(MarkUL
[0], MarkUL
[1])
2310 glVertex2d(MarkLR
[0], MarkUL
[1])
2311 glVertex2d(MarkLR
[0], MarkLR
[1])
2312 glVertex2d(MarkUL
[0], MarkLR
[1])
2316 glBegin(GL_LINE_STRIP
)
2317 glVertex2d(MarkUL
[0], MarkUL
[1])
2318 glVertex2d(MarkLR
[0], MarkUL
[1])
2319 glVertex2d(MarkLR
[0], MarkLR
[1])
2320 glVertex2d(MarkUL
[0], MarkLR
[1])
2321 glVertex2d(MarkUL
[0], MarkUL
[1])
2323 glEnable(TextureTarget
)
2325 # unapply the zoom transform
2327 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
2332 pygame
.display
.flip()
2334 # draw a black screen with the Accentuate logo at the center
2336 glClear(GL_COLOR_BUFFER_BIT
)
2337 glColor3ub(255, 255, 255)
2338 if TextureTarget
!= GL_TEXTURE_2D
:
2339 glDisable(TextureTarget
)
2340 glEnable(GL_TEXTURE_2D
)
2341 glBindTexture(GL_TEXTURE_2D
, LogoTexture
)
2343 glTexCoord2d(0, 0); glVertex2d(0.5 - 128.0 / ScreenWidth
, 0.5 - 32.0 / ScreenHeight
)
2344 glTexCoord2d(1, 0); glVertex2d(0.5 + 128.0 / ScreenWidth
, 0.5 - 32.0 / ScreenHeight
)
2345 glTexCoord2d(1, 1); glVertex2d(0.5 + 128.0 / ScreenWidth
, 0.5 + 32.0 / ScreenHeight
)
2346 glTexCoord2d(0, 1); glVertex2d(0.5 - 128.0 / ScreenWidth
, 0.5 + 32.0 / ScreenHeight
)
2349 OSDFont
.Draw((ScreenWidth
/ 2, ScreenHeight
/ 2 + 48), \
2350 __version__
, align
=Center
, alpha
=0.25)
2351 glDisable(GL_TEXTURE_2D
)
2353 # draw the prerender progress bar
2354 def DrawProgress(position
):
2355 glDisable(TextureTarget
)
2358 x1
= position
* x2
+ (1.0 - position
) * x0
2360 y0
= y1
- 16.0 / ScreenHeight
2362 glColor3ub( 64, 64, 64); glVertex2d(x0
, y0
); glVertex2d(x2
, y0
)
2363 glColor3ub(128, 128, 128); glVertex2d(x2
, y1
); glVertex2d(x0
, y1
)
2364 glColor3ub( 64, 128, 255); glVertex2d(x0
, y0
); glVertex2d(x1
, y0
)
2365 glColor3ub( 8, 32, 128); glVertex2d(x1
, y1
); glVertex2d(x0
, y1
)
2367 glEnable(TextureTarget
)
2370 def DrawFadeMode(intensity
, alpha
):
2371 if VideoPlaying
: return
2372 DrawCurrentPage(do_flip
=False)
2373 glDisable(TextureTarget
)
2375 glColor4d(intensity
, intensity
, intensity
, alpha
)
2377 glEnable(TextureTarget
)
2378 pygame
.display
.flip()
2380 def FadeMode(intensity
):
2381 t0
= pygame
.time
.get_ticks()
2383 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2384 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BlankFadeDuration
2386 DrawFadeMode(intensity
, t
)
2387 DrawFadeMode(intensity
, 1.0)
2390 event
= pygame
.event
.wait()
2391 if event
.type == QUIT
:
2394 elif event
.type == VIDEOEXPOSE
:
2395 DrawFadeMode(intensity
, 1.0)
2396 elif event
.type == MOUSEBUTTONUP
:
2398 elif event
.type == KEYDOWN
:
2399 if event
.unicode == u
'q':
2400 pygame
.event
.post(pygame
.event
.Event(QUIT
))
2404 t0
= pygame
.time
.get_ticks()
2406 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2407 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BlankFadeDuration
2409 DrawFadeMode(intensity
, 1.0 - t
)
2413 def SetGamma(new_gamma
=None, new_black
=None, force
=False):
2414 global Gamma
, BlackLevel
2415 if new_gamma
is None: new_gamma
= Gamma
2416 if new_gamma
< 0.1: new_gamma
= 0.1
2417 if new_gamma
> 10.0: new_gamma
= 10.0
2418 if new_black
is None: new_black
= BlackLevel
2419 if new_black
< 0: new_black
= 0
2420 if new_black
> 254: new_black
= 254
2421 if not(force
) and (abs(Gamma
- new_gamma
) < 0.01) and (new_black
== BlackLevel
):
2424 BlackLevel
= new_black
2425 scale
= 1.0 / (255 - BlackLevel
)
2427 ramp
= [int(65535.0 * ((max(0, x
- BlackLevel
) * scale
) ** power
)) for x
in range(256)]
2428 return pygame
.display
.set_gamma_ramp(ramp
, ramp
, ramp
)
2431 def PrepareCustomCursor(cimg
):
2432 global CursorTexture
, CursorSX
, CursorSY
, CursorTX
, CursorTY
2434 tw
, th
= map(npot
, cimg
.size
)
2435 if (tw
> 256) or (th
> 256):
2436 print >>sys
.stderr
, "Custom cursor is rediculously large, reverting to normal one."
2438 img
= Image
.new('RGBA', (tw
, th
))
2439 img
.paste(cimg
, (0, 0))
2440 CursorTexture
= glGenTextures(1)
2441 glBindTexture(GL_TEXTURE_2D
, CursorTexture
)
2442 glTexImage2D(GL_TEXTURE_2D
, 0, GL_RGBA
, tw
, th
, 0, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.tostring())
2443 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
)
2444 CursorSX
= w
* PixelX
2445 CursorSY
= h
* PixelY
2446 CursorTX
= w
/ float(tw
)
2447 CursorTY
= h
/ float(th
)
2451 ##### CONTROL AND NAVIGATION ###################################################
2453 # update the applications' title bar
2454 def UpdateCaption(page
=0, force
=False):
2455 global CurrentCaption
, CurrentOSDCaption
, CurrentOSDPage
, CurrentOSDStatus
2456 global CurrentOSDComment
2457 if (page
== CurrentCaption
) and not(force
):
2459 CurrentCaption
= page
2462 caption
+= " - " + DocumentTitle
2464 CurrentOSDCaption
= ""
2466 CurrentOSDStatus
= ""
2467 CurrentOSDComment
= ""
2468 pygame
.display
.set_caption(caption
, __title__
)
2470 CurrentOSDPage
= "%d/%d" % (page
, PageCount
)
2471 caption
= "%s (%s)" % (caption
, CurrentOSDPage
)
2472 title
= GetPageProp(page
, 'title') or GetPageProp(page
, '_title')
2474 caption
+= ": %s" % title
2475 CurrentOSDCaption
= title
2477 CurrentOSDCaption
= ""
2479 if GetPageProp(page
, 'skip', False):
2480 status
.append("skipped: yes")
2481 if not GetPageProp(page
, ('overview', '_overview'), True):
2482 status
.append("on overview page: no")
2483 CurrentOSDStatus
= ", ".join(status
)
2484 CurrentOSDComment
= GetPageProp(page
, 'comment')
2485 pygame
.display
.set_caption(caption
, __title__
)
2487 # get next/previous page
2488 def GetNextPage(page
, direction
, keypressed
=0):
2491 try_page
+= direction
2492 if try_page
== page
:
2493 return 0 # tried all pages, but none found
2495 if try_page
< 1: try_page
= PageCount
2496 if try_page
> PageCount
: try_page
= 1
2497 elif (FadeToBlackAtEnd
and keypressed
):
2498 if try_page
> PageCount
:
2501 if try_page
< 1 or try_page
> PageCount
:
2502 return 0 # start or end of presentation
2503 if not GetPageProp(try_page
, 'skip', False):
2506 # pre-load the following page into Pnext/Tnext
2507 def PreloadNextPage(page
):
2509 if (page
< 1) or (page
> PageCount
):
2514 RenderPage(page
, Tnext
)
2518 # perform box fading; the fade animation time is mapped through func()
2520 t0
= pygame
.time
.get_ticks()
2522 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2523 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / BoxFadeDuration
2525 DrawCurrentPage(func(t
))
2526 DrawCurrentPage(func(1.0))
2531 global StartTime
, PageEnterTime
2532 if TimeTracking
and not(FirstPage
):
2533 print "--- timer was reset here ---"
2534 StartTime
= pygame
.time
.get_ticks()
2537 # start video playback
2538 def PlayVideo(video
):
2539 global MPlayerPID
, VideoPlaying
2540 if not video
: return
2543 MPlayerPID
= spawn(os
.P_NOWAIT
, \
2544 MPlayerPath
, [MPlayerPath
, "-quiet", \
2545 "-monitorpixelaspect", "1:1", "-autosync", "100"] + \
2546 MPlayerPlatformOptions
+ [ "-slave", \
2547 "-wid", str(pygame
.display
.get_wm_info()['window']), \
2548 FileNameEscape
+ video
+ FileNameEscape
])
2550 glClear(GL_COLOR_BUFFER_BIT
)
2551 pygame
.display
.flip()
2556 # called each time a page is entered
2557 def PageEntered(update_time
=True):
2558 global PageEnterTime
, MPlayerPID
, IsZoomed
, WantStatus
2560 PageEnterTime
= pygame
.time
.get_ticks() - StartTime
2561 IsZoomed
= False # no, we don't have a pre-zoomed image right now
2562 WantStatus
= False # don't show status unless it's changed interactively
2563 timeout
= AutoAdvance
2564 shown
= GetPageProp(Pcurrent
, '_shown', 0)
2566 timeout
= GetPageProp(Pcurrent
, 'timeout', timeout
)
2567 video
= GetPageProp(Pcurrent
, 'video')
2568 sound
= GetPageProp(Pcurrent
, 'sound')
2570 if sound
and not(video
):
2573 MPlayerPID
= spawn(os
.P_NOWAIT
, \
2574 MPlayerPath
, [MPlayerPath
, "-quiet", "-really-quiet", \
2575 FileNameEscape
+ sound
+ FileNameEscape
])
2578 SafeCall(GetPageProp(Pcurrent
, 'OnEnterOnce'))
2579 SafeCall(GetPageProp(Pcurrent
, 'OnEnter'))
2580 if timeout
: pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, timeout
)
2581 SetPageProp(Pcurrent
, '_shown', shown
+ 1)
2583 # called each time a page is left
2584 def PageLeft(overview
=False):
2585 global FirstPage
, LastPage
, WantStatus
2588 if GetTristatePageProp(Pcurrent
, 'reset'):
2592 if GetPageProp(Pcurrent
, '_shown', 0) == 1:
2593 SafeCall(GetPageProp(Pcurrent
, 'OnLeaveOnce'))
2594 SafeCall(GetPageProp(Pcurrent
, 'OnLeave'))
2596 t1
= pygame
.time
.get_ticks() - StartTime
2597 dt
= (t1
- PageEnterTime
+ 500) / 1000
2601 p
= "%4d" % Pcurrent
2602 print "%s%9s%9s%9s" % (p
, FormatTime(dt
), \
2603 FormatTime(PageEnterTime
/ 1000), \
2604 FormatTime(t1
/ 1000))
2606 # perform a transition to a specified page
2607 def TransitionTo(page
):
2608 global Pcurrent
, Pnext
, Tcurrent
, Tnext
2609 global PageCount
, Marking
, Tracing
, Panning
, TransitionRunning
2611 # first, stop the auto-timer
2612 pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, 0)
2614 # invalid page? go away
2615 if not PreloadNextPage(page
):
2618 # notify that the page has been left
2622 if GetPageProp(Pcurrent
, 'boxes') or Tracing
:
2623 skip
= BoxFade(lambda t
: 1.0 - t
)
2632 # check if the transition is valid
2633 tpage
= min(Pcurrent
, Pnext
)
2634 if 'transition' in PageProps
[tpage
]:
2637 tkey
= '_transition'
2638 trans
= PageProps
[tpage
][tkey
]
2642 transtime
= GetPageProp(tpage
, 'transtime', TransitionDuration
)
2644 dummy
= trans
.__class
__
2645 except AttributeError:
2646 # ah, gotcha! the transition is not yet intantiated!
2648 PageProps
[tpage
][tkey
] = trans
2650 # backward motion? then swap page buffers now
2651 backward
= (Pnext
< Pcurrent
)
2653 Pcurrent
, Pnext
= (Pnext
, Pcurrent
)
2654 Tcurrent
, Tnext
= (Tnext
, Tcurrent
)
2656 # transition animation
2657 if not(skip
) and transtime
:
2658 transtime
= 1.0 / transtime
2659 TransitionRunning
= True
2660 t0
= pygame
.time
.get_ticks()
2661 while not(VideoPlaying
):
2662 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]):
2665 t
= (pygame
.time
.get_ticks() - t0
) * transtime
2667 if backward
: t
= 1.0 - t
2668 glEnable(TextureTarget
)
2671 pygame
.display
.flip()
2672 TransitionRunning
= False
2674 # forward motion => swap page buffers now
2676 Pcurrent
, Pnext
= (Pnext
, Pcurrent
)
2677 Tcurrent
, Tnext
= (Tnext
, Tcurrent
)
2680 if not(skip
) and GetPageProp(Pcurrent
, 'boxes'): BoxFade(lambda t
: t
)
2682 # finally update the screen and preload the next page
2683 DrawCurrentPage() # I do that twice because for some strange reason, the
2685 if not PreloadNextPage(GetNextPage(Pcurrent
, 1)):
2686 PreloadNextPage(GetNextPage(Pcurrent
, -1))
2689 # zoom mode animation
2690 def ZoomAnimation(targetx
, targety
, func
):
2691 global ZoomX0
, ZoomY0
, ZoomArea
2692 t0
= pygame
.time
.get_ticks()
2694 if pygame
.event
.get([KEYDOWN
,MOUSEBUTTONUP
]): break
2695 t
= (pygame
.time
.get_ticks() - t0
)* 1.0 / ZoomDuration
2699 ZoomX0
= targetx
* t
2700 ZoomY0
= targety
* t
2701 ZoomArea
= 1.0 - 0.5 * t
2704 ZoomX0
= targetx
* t
2705 ZoomY0
= targety
* t
2706 ZoomArea
= 1.0 - 0.5 * t
2711 def EnterZoomMode(targetx
, targety
):
2712 global ZoomMode
, IsZoomed
, ZoomWarningIssued
2713 ZoomAnimation(targetx
, targety
, lambda t
: t
)
2715 if TextureTarget
!= GL_TEXTURE_2D
:
2716 if not ZoomWarningIssued
:
2717 print >>sys
.stderr
, "Sorry, but I can't increase the detail level in zoom mode any further when"
2718 print >>sys
.stderr
, "GL_ARB_texture_rectangle is used. Please try running Accentuate with the"
2719 print >>sys
.stderr
, "'-e' parameter. If a modern nVidia or ATI graphics card is used, a driver"
2720 print >>sys
.stderr
, "update may also fix the problem."
2721 ZoomWarningIssued
= True
2724 glBindTexture(TextureTarget
, Tcurrent
)
2726 glTexImage2D(TextureTarget
, 0, 3, TexWidth
* 2, TexHeight
* 2, 0, \
2727 GL_RGB
, GL_UNSIGNED_BYTE
, PageImage(Pcurrent
, True))
2729 if not ZoomWarningIssued
:
2730 print >>sys
.stderr
, "Sorry, but I can't increase the detail level in zoom mode any further, because"
2731 print >>sys
.stderr
, "your OpenGL implementation does not support that. Either the texture memory is"
2732 print >>sys
.stderr
, "exhausted, or there is no support for large textures (%dx%d). If you really" % (TexWidth
* 2, TexHeight
* 2)
2733 print >>sys
.stderr
, "need high-res zooming, please try to run Accentuate in a smaller resolution"
2734 print >>sys
.stderr
, "using the -g command-line option."
2735 ZoomWarningIssued
= True
2740 # leave zoom mode (if enabled)
2741 def LeaveZoomMode():
2743 if not ZoomMode
: return
2744 ZoomAnimation(ZoomX0
, ZoomY0
, lambda t
: 1.0 - t
)
2748 # increment/decrement spot radius
2749 def IncrementSpotSize(delta
):
2753 SpotRadius
= max(SpotRadius
+ delta
, 8)
2757 # post-initialize the page transitions
2758 def PrepareTransitions():
2759 Unspecified
= 0xAFFED00F
2760 # STEP 1: randomly assign transitions where the user didn't specify them
2761 cnt
= sum([1 for page
in xrange(1, PageCount
+ 1) \
2762 if GetPageProp(page
, 'transition', Unspecified
) == Unspecified
])
2763 newtrans
= ((cnt
/ len(AvailableTransitions
) + 1) * AvailableTransitions
)[:cnt
]
2764 random
.shuffle(newtrans
)
2765 for page
in xrange(1, PageCount
+ 1):
2766 if GetPageProp(page
, 'transition', Unspecified
) == Unspecified
:
2767 SetPageProp(page
, '_transition', newtrans
.pop())
2768 # STEP 2: instantiate transitions
2769 for page
in PageProps
:
2770 for key
in ('transition', '_transition'):
2771 if not key
in PageProps
[page
]:
2773 trans
= PageProps
[page
][key
]
2774 if trans
is not None:
2775 PageProps
[page
][key
] = trans()
2777 # update timer values and screen timer
2779 global CurrentTime
, ProgressBarPos
2781 newtime
= (pygame
.time
.get_ticks() - StartTime
) * 0.001
2782 if EstimatedDuration
:
2783 newpos
= int(ScreenWidth
* newtime
/ EstimatedDuration
)
2784 if newpos
!= ProgressBarPos
:
2786 ProgressBarPos
= newpos
2787 newtime
= int(newtime
)
2788 if TimeDisplay
and (CurrentTime
!= newtime
):
2790 CurrentTime
= newtime
2793 # set cursor visibility
2794 def SetCursor(visible
):
2795 global CursorVisible
2796 CursorVisible
= visible
2798 pygame
.mouse
.set_visible(visible
)
2801 def IsValidShortcutKey(key
):
2802 return ((key
>= K_a
) and (key
<= K_z
)) \
2803 or ((key
>= K_0
) and (key
<= K_9
)) \
2804 or ((key
>= K_F1
) and (key
<= K_F12
))
2805 def FindShortcut(shortcut
):
2806 for page
, props
in PageProps
.iteritems():
2808 check
= props
['shortcut']
2809 if type(check
) != types
.StringType
:
2811 elif (len(check
) > 1) and (check
[0] in "Ff"):
2812 check
= K_F1
- 1 + int(check
[1:])
2814 check
= ord(check
.lower())
2815 except (KeyError, TypeError, ValueError):
2817 if check
== shortcut
:
2820 def AssignShortcut(page
, key
):
2821 old_page
= FindShortcut(key
)
2823 del PageProps
[old_page
]['shortcut']
2826 elif (key
>= K_F1
) and (key
<= K_F15
):
2827 shortcut
= "F%d" % (key
- K_F1
+ 1)
2830 SetPageProp(page
, 'shortcut', shortcut
)
2833 ##### OVERVIEW MODE ############################################################
2835 def UpdateOverviewTexture():
2836 global OverviewNeedUpdate
2837 glBindTexture(TextureTarget
, Tnext
)
2840 glTexImage2D(TextureTarget
, 0, 3, TexWidth
, TexHeight
, 0, \
2841 GL_RGB
, GL_UNSIGNED_BYTE
, OverviewImage
.tostring())
2844 OverviewNeedUpdate
= False
2846 # draw the overview page
2848 if VideoPlaying
: return
2849 glClear(GL_COLOR_BUFFER_BIT
)
2851 glEnable(TextureTarget
)
2852 glBindTexture(TextureTarget
, Tnext
)
2853 glColor3ub(192, 192, 192)
2856 pos
= OverviewPos(OverviewSelection
)
2857 X0
= PixelX
* pos
[0]
2858 Y0
= PixelY
* pos
[1]
2859 X1
= PixelX
* (pos
[0] + OverviewCellX
)
2860 Y1
= PixelY
* (pos
[1] + OverviewCellY
)
2861 glColor3d(1.0, 1.0, 1.0)
2869 DrawOSDEx(OSDTitlePos
, CurrentOSDCaption
)
2870 DrawOSDEx(OSDPagePos
, CurrentOSDPage
)
2871 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
)
2873 pygame
.display
.flip()
2875 # overview zoom effect, time mapped through func
2876 def OverviewZoom(func
):
2877 global TransitionRunning
2878 pos
= OverviewPos(OverviewSelection
)
2879 X0
= PixelX
* (pos
[0] + OverviewBorder
)
2880 Y0
= PixelY
* (pos
[1] + OverviewBorder
)
2881 X1
= PixelX
* (pos
[0] - OverviewBorder
+ OverviewCellX
)
2882 Y1
= PixelY
* (pos
[1] - OverviewBorder
+ OverviewCellY
)
2884 TransitionRunning
= True
2885 t0
= pygame
.time
.get_ticks()
2886 while not(VideoPlaying
):
2887 t
= (pygame
.time
.get_ticks() - t0
) * 1.0 / ZoomDuration
2893 zoom
= (t
* (X1
- X0
) + t1
) / (X1
- X0
)
2894 OX
= zoom
* (t
* X0
- X0
) - (zoom
- 1.0) * t
* X0
2895 OY
= zoom
* (t
* Y0
- Y0
) - (zoom
- 1.0) * t
* Y0
2896 OX
= t
* X0
- zoom
* X0
2897 OY
= t
* Y0
- zoom
* Y0
2900 glEnable(TextureTarget
)
2901 glBindTexture(TextureTarget
, Tnext
)
2903 glColor3ub(192, 192, 192)
2904 glTexCoord2d( 0.0, 0.0); glVertex2d(OX
, OY
)
2905 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(OX
+ zoom
, OY
)
2906 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(OX
+ zoom
, OY
+ zoom
)
2907 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(OX
, OY
+ zoom
)
2908 glColor3ub(255, 255, 255)
2909 glTexCoord2d(X0
* TexMaxS
, Y0
* TexMaxT
); glVertex2d(OX
+ X0
*zoom
, OY
+ Y0
* zoom
)
2910 glTexCoord2d(X1
* TexMaxS
, Y0
* TexMaxT
); glVertex2d(OX
+ X1
*zoom
, OY
+ Y0
* zoom
)
2911 glTexCoord2d(X1
* TexMaxS
, Y1
* TexMaxT
); glVertex2d(OX
+ X1
*zoom
, OY
+ Y1
* zoom
)
2912 glTexCoord2d(X0
* TexMaxS
, Y1
* TexMaxT
); glVertex2d(OX
+ X0
*zoom
, OY
+ Y1
* zoom
)
2916 glBindTexture(TextureTarget
, Tcurrent
)
2917 glColor4d(1.0, 1.0, 1.0, 1.0 - t
* t
* t
)
2919 glTexCoord2d( 0.0, 0.0); glVertex2d(t
* X0
, t
* Y0
)
2920 glTexCoord2d(TexMaxS
, 0.0); glVertex2d(t
* X1
+ t1
, t
* Y0
)
2921 glTexCoord2d(TexMaxS
, TexMaxT
); glVertex2d(t
* X1
+ t1
, t
* Y1
+ t1
)
2922 glTexCoord2d( 0.0, TexMaxT
); glVertex2d(t
* X0
, t
* Y1
+ t1
)
2925 DrawOSDEx(OSDTitlePos
, CurrentOSDCaption
, alpha_factor
=t
)
2926 DrawOSDEx(OSDPagePos
, CurrentOSDPage
, alpha_factor
=t
)
2927 DrawOSDEx(OSDStatusPos
, CurrentOSDStatus
, alpha_factor
=t
)
2929 pygame
.display
.flip()
2930 TransitionRunning
= False
2932 # overview keyboard navigation
2933 def OverviewKeyboardNav(delta
):
2934 global OverviewSelection
2935 dest
= OverviewSelection
+ delta
2936 if (dest
>= OverviewPageCount
) or (dest
< 0):
2938 OverviewSelection
= dest
2939 x
, y
= OverviewPos(OverviewSelection
)
2940 pygame
.mouse
.set_pos((x
+ (OverviewCellX
/ 2), y
+ (OverviewCellY
/ 2)))
2942 # overview mode PageProp toggle
2943 def OverviewTogglePageProp(prop
, default
):
2944 if (OverviewSelection
< 0) or (OverviewSelection
>= len(OverviewPageMap
)):
2946 page
= OverviewPageMap
[OverviewSelection
]
2947 SetPageProp(page
, prop
, not(GetPageProp(page
, prop
, default
)))
2948 UpdateCaption(page
, force
=True)
2951 # overview event handler
2952 def HandleOverviewEvent(event
):
2953 global OverviewSelection
, TimeDisplay
2955 if event
.type == QUIT
:
2956 PageLeft(overview
=True)
2958 elif event
.type == VIDEOEXPOSE
:
2961 elif event
.type == KEYDOWN
:
2962 if (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
2963 pygame
.event
.post(pygame
.event
.Event(QUIT
))
2964 elif event
.unicode == u
'f':
2965 SetFullscreen(not Fullscreen
)
2966 elif event
.unicode == u
't':
2967 TimeDisplay
= not(TimeDisplay
)
2969 elif event
.unicode == u
'r':
2971 if TimeDisplay
: DrawOverview()
2972 elif event
.unicode == u
's':
2973 SaveInfoScript(InfoScriptPath
)
2974 elif event
.unicode == u
'o':
2975 OverviewTogglePageProp('overview', GetPageProp(Pcurrent
, '_overview', True))
2976 elif event
.unicode == u
'i':
2977 OverviewTogglePageProp('skip', False)
2978 elif event
.key
== K_UP
: OverviewKeyboardNav(-OverviewGridSize
)
2979 elif event
.key
== K_LEFT
: OverviewKeyboardNav(-1)
2980 elif event
.key
== K_RIGHT
: OverviewKeyboardNav(+1)
2981 elif event
.key
== K_DOWN
: OverviewKeyboardNav(+OverviewGridSize
)
2982 elif event
.key
== K_TAB
:
2983 OverviewSelection
= -1
2985 elif event
.key
in (K_RETURN
, K_KP_ENTER
):
2987 elif IsValidShortcutKey(event
.key
):
2988 if event
.mod
& KMOD_SHIFT
:
2990 AssignShortcut(OverviewPageMap
[OverviewSelection
], event
.key
)
2992 pass # no valid page selected
2995 page
= FindShortcut(event
.key
)
2997 OverviewSelection
= OverviewPageMapInv
[page
]
2998 x
, y
= OverviewPos(OverviewSelection
)
2999 pygame
.mouse
.set_pos((x
+ (OverviewCellX
/ 2), \
3000 y
+ (OverviewCellY
/ 2)))
3003 elif event
.type == MOUSEBUTTONUP
:
3004 if event
.button
== 1:
3006 elif event
.button
in (2, 3):
3007 OverviewSelection
= -1
3010 elif event
.type == MOUSEMOTION
:
3011 pygame
.event
.clear(MOUSEMOTION
)
3012 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3014 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3016 # determine highlighted page
3017 OverviewSelection
= \
3018 int((event
.pos
[0] - OverviewOfsX
) / OverviewCellX
) + \
3019 int((event
.pos
[1] - OverviewOfsY
) / OverviewCellY
) * OverviewGridSize
3020 if (OverviewSelection
< 0) or (OverviewSelection
>= len(OverviewPageMap
)):
3023 UpdateCaption(OverviewPageMap
[OverviewSelection
])
3026 elif event
.type == USEREVENT_HIDE_MOUSE
:
3027 # mouse timer event -> hide fullscreen cursor
3028 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3034 # overview mode entry/loop/exit function
3036 global Pcurrent
, Pnext
, Tcurrent
, Tnext
, Tracing
, OverviewSelection
3037 global PageEnterTime
, OverviewMode
3039 pygame
.time
.set_timer(USEREVENT_PAGE_TIMEOUT
, 0)
3041 UpdateOverviewTexture()
3043 if GetPageProp(Pcurrent
, 'boxes') or Tracing
:
3044 BoxFade(lambda t
: 1.0 - t
)
3046 OverviewSelection
= OverviewPageMapInv
[Pcurrent
]
3049 OverviewZoom(lambda t
: 1.0 - t
)
3051 PageEnterTime
= pygame
.time
.get_ticks() - StartTime
3053 event
= pygame
.event
.poll()
3054 if event
.type == NOEVENT
:
3055 force_update
= OverviewNeedUpdate
3056 if OverviewNeedUpdate
:
3057 UpdateOverviewTexture()
3058 if TimerTick() or force_update
:
3060 pygame
.time
.wait(20)
3061 elif not HandleOverviewEvent(event
):
3063 PageLeft(overview
=True)
3065 if (OverviewSelection
< 0) or (OverviewSelection
>= OverviewPageCount
):
3066 OverviewSelection
= OverviewPageMapInv
[Pcurrent
]
3069 Pnext
= OverviewPageMap
[OverviewSelection
]
3070 if Pnext
!= Pcurrent
:
3072 RenderPage(Pcurrent
, Tcurrent
)
3073 UpdateCaption(Pcurrent
)
3074 OverviewZoom(lambda t
: t
)
3075 OverviewMode
= False
3078 if GetPageProp(Pcurrent
, 'boxes'):
3079 BoxFade(lambda t
: t
)
3081 if not PreloadNextPage(GetNextPage(Pcurrent
, 1)):
3082 PreloadNextPage(GetNextPage(Pcurrent
, -1))
3085 ##### EVENT HANDLING ###########################################################
3087 # set fullscreen mode
3088 def SetFullscreen(fs
, do_init
=True):
3091 # let pygame do the real work
3093 if fs
== Fullscreen
: return
3094 if not pygame
.display
.toggle_fullscreen(): return
3097 # redraw the current page (pygame is too lazy to send an expose event ...)
3100 # show cursor and set auto-hide timer
3102 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3104 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3108 def TogglePageProp(prop
, default
):
3110 SetPageProp(Pcurrent
, prop
, not(GetPageProp(Pcurrent
, prop
, default
)))
3111 UpdateCaption(Pcurrent
, force
=True)
3115 # main event handling function
3116 def HandleEvent(event
):
3117 global HaveMark
, ZoomMode
, Marking
, Tracing
, Panning
, SpotRadius
, FileStats
3118 global MarkUL
, MarkLR
, MouseDownX
, MouseDownY
, PanAnchorX
, PanAnchorY
3119 global ZoomX0
, ZoomY0
, RTrunning
, RTrestart
, StartTime
, PageEnterTime
3120 global CurrentTime
, TimeDisplay
, TimeTracking
, ProgressBarPos
3122 if event
.type == QUIT
:
3125 elif event
.type == VIDEOEXPOSE
:
3128 elif event
.type == KEYDOWN
:
3132 elif (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
3133 pygame
.event
.post(pygame
.event
.Event(QUIT
))
3134 elif event
.unicode == u
'f':
3135 SetFullscreen(not Fullscreen
)
3136 elif (event
.key
== K_TAB
) and (event
.mod
& KMOD_ALT
) and Fullscreen
:
3137 SetFullscreen(False)
3138 elif event
.unicode == u
's':
3139 SaveInfoScript(InfoScriptPath
)
3140 elif event
.unicode == u
'z': # handle QWERTY and QWERTZ keyboards
3144 tx
, ty
= MouseToScreen(pygame
.mouse
.get_pos())
3145 EnterZoomMode(0.5 * tx
, 0.5 * ty
)
3146 elif event
.unicode == u
'b':
3148 elif event
.unicode == u
'w':
3150 elif event
.unicode == u
't':
3151 TimeDisplay
= not(TimeDisplay
)
3153 if TimeDisplay
and not(TimeTracking
) and FirstPage
:
3154 print >>sys
.stderr
, "Time tracking mode enabled."
3156 print "page duration enter leave"
3157 print "---- -------- -------- --------"
3158 elif event
.unicode == u
'r':
3160 if TimeDisplay
: DrawCurrentPage()
3161 elif event
.unicode == u
'l':
3162 TransitionTo(LastPage
)
3163 elif event
.unicode == u
'o':
3164 TogglePageProp('overview', GetPageProp(Pcurrent
, '_overview', True))
3165 elif event
.unicode == u
'i':
3166 TogglePageProp('skip', False)
3167 elif event
.key
== K_TAB
:
3170 elif event
.key
in (32, K_DOWN
, K_RIGHT
, K_PAGEDOWN
):
3172 TransitionTo(GetNextPage(Pcurrent
, 1, 1))
3173 elif event
.key
in (K_BACKSPACE
, K_UP
, K_LEFT
, K_PAGEUP
):
3175 TransitionTo(GetNextPage(Pcurrent
, -1, 1))
3176 elif event
.key
== K_HOME
:
3179 elif event
.key
== K_END
:
3180 if Pcurrent
!= PageCount
:
3181 TransitionTo(PageCount
)
3182 elif event
.key
in (K_RETURN
, K_KP_ENTER
):
3183 if not(GetPageProp(Pcurrent
, 'boxes')) and Tracing
:
3184 BoxFade(lambda t
: 1.0 - t
)
3185 Tracing
= not(Tracing
)
3186 if not(GetPageProp(Pcurrent
, 'boxes')) and Tracing
:
3187 BoxFade(lambda t
: t
)
3188 elif event
.unicode == u
'+':
3189 IncrementSpotSize(+8)
3190 elif event
.unicode == u
'-':
3191 IncrementSpotSize(-8)
3192 elif event
.unicode == u
'[':
3193 SetGamma(new_gamma
=Gamma
/ GammaStep
)
3194 elif event
.unicode == u
']':
3195 SetGamma(new_gamma
=Gamma
* GammaStep
)
3196 elif event
.unicode == u
'{':
3197 SetGamma(new_black
=BlackLevel
- BlackLevelStep
)
3198 elif event
.unicode == u
'}':
3199 SetGamma(new_black
=BlackLevel
+ BlackLevelStep
)
3200 elif event
.unicode == u
'\\':
3203 keyfunc
= GetPageProp(Pcurrent
, 'keys', {}).get(event
.unicode, None)
3206 elif IsValidShortcutKey(event
.key
):
3207 if event
.mod
& KMOD_SHIFT
:
3208 AssignShortcut(Pcurrent
, event
.key
)
3210 # load keyboard shortcut
3211 page
= FindShortcut(event
.key
)
3212 if page
and (page
!= Pcurrent
):
3215 elif event
.type == MOUSEBUTTONDOWN
:
3220 MouseDownX
, MouseDownY
= event
.pos
3221 if event
.button
== 1:
3222 MarkUL
= MarkLR
= MouseToScreen(event
.pos
)
3223 elif (event
.button
== 3) and ZoomMode
:
3226 elif event
.button
== 4:
3227 IncrementSpotSize(+8)
3228 elif event
.button
== 5:
3229 IncrementSpotSize(-8)
3231 elif event
.type == MOUSEBUTTONUP
:
3238 if event
.button
== 2:
3242 if event
.button
== 1:
3244 # left mouse button released in marking mode -> stop box marking
3246 # reject too small boxes
3247 if (abs(MarkUL
[0] - MarkLR
[0]) > 0.04) \
3248 and (abs(MarkUL
[1] - MarkLR
[1]) > 0.03):
3249 boxes
= GetPageProp(Pcurrent
, 'boxes', [])
3250 oldboxcount
= len(boxes
)
3251 boxes
.append(NormalizeRect(MarkUL
[0], MarkUL
[1], MarkLR
[0], MarkLR
[1]))
3252 SetPageProp(Pcurrent
, 'boxes', boxes
)
3253 if not(oldboxcount
) and not(Tracing
):
3254 BoxFade(lambda t
: t
)
3257 # left mouse button released, but no marking
3259 dest
= GetNextPage(Pcurrent
, 1)
3261 for valid
, target
, x0
, y0
, x1
, y1
in GetPageProp(Pcurrent
, '_href', []):
3262 if valid
and (x
>= x0
) and (x
< x1
) and (y
>= y0
) and (y
< y1
):
3265 if type(dest
) == types
.IntType
:
3269 if (event
.button
== 3) and not(Panning
):
3270 # right mouse button -> check if a box has to be killed
3271 boxes
= GetPageProp(Pcurrent
, 'boxes', [])
3272 x
, y
= MouseToScreen(event
.pos
)
3274 # if a box is already present around the clicked position, kill it
3275 idx
= FindBox(x
, y
, boxes
)
3276 if (len(boxes
) == 1) and not(Tracing
):
3277 BoxFade(lambda t
: 1.0 - t
)
3279 SetPageProp(Pcurrent
, 'boxes', boxes
)
3282 # no box present -> go to previous page
3284 TransitionTo(GetNextPage(Pcurrent
, -1))
3287 elif event
.type == MOUSEMOTION
:
3288 pygame
.event
.clear(MOUSEMOTION
)
3289 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3291 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, MouseHideDelay
)
3293 # don't react on mouse input during video playback
3294 if VideoPlaying
: return
3295 # activate marking if mouse is moved away far enough
3296 if event
.buttons
[0] and not(Marking
):
3298 if (abs(x
- MouseDownX
) > 4) and (abs(y
- MouseDownY
) > 4):
3300 # mouse move while marking -> update marking box
3302 MarkLR
= MouseToScreen(event
.pos
)
3303 # mouse move while RMB is pressed -> panning
3304 if event
.buttons
[2] and ZoomMode
:
3306 if not(Panning
) and (abs(x
- MouseDownX
) > 4) and (abs(y
- MouseDownY
) > 4):
3308 ZoomX0
= PanAnchorX
+ (MouseDownX
- x
) * ZoomArea
/ ScreenWidth
3309 ZoomY0
= PanAnchorY
+ (MouseDownY
- y
) * ZoomArea
/ ScreenHeight
3310 ZoomX0
= min(max(ZoomX0
, 0.0), 1.0 - ZoomArea
)
3311 ZoomY0
= min(max(ZoomY0
, 0.0), 1.0 - ZoomArea
)
3312 # if anything changed, redraw the page
3313 if Marking
or Tracing
or event
.buttons
[2] or (CursorImage
and CursorVisible
):
3316 elif event
.type == USEREVENT_HIDE_MOUSE
:
3317 # mouse timer event -> hide fullscreen cursor
3318 pygame
.time
.set_timer(USEREVENT_HIDE_MOUSE
, 0)
3322 elif event
.type == USEREVENT_PAGE_TIMEOUT
:
3323 TransitionTo(GetNextPage(Pcurrent
, 1))
3325 elif event
.type == USEREVENT_POLL_FILE
:
3328 if my_stat(f
) != GetFileProp(f
, 'stat'):
3332 # first, check if the new file is valid
3333 if not os
.path
.isfile(GetPageProp(Pcurrent
, '_file')):
3335 # invalidate everything we used to know about the input files
3337 for props
in PageProps
.itervalues():
3338 for prop
in ('_overview_rendered', '_box', '_href'):
3339 if prop
in props
: del props
[prop
]
3341 # force a transition to the current page, reloading it
3343 TransitionTo(Pcurrent
)
3344 # restart the background renderer thread. this is not completely safe,
3345 # i.e. there's a small chance that we fail to restart the thread, but
3346 # this isn't critical
3347 if CacheMode
and BackgroundRendering
:
3352 thread
.start_new_thread(RenderThread
, (Pcurrent
, Pnext
))
3354 elif event
.type == USEREVENT_TIMER_UPDATE
:
3359 ##### FILE LIST GENERATION #####################################################
3361 def IsImageFileName(name
):
3362 return os
.path
.splitext(name
)[1].lower() in \
3363 (".jpg", ".jpeg", ".png", ".tif", ".tiff", ".bmp", ".ppm", ".pgm")
3364 def IsPlayable(name
):
3365 return IsImageFileName(name
) or name
.lower().endswith(".pdf") or os
.path
.isdir(name
)
3367 def AddFile(name
, title
=None):
3368 global FileList
, FileName
3370 if os
.path
.isfile(name
):
3371 FileList
.append(name
)
3372 if title
: SetFileProp(name
, 'title', title
)
3374 elif os
.path
.isdir(name
):
3375 images
= [os
.path
.join(name
, f
) for f
in os
.listdir(name
) if IsImageFileName(f
)]
3376 images
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
3378 print >>sys
.stderr
, "Warning: no image files in directory `%s'" % name
3379 for img
in images
: AddFile(img
)
3381 elif name
.startswith('@') and os
.path
.isfile(name
[1:]):
3383 dirname
= os
.path
.dirname(name
)
3388 line
= [part
.strip() for part
in line
.split('#', 1)]
3393 subfile
, title
= line
3395 AddFile(os
.path
.normpath(os
.path
.join(dirname
, subfile
)), title
)
3398 print >>sys
.stderr
, "Error: cannot read list file `%s'" % name
3405 files
= list(filter(IsPlayable
, glob
.glob(name
)))
3407 for f
in files
: AddFile(f
)
3409 print >>sys
.stderr
, "Error: input file `%s' not found" % name
3412 ##### INITIALIZATION ###########################################################
3415 global ScreenWidth
, ScreenHeight
, TexWidth
, TexHeight
, TexSize
, LogoImage
3416 global TexMaxS
, TexMaxT
, MeshStepX
, MeshStepY
, EdgeX
, EdgeY
, PixelX
, PixelY
3417 global OverviewGridSize
, OverviewCellX
, OverviewCellY
3418 global OverviewOfsX
, OverviewOfsY
, OverviewImage
, OverviewPageCount
3419 global OverviewPageMap
, OverviewPageMapInv
, FileName
, FileList
, PageCount
3420 global DocumentTitle
, PageProps
, LogoTexture
, OSDFont
3421 global Pcurrent
, Pnext
, Tcurrent
, Tnext
, InitialPage
3422 global CacheFile
, CacheFileName
3423 global Extensions
, AllowExtensions
, TextureTarget
, PAR
, DAR
, TempFileName
3424 global BackgroundRendering
, FileStats
, RTrunning
, RTrestart
, StartTime
3425 global CursorImage
, CursorVisible
, InfoScriptPath
, PowerdotFix
3427 # allocate temporary file
3428 TempFileName
= tempfile
.mktemp(prefix
="Accentuate-", suffix
="_tmp")
3430 # some input guesswork
3431 DocumentTitle
= os
.path
.splitext(os
.path
.split(FileName
)[1])[0]
3432 if FileName
and not(FileList
):
3434 if not(FileName
) and (len(FileList
) == 1):
3435 FileName
= FileList
[0]
3437 # fill the page list
3439 for name
in FileList
:
3440 ispdf
= name
.lower().endswith(".pdf")
3442 # PDF input -> try to pre-parse the PDF file
3444 # phase 1: internal PDF parser
3446 pages
, pdf_width
, pdf_height
= analyze_pdf(name
)
3448 pdf_width
, pdf_height
= (pdf_height
, pdf_width
)
3449 res
= min(ScreenWidth
* 72.0 / pdf_width
, \
3450 ScreenHeight
* 72.0 / pdf_height
)
3454 # phase 2: use pdftk
3456 assert 0 == spawn(os
.P_WAIT
, pdftkPath
, \
3457 ["pdftk", FileNameEscape
+ name
+ FileNameEscape
, \
3458 "dump_data", "output", TempFileName
+ ".txt"])
3459 title
, pages
= pdftkParse(TempFileName
+ ".txt", PageCount
)
3460 if DocumentTitle
and title
: DocumentTitle
= title
3466 SetPageProp(PageCount
+ 1, '_title', os
.path
.split(name
)[-1])
3470 print >>sys
.stderr
, "Warning: The input file `%s' could not be analyzed." % name
3473 # add pages and files into PageProps and FileProps
3474 pagerange
= list(range(PageCount
+ 1, PageCount
+ pages
+ 1))
3475 for page
in pagerange
:
3476 SetPageProp(page
, '_file', name
)
3477 if ispdf
: SetPageProp(page
, '_page', page
- PageCount
)
3478 title
= GetFileProp(name
, 'title')
3479 if title
: SetPageProp(page
, '_title', title
)
3480 SetFileProp(name
, 'pages', GetFileProp(name
, 'pages', []) + pagerange
)
3481 SetFileProp(name
, 'offsets', GetFileProp(name
, 'offsets', []) + [PageCount
])
3482 if not GetFileProp(name
, 'stat'): SetFileProp(name
, 'stat', my_stat(name
))
3483 if ispdf
: SetFileProp(name
, 'res', res
)
3486 # no pages? strange ...
3488 print >>sys
.stderr
, "The presentation doesn't have any pages, quitting."
3491 # if rendering is wanted, do it NOW
3492 if RenderToDirectory
:
3493 sys
.exit(DoRender())
3495 # load and execute info script
3496 if not InfoScriptPath
:
3497 InfoScriptPath
= FileName
+ ".info"
3500 # initialize graphics
3502 if Fullscreen
and UseAutoScreenSize
:
3503 size
= GetScreenSize()
3505 ScreenWidth
, ScreenHeight
= size
3506 print >>sys
.stderr
, "Detected screen size: %dx%d pixels" % (ScreenWidth
, ScreenHeight
)
3507 flags
= OPENGL|DOUBLEBUF
3511 pygame
.display
.set_mode((ScreenWidth
, ScreenHeight
), flags
)
3513 print >>sys
.stderr
, "FATAL: cannot create rendering surface in the desired resolution (%dx%d)" % (ScreenWidth
, ScreenHeight
)
3515 pygame
.display
.set_caption(__title__
)
3516 pygame
.key
.set_repeat(500, 30)
3518 pygame
.mouse
.set_visible(False)
3519 CursorVisible
= False
3520 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
3521 if (Gamma
<> 1.0) or (BlackLevel
<> 0):
3522 SetGamma(force
=True)
3524 # check if graphics are unaccelerated
3525 renderer
= glGetString(GL_RENDERER
)
3526 print >>sys
.stderr
, "OpenGL renderer:", renderer
3527 if renderer
.lower() in ("mesa glx indirect", "gdi generic"):
3528 print >>sys
.stderr
, "WARNING: Using an OpenGL software renderer. Accentuate will work, but it will"
3529 print >>sys
.stderr
, " very likely be too slow to be usable."
3531 # setup the OpenGL texture mode
3532 Extensions
= dict([(ext
.split('_', 2)[-1], None) for ext
in \
3533 glGetString(GL_EXTENSIONS
).split()])
3534 if AllowExtensions
and ("texture_non_power_of_two" in Extensions
):
3535 print >>sys
.stderr
, "Using GL_ARB_texture_non_power_of_two."
3536 TextureTarget
= GL_TEXTURE_2D
3537 TexWidth
= ScreenWidth
3538 TexHeight
= ScreenHeight
3541 elif AllowExtensions
and ("texture_rectangle" in Extensions
):
3542 print >>sys
.stderr
, "Using GL_ARB_texture_rectangle."
3543 TextureTarget
= 0x84F5 # GL_TEXTURE_RECTANGLE_ARB
3544 TexWidth
= ScreenWidth
3545 TexHeight
= ScreenHeight
3546 TexMaxS
= ScreenWidth
3547 TexMaxT
= ScreenHeight
3549 print >>sys
.stderr
, "Using conventional power-of-two textures with padding."
3550 TextureTarget
= GL_TEXTURE_2D
3551 TexWidth
= npot(ScreenWidth
)
3552 TexHeight
= npot(ScreenHeight
)
3553 TexMaxS
= ScreenWidth
* 1.0 / TexWidth
3554 TexMaxT
= ScreenHeight
* 1.0 / TexHeight
3555 TexSize
= TexWidth
* TexHeight
* 3
3557 # set up some variables
3559 PAR
= DAR
/ float(ScreenWidth
) * float(ScreenHeight
)
3560 MeshStepX
= 1.0 / MeshResX
3561 MeshStepY
= 1.0 / MeshResY
3562 PixelX
= 1.0 / ScreenWidth
3563 PixelY
= 1.0 / ScreenHeight
3564 EdgeX
= BoxEdgeSize
* 1.0 / ScreenWidth
3565 EdgeY
= BoxEdgeSize
* 1.0 / ScreenHeight
3566 if InitialPage
is None:
3567 InitialPage
= GetNextPage(0, 1)
3568 Pcurrent
= InitialPage
3570 # prepare logo image
3571 LogoImage
= Image
.open(StringIO
.StringIO(LOGO
))
3572 LogoTexture
= glGenTextures(1)
3573 glBindTexture(GL_TEXTURE_2D
, LogoTexture
)
3574 glTexImage2D(GL_TEXTURE_2D
, 0, 1, 256, 64, 0, GL_LUMINANCE
, GL_UNSIGNED_BYTE
, LogoImage
.tostring())
3575 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
)
3577 pygame
.display
.flip()
3579 # initialize OSD font
3581 OSDFont
= GLFont(FontTextureWidth
, FontTextureHeight
, FontList
, FontSize
, search_path
=FontPath
)
3584 for key
in ('title', '_title'):
3585 titles
.extend([p
[key
] for p
in PageProps
.itervalues() if key
in p
])
3587 OSDFont
.AddString("".join(titles
))
3589 print >>sys
.stderr
, "The OSD font size is too large, the OSD will be rendered incompletely."
3591 print >>sys
.stderr
, "Could not open OSD font file, disabling OSD."
3592 except (NameError, AttributeError, TypeError):
3593 print >>sys
.stderr
, "Your version of PIL is too old or incomplete, disabling OSD."
3595 # initialize mouse cursor
3598 CursorImage
= PrepareCustomCursor(Image
.open(CursorImage
))
3600 print >>sys
.stderr
, "Could not open the mouse cursor image, using standard cursor."
3604 if CacheMode
== PersistentCache
:
3605 if not CacheFileName
:
3606 CacheFileName
= FileName
+ ".cache"
3608 if CacheMode
== FileCache
:
3609 CacheFile
= tempfile
.TemporaryFile(prefix
="Accentuate-", suffix
=".cache")
3611 # initialize overview metadata
3612 OverviewPageMap
=[i
for i
in xrange(1, PageCount
+ 1) \
3613 if GetPageProp(i
, ('overview', '_overview'), True) \
3614 and (i
>= PageRangeStart
) and (i
<= PageRangeEnd
)]
3615 OverviewPageCount
= max(len(OverviewPageMap
), 1)
3616 OverviewPageMapInv
= {}
3617 for page
in xrange(1, PageCount
+ 1):
3618 OverviewPageMapInv
[page
] = len(OverviewPageMap
) - 1
3619 for i
in xrange(len(OverviewPageMap
)):
3620 if OverviewPageMap
[i
] >= page
:
3621 OverviewPageMapInv
[page
] = i
3624 # initialize overview page geometry
3625 OverviewGridSize
= 1
3626 while OverviewPageCount
> OverviewGridSize
* OverviewGridSize
:
3627 OverviewGridSize
+= 1
3628 OverviewCellX
= int(ScreenWidth
/ OverviewGridSize
)
3629 OverviewCellY
= int(ScreenHeight
/ OverviewGridSize
)
3630 OverviewOfsX
= int((ScreenWidth
- OverviewCellX
* OverviewGridSize
)/2)
3631 OverviewOfsY
= int((ScreenHeight
- OverviewCellY
* \
3632 int((OverviewPageCount
+ OverviewGridSize
- 1) / OverviewGridSize
)) / 2)
3633 OverviewImage
= Image
.new('RGB', (TexWidth
, TexHeight
))
3635 # fill overlay "dummy" images
3636 dummy
= LogoImage
.copy()
3637 maxsize
= (OverviewCellX
- 2 * OverviewBorder
, OverviewCellY
- 2 * OverviewBorder
)
3638 if (dummy
.size
[0] > maxsize
[0]) or (dummy
.size
[1] > maxsize
[1]):
3639 dummy
.thumbnail(ZoomToFit(dummy
.size
, maxsize
), Image
.ANTIALIAS
)
3640 margX
= int((OverviewCellX
- dummy
.size
[0]) / 2)
3641 margY
= int((OverviewCellY
- dummy
.size
[1]) / 2)
3642 dummy
= dummy
.convert(mode
='RGB')
3643 for page
in range(OverviewPageCount
):
3644 pos
= OverviewPos(page
)
3645 OverviewImage
.paste(dummy
, (pos
[0] + margX
, pos
[1] + margY
))
3648 # set up background rendering
3649 if not EnableBackgroundRendering
:
3650 print >>sys
.stderr
, "Background rendering isn't available on this platform."
3651 BackgroundRendering
= False
3653 # if caching is enabled, pre-render all pages
3654 if CacheMode
and not(BackgroundRendering
):
3657 pygame
.display
.flip()
3658 for pdf
in FileProps
:
3659 if pdf
.lower().endswith(".pdf"):
3663 for page
in range(InitialPage
, PageCount
+ 1) + range(1, InitialPage
):
3664 event
= pygame
.event
.poll()
3665 while event
.type != NOEVENT
:
3666 if event
.type == KEYDOWN
:
3667 if (event
.key
== K_ESCAPE
) or (event
.unicode == u
'q'):
3670 elif event
.type == MOUSEBUTTONUP
:
3672 event
= pygame
.event
.poll()
3674 if (page
>= PageRangeStart
) and (page
<= PageRangeEnd
):
3677 progress
+= 1.0 / PageCount
;
3678 DrawProgress(progress
)
3679 pygame
.display
.flip()
3681 # create buffer textures
3683 pygame
.display
.flip()
3684 glEnable(TextureTarget
)
3685 Tcurrent
= glGenTextures(1)
3686 Tnext
= glGenTextures(1)
3687 for T
in (Tcurrent
, Tnext
):
3688 glBindTexture(TextureTarget
, T
)
3689 glTexParameteri(TextureTarget
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
)
3690 glTexParameteri(TextureTarget
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
)
3691 glTexParameteri(TextureTarget
, GL_TEXTURE_WRAP_S
, GL_CLAMP
)
3692 glTexParameteri(TextureTarget
, GL_TEXTURE_WRAP_T
, GL_CLAMP
)
3694 # prebuffer current and next page
3696 RenderPage(Pcurrent
, Tcurrent
)
3697 PageEntered(update_time
=False)
3698 PreloadNextPage(GetNextPage(Pcurrent
, 1))
3700 # some other preparations
3701 PrepareTransitions()
3704 pygame
.time
.set_timer(USEREVENT_POLL_FILE
, PollInterval
* 1000)
3706 # start the background rendering thread
3707 if CacheMode
and BackgroundRendering
:
3709 thread
.start_new_thread(RenderThread
, (Pcurrent
, Pnext
))
3711 # start output and enter main loop
3712 StartTime
= pygame
.time
.get_ticks()
3713 pygame
.time
.set_timer(USEREVENT_TIMER_UPDATE
, 100)
3714 if not(Fullscreen
) and CursorImage
:
3715 pygame
.mouse
.set_visible(False)
3717 UpdateCaption(Pcurrent
)
3719 # Kick off LIRC thread
3720 if(UseLIRC
is True):
3725 HandleEvent(pygame
.event
.wait())
3728 # wrapper around main() that ensures proper uninitialization
3735 # ensure that background rendering is halted
3738 # remove all temp files
3739 if 'CacheFile' in globals():
3741 for tmp
in glob
.glob(TempFileName
+ "*"):
3747 if(UseLIRC
is True):
3751 ##### COMMAND-LINE PARSER AND HELP #############################################
3753 def if_op(cond
, res_then
, res_else
):
3754 if cond
: return res_then
3755 else: return res_else
3757 def HelpExit(code
=0):
3758 print """A nice presentation tool.
3760 Usage: """+os
.path
.basename(sys
.argv
[0])+""" [OPTION...] <INPUT(S)...>
3762 You may either play a PDF file, a directory containing image files or
3763 individual image files.
3766 -r, --rotate <n> rotate pages clockwise in 90-degree steps
3767 --scale scale images to fit screen (not used in PDF mode)
3768 --supersample use supersampling (only used in PDF mode)
3769 -s --supersample for PDF files, --scale for image files
3770 -I, --script <path> set the path of the info script
3771 -u, --poll <seconds> check periodically if the source file has been
3772 updated and reload it if it did
3773 -o, --output <dir> don't display the presentation, only render to .png
3774 -h, --help show this help text and exit
3777 -f, --fullscreen """+if_op(Fullscreen
,"do NOT ","")+"""start in fullscreen mode
3778 -g, --geometry <WxH> set window size or fullscreen resolution
3779 -A, --aspect <X:Y> adjust for a specific display aspect ratio (e.g. 5:4)
3780 -G, --gamma <G[:BL]> specify startup gamma and black level
3783 -i, --initialpage <n> start with page <n>
3784 -p, --pages <A-B> only cache pages in the specified range;
3785 implicitly sets -i <A>
3786 -w, --wrap go back to the first page after the last page
3787 -J, --fade fade to black after the last page
3788 -H, --hyperlink attempt to compensate for landscaped hyperlinks (hack)
3789 -a, --auto <seconds> automatically advance to next page after some seconds
3790 -O, --autooverview <x> automatically derive page visibility on overview page
3791 -O first = show pages with captions
3792 -O last = show pages before pages with captions
3795 -t, --transition <trans[,trans2...]>
3796 force a specific transitions or set of transitions
3797 -l, --listtrans print a list of available transitions and exit
3798 -F, --font <file> use a specific TrueType font file for the OSD
3799 -S, --fontsize <px> specify the OSD font size in pixels
3800 -C, --cursor <F[:X,Y]> use a .png image as the mouse cursor
3801 -L, --layout <spec> set the OSD layout (please read the documentation)
3804 -M, --minutes display time in minutes, not seconds
3805 -d, --duration <time> set the desired duration of the presentation and show
3806 a progress bar at the bottom of the screen
3807 -T, --transtime <ms> set transition duration in milliseconds
3808 -D, --mousedelay <ms> set mouse hide delay for fullscreen mode (in ms)
3809 -B, --boxfade <ms> set highlight box fade duration in milliseconds
3810 -Z, --zoom <ms> set zoom duration in milliseconds
3813 -c, --cache <mode> set page cache mode:
3814 -c none = disable caching completely
3815 -c memory = store cache in RAM
3816 -c disk = store cache on disk temporarily
3817 -c persistent = store cache on disk persistently
3818 --cachefile <path> set the persistent cache file path (implies -cp)
3819 -b, --noback don't pre-render images in the background
3820 -P, --gspath <path> set path to GhostScript or pdftoppm executable
3821 -R, --meshres <XxY> set mesh resolution for effects (default: 48x36)
3822 -e, --noext don't use OpenGL texture size extensions
3824 For detailed information, visit""", __website__
3827 def ListTransitions():
3828 print "Available transitions:"
3829 standard
= dict([(tc
.__name
__, None) for tc
in AvailableTransitions
])
3830 trans
= [(tc
.__name
__, tc
.__doc
__) for tc
in AllTransitions
]
3831 trans
.append(('None', "no transition"))
3833 maxlen
= max([len(item
[0]) for item
in trans
])
3834 for name
, desc
in trans
:
3835 if name
in standard
:
3839 print star
, name
.ljust(maxlen
), '-', desc
3840 print "(transitions with * are enabled by default)"
3843 def TryTime(s
, regexp
, func
):
3844 m
= re
.match(regexp
, s
, re
.I
)
3846 return func(map(int, m
.groups()))
3848 return TryTime(s
, r
'([0-9]+)s?$', lambda m
: m
[0]) \
3849 or TryTime(s
, r
'([0-9]+)m$', lambda m
: m
[0] * 60) \
3850 or TryTime(s
, r
'([0-9]+)[m:]([0-9]+)[ms]?$', lambda m
: m
[0] * 60 + m
[1]) \
3851 or TryTime(s
, r
'([0-9]+)[h:]([0-9]+)[hm]?$', lambda m
: m
[0] * 3600 + m
[1] * 60) \
3852 or TryTime(s
, r
'([0-9]+)[h:]([0-9]+)[m:]([0-9]+)s?$', lambda m
: m
[0] * 3600 + m
[1] * 60 + m
[2])
3855 print >>sys
.stderr
, "command line parse error:", msg
3856 print >>sys
.stderr
, "use `%s -h' to get help" % sys
.argv
[0]
3857 print >>sys
.stderr
, "or visit", __website__
, "for full documentation"
3860 def SetTransitions(list):
3861 global AvailableTransitions
3862 index
= dict([(tc
.__name
__.lower(), tc
) for tc
in AllTransitions
])
3863 index
['none'] = None
3864 AvailableTransitions
=[]
3865 for trans
in list.split(','):
3867 AvailableTransitions
.append(index
[trans
.lower()])
3869 opterr("unknown transition `%s'" % trans
)
3871 def ParseLayoutPosition(value
):
3874 for c
in value
.strip().lower():
3875 if c
== 't': ypos
.append(0)
3876 elif c
== 'b': ypos
.append(1)
3877 elif c
== 'l': xpos
.append(0)
3878 elif c
== 'r': xpos
.append(1)
3879 elif c
== 'c': xpos
.append(2)
3880 else: opterr("invalid position specification `%s'" % value
)
3881 if not xpos
: opterr("position `%s' lacks X component" % value
)
3882 if not ypos
: opterr("position `%s' lacks Y component" % value
)
3883 if len(xpos
)>1: opterr("position `%s' has multiple X components" % value
)
3884 if len(ypos
)>1: opterr("position `%s' has multiple Y components" % value
)
3885 return (xpos
[0] << 1) | ypos
[0]
3886 def SetLayoutSubSpec(key
, value
):
3887 global OSDTimePos
, OSDTitlePos
, OSDPagePos
, OSDStatusPos
3888 global OSDAlpha
, OSDMargin
3889 lkey
= key
.strip().lower()
3890 if lkey
in ('a', 'alpha', 'opacity'):
3892 OSDAlpha
= float(value
)
3894 opterr("invalid alpha value `%s'" % value
)
3896 OSDAlpha
*= 0.01 # accept percentages, too
3897 if (OSDAlpha
< 0.0) or (OSDAlpha
> 1.0):
3898 opterr("alpha value %s out of range" % value
)
3899 elif lkey
in ('margin', 'dist', 'distance'):
3901 OSDMargin
= float(value
)
3903 opterr("invalid margin value `%s'" % value
)
3905 opterr("margin value %s out of range" % value
)
3906 elif lkey
in ('t', 'time'):
3907 OSDTimePos
= ParseLayoutPosition(value
)
3908 elif lkey
in ('title', 'caption'):
3909 OSDTitlePos
= ParseLayoutPosition(value
)
3910 elif lkey
in ('page', 'number'):
3911 OSDPagePos
= ParseLayoutPosition(value
)
3912 elif lkey
in ('status', 'info'):
3913 OSDStatusPos
= ParseLayoutPosition(value
)
3915 opterr("unknown layout element `%s'" % key
)
3916 def SetLayout(spec
):
3917 for sub
in spec
.replace(':', '=').split(','):
3919 key
, value
= sub
.split('=')
3921 opterr("invalid layout spec `%s'" % sub
)
3922 SetLayoutSubSpec(key
, value
)
3924 def ParseCacheMode(arg
):
3925 arg
= arg
.strip().lower()
3926 if "none".startswith(arg
): return NoCache
3927 if "off".startswith(arg
): return NoCache
3928 if "memory".startswith(arg
): return MemCache
3929 if "disk".startswith(arg
): return FileCache
3930 if "file".startswith(arg
): return FileCache
3931 if "persistent".startswith(arg
): return PersistentCache
3932 opterr("invalid cache mode `%s'" % arg
)
3934 def ParseAutoOverview(arg
):
3935 arg
= arg
.strip().lower()
3936 if "off".startswith(arg
): return Off
3937 if "first".startswith(arg
): return First
3938 if "last".startswith(arg
): return Last
3941 assert (i
>= Off
) and (i
<= Last
)
3943 opterr("invalid auto-overview mode `%s'" % arg
)
3945 def ParseOptions(argv
):
3946 global FileName
, FileList
, Fullscreen
, Scaling
, Supersample
, CacheMode
3947 global TransitionDuration
, MouseHideDelay
, BoxFadeDuration
, ZoomDuration
3948 global ScreenWidth
, ScreenHeight
, MeshResX
, MeshResY
, InitialPage
, Wrap
3949 global AutoAdvance
, RenderToDirectory
, Rotation
, AllowExtensions
, DAR
3950 global BackgroundRendering
, UseAutoScreenSize
, PollInterval
, CacheFileName
3951 global PageRangeStart
, PageRangeEnd
, FontList
, FontSize
, Gamma
, BlackLevel
3952 global EstimatedDuration
, CursorImage
, CursorHotspot
, MinutesOnly
3953 global GhostScriptPath
, pdftoppmPath
, UseGhostScript
, InfoScriptPath
3954 global AutoOverview
, FadeToBlackAtEnd
, PowerdotFix
3956 try: # unused short options: jknqvxyzEKNQUVWXY
3957 opts
, args
= getopt
.getopt(argv
, \
3958 "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", \
3959 ["help", "fullscreen", "geometry=", "scale", "supersample", \
3960 "nocache", "initialpage=", "wrap", "auto", "listtrans", "output=", \
3961 "rotate=", "transition=", "transtime=", "mousedelay=", "boxfade=", \
3962 "zoom=", "gspath=", "meshres=", "noext", "aspect", "memcache", \
3963 "noback", "pages=", "poll=", "font=", "fontsize=", "gamma=",
3964 "duration=", "cursor=", "minutes", "layout=", "script=", "cache=",
3965 "cachefile=", "autooverview=", "fade", "hyperlink"])
3966 except getopt
.GetoptError
, message
:
3969 for opt
, arg
in opts
:
3970 if opt
in ("-h", "--help"):
3972 if opt
in ("-l", "--listtrans"):
3974 if opt
in ("-f", "--fullscreen"):
3975 Fullscreen
= not(Fullscreen
)
3976 if opt
in ("-e", "--noext"):
3977 AllowExtensions
= not(AllowExtensions
)
3978 if opt
in ("-s", "--scale"):
3979 Scaling
= not(Scaling
)
3980 if opt
in ("-s", "--supersample"):
3982 if opt
in ("-w", "--wrap"):
3984 if opt
in ("-J", "--fade"):
3985 FadeToBlackAtEnd
= not(FadeToBlackAtEnd
)
3986 if opt
in ("-H", "--hyperlink"):
3987 PowerdotFix
= not(PowerdotFix
)
3988 if opt
in ("-O", "--autooverview"):
3989 AutoOverview
= ParseAutoOverview(arg
)
3990 if opt
in ("-c", "--cache"):
3991 CacheMode
= ParseCacheMode(arg
)
3992 if opt
== "--nocache":
3993 print >>sys
.stderr
, "Note: The `--nocache' option is deprecated, use `--cache none' instead."
3995 if opt
in ("-m", "--memcache"):
3996 print >>sys
.stderr
, "Note: The `--memcache' option is deprecated, use `--cache memory' instead."
3997 CacheMode
= MemCache
3998 if opt
== "--cachefile":
4000 CacheMode
= PersistentCache
4001 if opt
in ("-M", "--minutes"):
4002 MinutesOnly
= not(MinutesOnly
)
4003 if opt
in ("-b", "--noback"):
4004 BackgroundRendering
= not(BackgroundRendering
)
4005 if opt
in ("-t", "--transition"):
4007 if opt
in ("-L", "--layout"):
4009 if opt
in ("-o", "--output"):
4010 RenderToDirectory
= arg
4011 if opt
in ("-I", "--script"):
4012 InfoScriptPath
= arg
4013 if opt
in ("-F", "--font"):
4015 if opt
in ("-P", "--gspath"):
4016 UseGhostScript
= (arg
.replace("\\", "/").split("/")[-1].lower().find("pdftoppm") < 0)
4018 GhostScriptPath
= arg
4021 if opt
in ("-S", "--fontsize"):
4026 opterr("invalid parameter for --fontsize")
4027 if opt
in ("-i", "--initialpage"):
4029 InitialPage
= int(arg
)
4030 assert InitialPage
> 0
4032 opterr("invalid parameter for --initialpage")
4033 if opt
in ("-d", "--duration"):
4035 EstimatedDuration
= ParseTime(arg
)
4036 assert EstimatedDuration
> 0
4038 opterr("invalid parameter for --duration")
4039 if opt
in ("-a", "--auto"):
4041 AutoAdvance
= int(arg
) * 1000
4042 assert (AutoAdvance
> 0) and (AutoAdvance
<= 86400000)
4044 opterr("invalid parameter for --auto")
4045 if opt
in ("-T", "--transtime"):
4047 TransitionDuration
= int(arg
)
4048 assert (TransitionDuration
>= 0) and (TransitionDuration
< 32768)
4050 opterr("invalid parameter for --transtime")
4051 if opt
in ("-D", "--mousedelay"):
4053 MouseHideDelay
= int(arg
)
4054 assert (MouseHideDelay
>= 0) and (MouseHideDelay
< 32768)
4056 opterr("invalid parameter for --mousedelay")
4057 if opt
in ("-B", "--boxfade"):
4059 BoxFadeDuration
= int(arg
)
4060 assert (BoxFadeDuration
>= 0) and (BoxFadeDuration
< 32768)
4062 opterr("invalid parameter for --boxfade")
4063 if opt
in ("-Z", "--zoom"):
4065 ZoomDuration
= int(arg
)
4066 assert (ZoomDuration
>= 0) and (ZoomDuration
< 32768)
4068 opterr("invalid parameter for --zoom")
4069 if opt
in ("-r", "--rotate"):
4073 opterr("invalid parameter for --rotate")
4074 while Rotation
< 0: Rotation
+= 4
4075 Rotation
= Rotation
& 3
4076 if opt
in ("-u", "--poll"):
4078 PollInterval
= int(arg
)
4079 assert PollInterval
>= 0
4081 opterr("invalid parameter for --poll")
4082 if opt
in ("-g", "--geometry"):
4084 ScreenWidth
, ScreenHeight
= map(int, arg
.split("x"))
4085 assert (ScreenWidth
>= 320) and (ScreenWidth
< 4096)
4086 assert (ScreenHeight
>= 200) and (ScreenHeight
< 4096)
4087 UseAutoScreenSize
= False
4089 opterr("invalid parameter for --geometry")
4090 if opt
in ("-R", "--meshres"):
4092 MeshResX
, MeshResY
= map(int, arg
.split("x"))
4093 assert (MeshResX
> 0) and (MeshResX
<= ScreenWidth
)
4094 assert (MeshResY
> 0) and (MeshResY
<= ScreenHeight
)
4096 opterr("invalid parameter for --meshres")
4097 if opt
in ("-p", "--pages"):
4099 PageRangeStart
, PageRangeEnd
= map(int, arg
.split("-"))
4100 assert PageRangeStart
> 0
4101 assert PageRangeStart
<= PageRangeEnd
4103 opterr("invalid parameter for --pages")
4104 InitialPage
=PageRangeStart
4105 if opt
in ("-A", "--aspect"):
4108 fx
, fy
= map(float, arg
.split(':'))
4114 opterr("invalid parameter for --aspect")
4115 if opt
in ("-G", "--gamma"):
4118 arg
, bl
= arg
.split(':', 1)
4119 BlackLevel
= int(bl
)
4122 assert (BlackLevel
>= 0) and (BlackLevel
< 255)
4124 opterr("invalid parameter for --gamma")
4125 if opt
in ("-C", "--cursor"):
4128 arg
= arg
.split(':')
4130 CursorImage
= ':'.join(arg
[:-1])
4131 CursorHotspot
= map(int, arg
[-1].split(','))
4134 assert (BlackLevel
>= 0) and (BlackLevel
< 255)
4136 opterr("invalid parameter for --cursor")
4141 opterr("no playable files specified")
4144 # glob and filter argument list
4147 files
.extend(glob
.glob(arg
))
4148 files
= list(filter(IsPlayable
, files
))
4150 # if only one argument is specified, use it as the informal file name
4156 # construct final FileList by expanding directories to image file lists
4159 if os
.path
.isdir(item
):
4160 images
= [os
.path
.join(item
, f
) for f
in os
.listdir(item
) if IsImageFileName(f
)]
4161 images
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
4162 FileList
.extend(images
)
4164 FileList
.append(item
)
4167 opterr("no playable files specified")
4170 # use this function if you intend to use Accentuate as a library
4174 except SystemExit, e
:
4177 if __name__
=="__main__":
4178 ParseOptions(sys
.argv
[1:])