more scripts file
[archive.git] / Apkawa / pyscreenlist / screenlist.py
blobbdb042b6db9a2440c0ceed5fa54dabf888439b98
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 ###
4 #This file is part of screenlist project
6 #screenlist is a program that captures frames from a video file and save these frames as thumbnails in a single image file.
7 #Copyright (C) 2009 Apkawa
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #You can contact author by email Apkawa@gmail.com
23 ###
25 import os
26 import shutil
27 import tempfile
29 import re
30 import time
31 import Image, ImageFont, ImageDraw, ImageOps
32 from math import ceil
35 import optparse
38 #http://code.google.com/p/mplayer-snapshot/
39 #http://code.activestate.com/recipes/412982/
40 # TODO:
41 # get info file - mediainfo
42 # get screen - http://code.google.com/p/pyffmpeg/
43 # ffmpeg -i video.avi -an -ss 00:00:10 -r 1 -vframes 1 -s 320×240 -y -f mjpeg screenshot.jpg
45 def test(__s):
46 s = __s()
47 s.open_video('01.mkv')
48 #s.make_screens('tmp')
49 s.SETTING.title.logo = True
50 s.make_screenlist(s.GRID,grid=(6,6),)
51 s.save_result('tmp/s.png')
53 def human_format(num, suffix='b'):
54 num=float(num)
55 for x in ['','K','M','G','T']:
56 if num<1024:
57 return "%3.1f%s%s" % (num, x, suffix)
58 num /=1024
60 def calc_resize( orig_wxh, w=None, h=None ):
61 def ince(__float):
62 return int( ceil( __float ))
63 o_w, o_h = orig_wxh
64 if w:
65 h= ince( ( o_h * w ) / float(o_w) )
66 if h:
67 w = ince( ( o_w * h ) / float(o_h) )
68 return (w, h)
70 #@test
71 class Screenlist:
72 FRAMES = 0
73 SECONDS = 1
74 GRID = 2
76 SETTING = type('Visual_Setting',(),{
77 'canvas':type('Canvas_Setting',(),{
78 'bgcolor':'#FFF',
79 'fgcolor':'#000',
80 'padding':5,
81 }),
82 'frame': type('Frame_Setting',(),{
83 'bgcolor':'#CCC',
84 'fgcolor':'#000',
85 'fontsize':12,
86 'padding':5,
87 'border_color': '#AAA'
88 }),
89 'title': type('Title_Setting',(),{
90 'fgcolor':'#000',
91 'fontsize':18,
92 'logo': False,
93 'logopath': 'logo.png',
94 }),
95 '__doc__': 'Setting Screenlist class',
98 imgs = []
99 temp_files =[]
100 columns = None
101 tempdir = tempfile.gettempdir()
102 oformat = 'png'#jpeg
103 def __init__(self):
104 self.exec_mplayer_info = 'mplayer "%s" \
105 -ao null -vo null \
106 -frames 1 \
107 -identify \
108 2>/dev/null'
109 self.exec_mplayer = 'mplayer "%(videopath)s" \
110 -ao null \
111 -vo %(oformat)s:outdir=%(tempdir)s \
112 -nojoystick \
113 -nolirc \
114 -nocache \
115 -nortc \
116 -noautosub \
117 -slave \
118 -benchmark\
119 -ss %(time_pos)s \
120 -frames 1 '
121 #2>/dev/null'
122 self.exec_mplayer_thumb = self.exec_mplayer+ '-zoom -xy %(t_width)i '
124 def open_video(self, video_path):
125 'Open video and return bool'
126 self.videopath = video_path
127 __info = os.popen( self.exec_mplayer_info% video_path).read()
128 #print __info
129 self.identify = dict(re.findall('ID_(.*)=(.*)',__info))
130 self.identify.update({
131 'filesize': os.stat( video_path).st_size
133 if self.identify.get('LENGTH'):
134 #print self.identify
135 self.video_info = re.findall('VIDEO\:.*', __info)[0]
136 _a = re.findall('AUDIO\:.*',__info)
137 self.audio_info = [ _a[0] if _a else 'AUDIO: None']
139 for i in xrange(10):
140 __a_lang = self.identify.get('AID_%i_LANG'%i)
141 __a_name = self.identify.get('AID_%i_NAME'%i)
142 if __a_lang:
143 __a_str = 'AID %i: %s %s'%(i, __a_lang, __a_name if __a_name else '')
144 self.audio_info.append( __a_str )
145 else:
146 break
147 #print self.video_info
148 #print self.audio_info
151 self.video_length = float(self.identify['LENGTH'])
152 return True
153 else:
154 return False
156 #print self.identify
158 def get_screen(self, outfile, time_pos='00:00:00', t_width=None):
159 __exec_arg = {
160 'videopath':self.videopath,
161 'time_pos':time_pos,
162 'tempdir':self.tempdir,
163 'oformat':self.oformat,
165 if t_width:
166 __exec_arg.update({'t_width':t_width,})
167 __t = os.popen(
168 self.exec_mplayer_thumb%__exec_arg+'2>/dev/null'
169 ).read()
170 else:
171 __t = os.popen(
172 self.exec_mplayer%__exec_arg+'2>/dev/null'
173 ).read()
174 screen = os.path.join(self.tempdir,'00000001.png')
175 try:
176 shutil.copyfile( screen, outfile)
177 os.remove( screen)
178 return True
179 except ( OSError,IOError):
180 return False
182 def make_screens(self, outdir,frames=5, prefix='',suffix='' , t_width=None):
183 if not os.path.isdir( outdir):
184 raise Exception('%s not dir'%outdir)
186 s_time = int( self.video_length / frames )
187 files = []
188 for f in xrange(frames):
189 __filepath = os.path.join(
190 os.path.abspath(outdir),
191 '%s%s%s.%s'%(prefix, f+1, suffix, self.oformat) )
192 time_pos = self.str_time(
193 s_time*f if f else s_time/3
195 if self.get_screen( __filepath, time_pos, t_width ):
196 files.append(__filepath)
197 return files
199 def make_screenlist(self,
200 time_type=FRAMES,#
201 frames=24,
202 grid=(6,6),
203 columns=6,
204 s_time=120,
205 t_width=200,
208 time_type - set method get frames of video.
209 self.FRAMES, self.SECONDS, self.GRID
210 t_width - width frame thumbinail
213 padding = self.SETTING.canvas.padding
214 fgcolor = self.SETTING.canvas.fgcolor
215 bgcolor = self.SETTING.canvas.bgcolor
218 if time_type == self.FRAMES:
219 self.frames = frames
220 s_time = int( self.video_length /frames)
222 elif time_type == self.SECONDS:
223 self.frames = int( self.video_length/ s_time )
225 elif time_type == self.GRID:
226 self.frames = grid[0]*grid[1]
227 s_time = int( self.video_length/ self.frames )
228 columns = grid[0]
230 for f in xrange(self.frames):
231 __tempfile = os.path.join( self.tempdir,'%i.png'%f)
232 time_pos = self.str_time(
233 s_time*f if f else s_time/3
236 #print time_pos
238 if self.get_screen( __tempfile, time_pos, t_width):
239 self.temp_files.append( __tempfile )
240 self.imgs.append(
241 self._make_frame( Image.open( __tempfile ), time_pos ) )
242 else:
243 self.frames -=1
244 continue
245 #print self.frames
247 def title( img, text_tuple,font=None, title_height=150, ):
248 logo = self.SETTING.title.logo
249 logo_left = 0
250 if logo:
251 logo_img = Image.open( self.SETTING.title.logopath)
252 logo_img = logo_img.resize( calc_resize( logo_img.size, h=title_height-padding*2) )
253 l_w,l_h = logo_img.size
254 left = marl
255 right = left+l_h
256 top = mart
257 bottom = top+l_h
258 bbox = (left, top, right, bottom)
259 img.paste( logo_img, bbox)
261 logo_left = marl+l_w+padding
264 fgcolor= self.SETTING.title.fgcolor
265 if not font:
266 font, fontsize = self._font()
268 draw = ImageDraw.Draw( img)
270 for r in xrange(len( text_tuple)):
271 t = text_tuple[r]
272 draw.text(
273 xy=(marl+logo_left ,mart+r*(padding+fontsize)),
274 text=t,
275 font=font,
276 fill=fgcolor )
278 title_h = 150
279 photow, photoh = self.imgs[0].size
280 #print photow, photoh
281 marl = 5
282 marr = 5
283 mart = 5
284 marb = 5
286 marw = marl+marr
287 marh = mart+marb
288 padw = (columns-1)*padding
289 rows = int(ceil(self.frames/float(columns)))
290 padh = (rows-1)*padding
291 image_size = ( columns*photow+padw+marw, rows*photoh+padh+marh+title_h )
293 self.image_new = Image.new('RGB', image_size, bgcolor)
295 title_text = [
296 'Filename: %s; '%os.path.split( self.identify['FILENAME'] )[1] +
297 'size: %s'% human_format( int(self.identify['filesize']) ),
298 # 'Resolution: %sx%s; '%( self.identify['VIDEO_WIDTH'],self.identify['VIDEO_HEIGHT'])+
299 'Duration: %s'%self.str_time( self.identify['LENGTH']) ,
300 self.video_info,
302 title_text += self.audio_info
304 title( self.image_new, title_text)
306 for r in xrange(rows):
307 for c in xrange( columns):
308 left = marl+c*(photow+padding)
309 right = left+photow
310 upper = (mart+r*(photoh+padding))+title_h
311 lower = upper+photoh
312 bbox = (left, upper, right, lower)
313 try:
314 img = self.imgs.pop(0)
315 self.image_new.paste(img,bbox)
316 except IndexError:
317 break
319 self._clean()
321 def str_time(self, seconds):
322 'Return str_time in %H:%M:%S format'
323 return time.strftime("%H:%M:%S", time.gmtime(
324 int( float(seconds) )
327 def _make_frame(self, img, str_time,):
328 __frame = self.SETTING.frame
329 padding = __frame.padding
330 bgcolor = __frame.bgcolor
331 fgcolor = __frame.fgcolor
332 border_color = __frame.border_color
333 font, fontsize = self._font( self.SETTING.frame.fontsize)
334 title_frame_height= fontsize + padding
336 img = ImageOps.expand(img,(padding,padding,padding,title_frame_height),fill=bgcolor)
337 img = ImageOps.expand(img,(1,1,1,1),fill= border_color )
338 img_w, img_h = img.size
339 _draw = ImageDraw.Draw( img)
340 _draw.text(
341 xy=( padding, img_h-(title_frame_height) ),
342 text=str_time,
343 font=font,
344 fill= fgcolor)
345 return img
347 def _font(self, fontsize=18, fontpath='fonts/arial.ttf'):
348 try:
349 font = ImageFont.truetype( fontpath,fontsize)
350 except IOError:
351 font = ImageFont.load_default()
352 return font, fontsize
354 def save_result(self,dest='tmp/screenlist.png'):
355 self.image_new.save(dest, 'PNG')
357 def _clean(self):
358 for tf in self.temp_files:
359 try:
360 os.remove(tf)
361 except OSError:
362 pass
363 return True
365 class CliAppScreenlist:
366 time_type = None
367 outdir = None
369 frames=None
370 seconds=None
371 grid=None
372 screenshots = False
374 def __init__(self):
375 pass
376 def parseopt(self):
377 def time_parse(option, opt_str, value, __parser, *args, **kwargs):
378 if not self.time_type:
379 self.time_type = kwargs.get('time_type')
380 else:
381 parser.error('select only of -f,-s,-g')
382 if self.time_type == Screenlist.FRAMES:
383 self.frames= int(value)
384 elif self.time_type == Screenlist.SECONDS:
385 self.seconds = int(value)
386 elif self.time_type == Screenlist.GRID:
387 self.grid = re.findall('^(\d+),(\d+)$',value)
388 if self.grid:
389 self.grid = [int(i) for i in self.grid[0]]
390 else:
391 parser.error('not valid values in %s. \d+,\d+'%option)
392 if opt_str == '--sh':
393 if value:
394 print dir(value)
395 print option, opt_str, value, args, kwargs
396 return 1
398 parser = optparse.OptionParser()
399 parser.add_option('-f','--frames', action='callback', type='int',
400 callback=time_parse, callback_kwargs={'time_type':Screenlist.FRAMES})
401 parser.add_option('-s','--seconds', action='callback', type='int' ,
402 callback=time_parse, callback_kwargs={'time_type':Screenlist.SECONDS})
403 parser.add_option('-g','--grid', action='callback', type='string', metavar='cols,rows',
404 callback=time_parse, callback_kwargs={'time_type':Screenlist.GRID})
405 parser.add_option('-o','--outdir', action='store',
406 type='string',dest='outdir',)
407 parser.add_option('--sh','--screenshots', action='store_true',dest='screenshots')
408 if os.sys.argv[1:]:
409 (options, self.videofiles ) = parser.parse_args()
410 #print options, self.videofiles
411 if not self.videofiles:
412 parser.error('not select videofiles')
413 if options.outdir:
414 self.outdir = os.path.abspath( options.outdir )
415 if not os.path.isdir(self.outdir):
416 parser.error('-o select dir name')
417 if options.screenshots:
418 self.screenshots = True
419 else:
420 parser.print_help()
421 os.sys.exit(1)
423 def main(self):
424 screenlist = Screenlist()
425 #screenlist.SETTING.title.logo = True
426 for vf in self.videofiles:
427 if os.path.isfile( vf):
428 if not screenlist.open_video(vf):
429 os.sys.stderr.write( 'Not open file %s\n'%vf)
430 continue
432 if self.time_type:
433 screenlist.make_screenlist(
434 self.time_type,
435 frames=self.frames,
436 s_time=self.seconds,
437 grid=self.grid)
438 else:
439 screenlist.make_screenlist( screenlist.GRID )
440 video_dir, video_name = os.path.split(vf)
441 if not self.outdir:
442 save_dir = video_dir
443 else:
444 save_dir= self.outdir
445 save_dir=os.path.abspath( save_dir)
446 result_path = os.path.join( save_dir, video_name+'.png')
448 if self.screenshots:
449 files = screenlist.make_screens( save_dir, prefix='screenshots_'+video_name+'_')
450 print '\n'.join(files)
452 if os.path.exists( result_path):
454 while True:
455 if os.path.exists( result_path):
456 result_path = os.path.join(
457 save_dir, video_name+'-%i.png'%i)
458 i += 1
459 else:
460 break
462 print result_path
463 screenlist.save_result( result_path )
471 if __name__ == '__main__':
472 c = CliAppScreenlist()
473 c.parseopt()
474 c.main()