2011-05-06 00:37:15 +02:00
from CachePath import *
2011-05-05 13:04:40 +02:00
from datetime import datetime
import json
2011-05-07 13:42:34 +02:00
import os
2011-05-05 13:04:40 +02:00
import os . path
from PIL import Image
from PIL . ExifTags import TAGS
2014-03-16 15:17:13 +01:00
from multiprocessing import Pool
2011-05-06 08:43:54 +02:00
import gc
2013-12-21 04:40:25 +01:00
import tempfile
2014-01-30 00:11:25 +01:00
from VideoToolWrapper import *
2011-05-05 13:04:40 +02:00
2015-05-04 19:46:58 +02:00
def make_photo_thumbs ( self , original_path , thumb_path , size ) :
2014-03-16 15:17:13 +01:00
# The pool methods use a queue.Queue to pass tasks to the worker processes.
# Everything that goes through the queue.Queue must be pickable, and since
2015-05-04 19:46:58 +02:00
# self._photo_thumbnail is not defined at the top level, it's not pickable.
2014-03-16 15:17:13 +01:00
# This is why we have this "dummy" function, so that it's pickable.
2015-05-04 19:46:58 +02:00
self . _photo_thumbnail ( original_path , thumb_path , size [ 0 ] , size [ 1 ] )
2014-03-16 15:17:13 +01:00
2011-05-05 13:04:40 +02:00
class Album ( object ) :
def __init__ ( self , path ) :
self . _path = trim_base ( path )
self . _photos = list ( )
self . _albums = list ( )
self . _photos_sorted = True
self . _albums_sorted = True
@property
2011-05-06 00:37:15 +02:00
def photos ( self ) :
return self . _photos
@property
def albums ( self ) :
return self . _albums
@property
2011-05-05 13:04:40 +02:00
def path ( self ) :
return self . _path
def __str__ ( self ) :
return self . path
@property
def cache_path ( self ) :
return json_cache ( self . path )
@property
def date ( self ) :
self . _sort ( )
if len ( self . _photos ) == 0 and len ( self . _albums ) == 0 :
2011-05-05 14:42:04 +02:00
return datetime ( 1900 , 1 , 1 )
2011-05-05 13:04:40 +02:00
elif len ( self . _photos ) == 0 :
return self . _albums [ - 1 ] . date
elif len ( self . _albums ) == 0 :
return self . _photos [ - 1 ] . date
return max ( self . _photos [ - 1 ] . date , self . _albums [ - 1 ] . date )
def __cmp__ ( self , other ) :
return cmp ( self . date , other . date )
def add_photo ( self , photo ) :
self . _photos . append ( photo )
self . _photos_sorted = False
def add_album ( self , album ) :
self . _albums . append ( album )
2011-05-06 14:38:15 +02:00
self . _albums_sorted = False
2011-05-05 13:04:40 +02:00
def _sort ( self ) :
if not self . _photos_sorted :
self . _photos . sort ( )
self . _photos_sorted = True
if not self . _albums_sorted :
self . _albums . sort ( )
self . _albums_sorted = True
2011-05-07 04:48:09 +02:00
@property
def empty ( self ) :
if len ( self . _photos ) != 0 :
return False
if len ( self . _albums ) == 0 :
return True
for album in self . _albums :
if not album . empty :
return False
return True
2011-05-05 13:04:40 +02:00
def cache ( self , base_dir ) :
self . _sort ( )
fp = open ( os . path . join ( base_dir , self . cache_path ) , ' w ' )
json . dump ( self , fp , cls = PhotoAlbumEncoder )
fp . close ( )
@staticmethod
def from_cache ( path ) :
fp = open ( path , " r " )
dictionary = json . load ( fp )
fp . close ( )
return Album . from_dict ( dictionary )
@staticmethod
def from_dict ( dictionary , cripple = True ) :
album = Album ( dictionary [ " path " ] )
for photo in dictionary [ " photos " ] :
2011-05-05 14:42:04 +02:00
album . add_photo ( Photo . from_dict ( photo , untrim_base ( album . path ) ) )
2011-05-05 13:04:40 +02:00
if not cripple :
for subalbum in dictionary [ " albums " ] :
album . add_album ( Album . from_dict ( subalbum ) , cripple )
album . _sort ( )
return album
def to_dict ( self , cripple = True ) :
self . _sort ( )
2011-05-07 04:48:09 +02:00
subalbums = [ ]
2011-05-05 13:04:40 +02:00
if cripple :
2011-05-07 04:48:09 +02:00
for sub in self . _albums :
if not sub . empty :
subalbums . append ( { " path " : trim_base_custom ( sub . path , self . _path ) , " date " : sub . date } )
2011-05-05 13:04:40 +02:00
else :
2011-05-07 04:48:09 +02:00
for sub in self . _albums :
if not sub . empty :
subalbums . append ( sub )
2011-05-05 13:04:40 +02:00
return { " path " : self . path , " date " : self . date , " albums " : subalbums , " photos " : self . _photos }
2011-05-05 14:42:04 +02:00
def photo_from_path ( self , path ) :
for photo in self . _photos :
if trim_base ( path ) == photo . _path :
return photo
return None
2013-12-21 04:40:25 +01:00
2011-05-05 13:04:40 +02:00
class Photo ( object ) :
2014-03-16 15:03:54 +01:00
thumb_sizes = [ ( 75 , True ) , ( 150 , True ) , ( 640 , False ) , ( 1024 , False ) , ( 1600 , False ) ]
2011-05-05 14:42:04 +02:00
def __init__ ( self , path , thumb_path = None , attributes = None ) :
2011-05-05 13:04:40 +02:00
self . _path = trim_base ( path )
self . is_valid = True
2013-12-21 04:40:25 +01:00
image = None
2011-05-06 00:37:15 +02:00
try :
mtime = file_mtime ( path )
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-06 00:37:15 +02:00
except :
self . is_valid = False
return
2011-05-07 08:56:54 +02:00
if attributes is not None and attributes [ " dateTimeFile " ] > = mtime :
2011-05-05 13:04:40 +02:00
self . _attributes = attributes
return
2011-05-05 13:21:12 +02:00
self . _attributes = { }
2011-05-07 08:56:54 +02:00
self . _attributes [ " dateTimeFile " ] = mtime
2013-12-21 04:40:25 +01:00
self . _attributes [ " mediaType " ] = " photo "
2011-05-05 13:21:12 +02:00
2011-05-05 13:04:40 +02:00
try :
2011-05-05 14:42:04 +02:00
image = Image . open ( path )
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-05 13:04:40 +02:00
except :
2013-12-21 04:40:25 +01:00
self . _video_metadata ( path )
if isinstance ( image , Image . Image ) :
self . _photo_metadata ( image )
2015-05-05 00:39:26 +02:00
self . _photo_thumbnails ( path , thumb_path )
2013-12-21 04:40:25 +01:00
elif self . _attributes [ " mediaType " ] == " video " :
self . _video_thumbnails ( thumb_path , path )
self . _video_transcode ( thumb_path , path )
else :
2011-05-05 13:04:40 +02:00
self . is_valid = False
return
2015-05-04 19:46:58 +02:00
2013-12-21 04:40:25 +01:00
def _photo_metadata ( self , image ) :
2011-05-06 13:55:47 +02:00
self . _attributes [ " size " ] = image . size
2011-05-07 08:56:54 +02:00
self . _orientation = 1
2011-05-05 13:04:40 +02:00
try :
2011-05-05 14:42:04 +02:00
info = image . _getexif ( )
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-05 13:04:40 +02:00
except :
2011-05-05 14:42:04 +02:00
return
2011-05-06 00:37:15 +02:00
if not info :
return
2011-05-07 08:56:54 +02:00
exif = { }
2011-05-05 14:42:04 +02:00
for tag , value in info . items ( ) :
decoded = TAGS . get ( tag , tag )
2015-01-19 13:08:45 +01:00
if isinstance ( value , str ) or isinstance ( value , unicode ) :
2012-03-29 15:42:53 +02:00
value = value . strip ( ) . partition ( " \x00 " ) [ 0 ]
2015-01-25 14:19:38 +01:00
if ( isinstance ( decoded , str ) or isinstance ( decoded , unicode ) ) and decoded . startswith ( " DateTime " ) :
2011-05-07 08:56:54 +02:00
try :
value = datetime . strptime ( value , ' % Y: % m: %d % H: % M: % S ' )
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-07 08:56:54 +02:00
except :
2011-05-07 09:00:06 +02:00
continue
2011-05-07 08:56:54 +02:00
exif [ decoded ] = value
if " Orientation " in exif :
self . _orientation = exif [ " Orientation " ] ;
if self . _orientation in range ( 5 , 9 ) :
self . _attributes [ " size " ] = ( self . _attributes [ " size " ] [ 1 ] , self . _attributes [ " size " ] [ 0 ] )
2013-12-21 04:40:25 +01:00
if self . _orientation - 1 < len ( self . _photo_metadata . orientation_list ) :
self . _attributes [ " orientation " ] = self . _photo_metadata . orientation_list [ self . _orientation - 1 ]
2011-05-07 08:56:54 +02:00
if " Make " in exif :
self . _attributes [ " make " ] = exif [ " Make " ]
if " Model " in exif :
self . _attributes [ " model " ] = exif [ " Model " ]
if " ApertureValue " in exif :
self . _attributes [ " aperture " ] = exif [ " ApertureValue " ]
2011-05-07 09:42:44 +02:00
elif " FNumber " in exif :
self . _attributes [ " aperture " ] = exif [ " FNumber " ]
2011-05-07 08:56:54 +02:00
if " FocalLength " in exif :
self . _attributes [ " focalLength " ] = exif [ " FocalLength " ]
if " ISOSpeedRatings " in exif :
self . _attributes [ " iso " ] = exif [ " ISOSpeedRatings " ]
if " ISO " in exif :
self . _attributes [ " iso " ] = exif [ " ISO " ]
if " PhotographicSensitivity " in exif :
self . _attributes [ " iso " ] = exif [ " PhotographicSensitivity " ]
if " ExposureTime " in exif :
self . _attributes [ " exposureTime " ] = exif [ " ExposureTime " ]
2013-12-21 04:40:25 +01:00
if " Flash " in exif and exif [ " Flash " ] in self . _photo_metadata . flash_dictionary :
2011-05-07 09:42:44 +02:00
try :
2013-12-21 04:40:25 +01:00
self . _attributes [ " flash " ] = self . _photo_metadata . flash_dictionary [ exif [ " Flash " ] ]
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-07 09:42:44 +02:00
except :
pass
2013-12-21 04:40:25 +01:00
if " LightSource " in exif and exif [ " LightSource " ] in self . _photo_metadata . light_source_dictionary :
2011-06-27 23:12:59 +02:00
try :
2013-12-21 04:40:25 +01:00
self . _attributes [ " lightSource " ] = self . _photo_metadata . light_source_dictionary [ exif [ " LightSource " ] ]
2011-06-27 23:12:59 +02:00
except KeyboardInterrupt :
raise
except :
pass
2013-12-21 04:40:25 +01:00
if " ExposureProgram " in exif and exif [ " ExposureProgram " ] < len ( self . _photo_metadata . exposure_list ) :
self . _attributes [ " exposureProgram " ] = self . _photo_metadata . exposure_list [ exif [ " ExposureProgram " ] ]
2011-05-07 08:56:54 +02:00
if " SpectralSensitivity " in exif :
self . _attributes [ " spectralSensitivity " ] = exif [ " SpectralSensitivity " ]
2013-12-21 04:40:25 +01:00
if " MeteringMode " in exif and exif [ " MeteringMode " ] < len ( self . _photo_metadata . metering_list ) :
self . _attributes [ " meteringMode " ] = self . _photo_metadata . metering_list [ exif [ " MeteringMode " ] ]
if " SensingMethod " in exif and exif [ " SensingMethod " ] < len ( self . _photo_metadata . sensing_method_list ) :
self . _attributes [ " sensingMethod " ] = self . _photo_metadata . sensing_method_list [ exif [ " SensingMethod " ] ]
if " SceneCaptureType " in exif and exif [ " SceneCaptureType " ] < len ( self . _photo_metadata . scene_capture_type_list ) :
self . _attributes [ " sceneCaptureType " ] = self . _photo_metadata . scene_capture_type_list [ exif [ " SceneCaptureType " ] ]
if " SubjectDistanceRange " in exif and exif [ " SubjectDistanceRange " ] < len ( self . _photo_metadata . subject_distance_range_list ) :
self . _attributes [ " subjectDistanceRange " ] = self . _photo_metadata . subject_distance_range_list [ exif [ " SubjectDistanceRange " ] ]
2011-05-07 08:56:54 +02:00
if " ExposureCompensation " in exif :
self . _attributes [ " exposureCompensation " ] = exif [ " ExposureCompensation " ]
if " ExposureBiasValue " in exif :
self . _attributes [ " exposureCompensation " ] = exif [ " ExposureBiasValue " ]
if " DateTimeOriginal " in exif :
2015-06-02 20:28:28 +02:00
try :
self . _attributes [ " dateTimeOriginal " ] = datetime . strptime ( exif [ " DateTimeOriginal " ] , ' % Y: % m: %d % H: % M: % S ' )
except KeyboardInterrupt :
raise
except TypeError :
self . _attributes [ " dateTimeOriginal " ] = exif [ " DateTimeOriginal " ]
2011-05-07 08:56:54 +02:00
if " DateTime " in exif :
2015-06-02 20:28:28 +02:00
try :
self . _attributes [ " dateTime " ] = datetime . strptime ( exif [ " DateTime " ] , ' % Y: % m: %d % H: % M: % S ' )
except KeyboardInterrupt :
raise
except TypeError :
self . _attributes [ " dateTime " ] = exif [ " DateTime " ]
2015-05-04 19:46:58 +02:00
2013-12-21 04:40:25 +01:00
_photo_metadata . flash_dictionary = { 0x0 : " No Flash " , 0x1 : " Fired " , 0x5 : " Fired, Return not detected " , 0x7 : " Fired, Return detected " , 0x8 : " On, Did not fire " , 0x9 : " On, Fired " , 0xd : " On, Return not detected " , 0xf : " On, Return detected " , 0x10 : " Off, Did not fire " , 0x14 : " Off, Did not fire, Return not detected " , 0x18 : " Auto, Did not fire " , 0x19 : " Auto, Fired " , 0x1d : " Auto, Fired, Return not detected " , 0x1f : " Auto, Fired, Return detected " , 0x20 : " No flash function " , 0x30 : " Off, No flash function " , 0x41 : " Fired, Red-eye reduction " , 0x45 : " Fired, Red-eye reduction, Return not detected " , 0x47 : " Fired, Red-eye reduction, Return detected " , 0x49 : " On, Red-eye reduction " , 0x4d : " On, Red-eye reduction, Return not detected " , 0x4f : " On, Red-eye reduction, Return detected " , 0x50 : " Off, Red-eye reduction " , 0x58 : " Auto, Did not fire, Red-eye reduction " , 0x59 : " Auto, Fired, Red-eye reduction " , 0x5d : " Auto, Fired, Red-eye reduction, Return not detected " , 0x5f : " Auto, Fired, Red-eye reduction, Return detected " }
_photo_metadata . light_source_dictionary = { 0 : " Unknown " , 1 : " Daylight " , 2 : " Fluorescent " , 3 : " Tungsten (incandescent light) " , 4 : " Flash " , 9 : " Fine weather " , 10 : " Cloudy weather " , 11 : " Shade " , 12 : " Daylight fluorescent (D 5700 - 7100K) " , 13 : " Day white fluorescent (N 4600 - 5400K) " , 14 : " Cool white fluorescent (W 3900 - 4500K) " , 15 : " White fluorescent (WW 3200 - 3700K) " , 17 : " Standard light A " , 18 : " Standard light B " , 19 : " Standard light C " , 20 : " D55 " , 21 : " D65 " , 22 : " D75 " , 23 : " D50 " , 24 : " ISO studio tungsten " }
_photo_metadata . metering_list = [ " Unknown " , " Average " , " Center-weighted average " , " Spot " , " Multi-spot " , " Multi-segment " , " Partial " ]
_photo_metadata . exposure_list = [ " Not Defined " , " Manual " , " Program AE " , " Aperture-priority AE " , " Shutter speed priority AE " , " Creative (Slow speed) " , " Action (High speed) " , " Portrait " , " Landscape " , " Bulb " ]
_photo_metadata . orientation_list = [ " Horizontal (normal) " , " Mirror horizontal " , " Rotate 180 " , " Mirror vertical " , " Mirror horizontal and rotate 270 CW " , " Rotate 90 CW " , " Mirror horizontal and rotate 90 CW " , " Rotate 270 CW " ]
_photo_metadata . sensing_method_list = [ " Not defined " , " One-chip color area sensor " , " Two-chip color area sensor " , " Three-chip color area sensor " , " Color sequential area sensor " , " Trilinear sensor " , " Color sequential linear sensor " ]
_photo_metadata . scene_capture_type_list = [ " Standard " , " Landscape " , " Portrait " , " Night scene " ]
_photo_metadata . subject_distance_range_list = [ " Unknown " , " Macro " , " Close view " , " Distant view " ]
2015-05-04 19:46:58 +02:00
2013-12-21 04:40:25 +01:00
def _video_metadata ( self , path , original = True ) :
2015-06-02 23:13:26 +02:00
p = VideoProbeWrapper ( ) . call ( ' -show_format ' , ' -show_streams ' , ' -of ' , ' json ' , ' -loglevel ' , ' 0 ' , path )
if p == False :
self . is_valid = False
return
info = json . loads ( p )
for s in info [ " streams " ] :
if ' codec_type ' in s and s [ ' codec_type ' ] == ' video ' :
self . _attributes [ " mediaType " ] = " video "
self . _attributes [ " size " ] = ( int ( s [ " width " ] ) , int ( s [ " height " ] ) )
if " duration " in s :
2013-12-23 02:33:43 +01:00
self . _attributes [ " duration " ] = s [ " duration " ]
if " tags " in s and " rotate " in s [ " tags " ] :
self . _attributes [ " rotate " ] = s [ " tags " ] [ " rotate " ]
2013-12-21 04:40:25 +01:00
if original :
self . _attributes [ " originalSize " ] = ( int ( s [ " width " ] ) , int ( s [ " height " ] ) )
2013-12-23 02:33:43 +01:00
break
2015-05-04 19:46:58 +02:00
def _photo_thumbnail ( self , original_path , thumb_path , size , square = False ) :
2014-03-16 15:19:52 +01:00
try :
2014-03-16 15:17:13 +01:00
image = Image . open ( original_path )
except KeyboardInterrupt :
raise
except :
self . is_valid = False
return
mirror = image
if self . _orientation == 2 :
# Vertical Mirror
mirror = image . transpose ( Image . FLIP_LEFT_RIGHT )
elif self . _orientation == 3 :
# Rotation 180
mirror = image . transpose ( Image . ROTATE_180 )
elif self . _orientation == 4 :
# Horizontal Mirror
mirror = image . transpose ( Image . FLIP_TOP_BOTTOM )
elif self . _orientation == 5 :
# Horizontal Mirror + Rotation 270
mirror = image . transpose ( Image . FLIP_TOP_BOTTOM ) . transpose ( Image . ROTATE_270 )
elif self . _orientation == 6 :
# Rotation 270
mirror = image . transpose ( Image . ROTATE_270 )
elif self . _orientation == 7 :
# Vertical Mirror + Rotation 270
mirror = image . transpose ( Image . FLIP_LEFT_RIGHT ) . transpose ( Image . ROTATE_270 )
elif self . _orientation == 8 :
# Rotation 90
mirror = image . transpose ( Image . ROTATE_90 )
2015-05-05 00:08:37 +02:00
2014-03-16 15:19:52 +01:00
image = mirror
2015-05-05 00:12:40 +02:00
self . _thumbnail ( image , original_path , thumb_path , size , square )
2014-03-16 15:17:13 +01:00
2015-05-05 00:12:40 +02:00
def _thumbnail ( self , image , original_path , thumb_path , size , square ) :
2011-05-06 00:37:15 +02:00
thumb_path = os . path . join ( thumb_path , image_cache ( self . _path , size , square ) )
2011-05-23 11:25:45 +02:00
info_string = " %s -> %s px " % ( os . path . basename ( original_path ) , str ( size ) )
if square :
info_string + = " , square "
message ( " thumbing " , info_string )
2011-05-07 08:56:54 +02:00
if os . path . exists ( thumb_path ) and file_mtime ( thumb_path ) > = self . _attributes [ " dateTimeFile " ] :
2011-05-05 14:42:04 +02:00
return
2011-05-06 08:49:26 +02:00
gc . collect ( )
2011-05-09 15:33:30 +02:00
try :
image = image . copy ( )
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-09 15:33:30 +02:00
except :
try :
image = image . copy ( ) # we try again to work around PIL bug
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-09 15:33:30 +02:00
except :
2011-05-23 11:25:45 +02:00
message ( " corrupt image " , os . path . basename ( original_path ) )
2013-12-22 02:46:56 +01:00
self . is_valid = False
2011-05-09 15:33:30 +02:00
return
2011-05-05 14:42:04 +02:00
if square :
if image . size [ 0 ] > image . size [ 1 ] :
left = ( image . size [ 0 ] - image . size [ 1 ] ) / 2
top = 0
right = image . size [ 0 ] - ( ( image . size [ 0 ] - image . size [ 1 ] ) / 2 )
bottom = image . size [ 1 ]
else :
left = 0
top = ( image . size [ 1 ] - image . size [ 0 ] ) / 2
right = image . size [ 0 ]
bottom = image . size [ 1 ] - ( ( image . size [ 1 ] - image . size [ 0 ] ) / 2 )
image = image . crop ( ( left , top , right , bottom ) )
2011-05-06 09:19:20 +02:00
gc . collect ( )
2011-05-05 14:42:04 +02:00
image . thumbnail ( ( size , size ) , Image . ANTIALIAS )
2011-05-06 00:37:15 +02:00
try :
2012-04-22 19:20:54 +02:00
image . save ( thumb_path , " JPEG " , quality = 88 )
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
2015-01-27 15:18:27 +01:00
try :
os . unlink ( thumb_path )
except :
pass
2011-05-23 11:25:45 +02:00
raise
2011-05-06 00:37:15 +02:00
except :
2011-05-23 11:25:45 +02:00
message ( " save failure " , os . path . basename ( thumb_path ) )
2015-01-27 15:18:27 +01:00
try :
os . unlink ( thumb_path )
except :
pass
2015-05-04 19:46:58 +02:00
def _photo_thumbnails ( self , original_path , thumb_path ) :
2014-03-16 15:19:52 +01:00
# get number of cores on the system, and use all minus one
num_of_cores = os . sysconf ( ' SC_NPROCESSORS_ONLN ' ) - 1
pool = Pool ( processes = num_of_cores )
for size in Photo . thumb_sizes :
2015-05-04 19:46:58 +02:00
pool . apply_async ( make_photo_thumbs , args = ( self , original_path , thumb_path , size ) )
2014-03-16 15:19:52 +01:00
pool . close ( )
pool . join ( )
2014-03-16 15:17:13 +01:00
2013-12-21 04:40:25 +01:00
def _video_thumbnails ( self , thumb_path , original_path ) :
( tfd , tfn ) = tempfile . mkstemp ( ) ;
2014-01-30 00:11:25 +01:00
p = VideoTranscodeWrapper ( ) . call ( ' -i ' , original_path , ' -f ' , ' image2 ' , ' -vsync ' , ' 1 ' , ' -vframes ' , ' 1 ' , ' -an ' , ' -loglevel ' , ' quiet ' , tfn )
if p == False :
2013-12-21 04:40:25 +01:00
message ( " couldn ' t extract video frame " , os . path . basename ( original_path ) )
os . unlink ( tfn )
2013-12-22 02:46:56 +01:00
self . is_valid = False
2013-12-21 04:40:25 +01:00
return
try :
image = Image . open ( tfn )
except KeyboardInterrupt :
raise
except :
message ( " couldn ' t open video thumbnail " , tfn )
os . unlink ( tfn )
2013-12-22 02:46:56 +01:00
self . is_valid = False
2013-12-21 04:40:25 +01:00
return
mirror = image
if " rotate " in self . _attributes :
if self . _attributes [ " rotate " ] == " 90 " :
mirror = image . transpose ( Image . ROTATE_270 )
elif self . _attributes [ " rotate " ] == " 180 " :
mirror = image . transpose ( Image . ROTATE_180 )
elif self . _attributes [ " rotate " ] == " 270 " :
mirror = image . transpose ( Image . ROTATE_90 )
for size in Photo . thumb_sizes :
if size [ 1 ] :
2015-05-05 00:39:26 +02:00
self . _thumbnail ( mirror , original_path , thumb_path , size [ 0 ] , size [ 1 ] )
2013-12-21 04:40:25 +01:00
os . unlink ( tfn )
def _video_transcode ( self , transcode_path , original_path ) :
2014-02-14 05:28:59 +01:00
transcode_path = os . path . join ( transcode_path , video_cache ( self . _path ) )
2015-05-04 19:46:58 +02:00
# get number of cores on the system, and use all minus one
num_of_cores = os . sysconf ( ' SC_NPROCESSORS_ONLN ' ) - 1
2015-06-02 23:13:26 +02:00
transcode_cmd = [ ' -i ' , original_path , ' -c:v ' , ' libvpx ' , ' -crf ' , ' 10 ' , ' -b:v ' , ' 4M ' , ' -c:a ' , ' libvorbis ' , ' -f ' , ' webm ' , ' -threads ' , str ( num_of_cores ) , ' -loglevel ' , ' 0 ' , ' -y ' ]
2013-12-21 04:40:25 +01:00
filters = [ ]
info_string = " %s -> webm " % ( os . path . basename ( original_path ) )
message ( " transcoding " , info_string )
if os . path . exists ( transcode_path ) and file_mtime ( transcode_path ) > = self . _attributes [ " dateTimeFile " ] :
2013-12-23 03:09:05 +01:00
self . _video_metadata ( transcode_path , False )
2013-12-21 04:40:25 +01:00
return
if " originalSize " in self . _attributes and self . _attributes [ " originalSize " ] [ 1 ] > 720 :
2015-06-02 23:13:26 +02:00
filters . append ( " scale= ' trunc(oh*a/2)*2:min(720 \ ,iw) ' " )
2013-12-21 04:40:25 +01:00
if " rotate " in self . _attributes :
if self . _attributes [ " rotate " ] == " 90 " :
filters . append ( ' transpose=1 ' )
elif self . _attributes [ " rotate " ] == " 180 " :
filters . append ( ' vflip,hflip ' )
elif self . _attributes [ " rotate " ] == " 270 " :
filters . append ( ' transpose=2 ' )
if len ( filters ) :
transcode_cmd . append ( ' -vf ' )
transcode_cmd . append ( ' , ' . join ( filters ) )
transcode_cmd . append ( transcode_path )
2014-01-30 00:11:25 +01:00
p = VideoTranscodeWrapper ( ) . call ( * transcode_cmd )
if p == False :
2013-12-21 04:40:25 +01:00
message ( " transcoding failure " , os . path . basename ( original_path ) )
try :
os . unlink ( transcode_path )
except :
pass
2013-12-22 02:46:56 +01:00
self . is_valid = False
2013-12-21 04:40:25 +01:00
return
self . _video_metadata ( transcode_path , False )
2011-05-05 13:04:40 +02:00
@property
def name ( self ) :
return os . path . basename ( self . _path )
def __str__ ( self ) :
return self . name
@property
2011-05-07 03:12:51 +02:00
def path ( self ) :
return self . _path
@property
2011-05-06 00:37:15 +02:00
def image_caches ( self ) :
2013-12-21 04:40:25 +01:00
caches = [ ]
if " mediaType " in self . _attributes and self . _attributes [ " mediaType " ] == " video " :
for size in Photo . thumb_sizes :
if size [ 1 ] :
caches . append ( image_cache ( self . _path , size [ 0 ] , size [ 1 ] ) )
2014-02-14 05:28:59 +01:00
caches . append ( video_cache ( self . _path ) )
2013-12-21 04:40:25 +01:00
else :
caches = [ image_cache ( self . _path , size [ 0 ] , size [ 1 ] ) for size in Photo . thumb_sizes ]
return caches
2011-05-06 00:37:15 +02:00
@property
2011-05-05 13:04:40 +02:00
def date ( self ) :
2014-08-25 22:37:46 +02:00
correct_date = None ;
2011-05-06 00:37:15 +02:00
if not self . is_valid :
2014-08-25 22:37:46 +02:00
correct_date = datetime ( 1900 , 1 , 1 )
2011-05-07 09:05:54 +02:00
if " dateTimeOriginal " in self . _attributes :
2014-08-25 22:37:46 +02:00
correct_date = self . _attributes [ " dateTimeOriginal " ]
2011-05-07 09:05:54 +02:00
elif " dateTime " in self . _attributes :
2014-08-25 22:37:46 +02:00
correct_date = self . _attributes [ " dateTime " ]
2011-05-05 13:04:40 +02:00
else :
2014-08-25 22:37:46 +02:00
correct_date = self . _attributes [ " dateTimeFile " ]
return correct_date
2011-05-05 13:04:40 +02:00
def __cmp__ ( self , other ) :
2011-05-06 01:49:57 +02:00
date_compare = cmp ( self . date , other . date )
if date_compare == 0 :
return cmp ( self . name , other . name )
return date_compare
2011-05-05 13:04:40 +02:00
@property
def attributes ( self ) :
return self . _attributes
@staticmethod
def from_dict ( dictionary , basepath ) :
del dictionary [ " date " ]
path = os . path . join ( basepath , dictionary [ " name " ] )
del dictionary [ " name " ]
2011-05-05 14:42:04 +02:00
for key , value in dictionary . items ( ) :
2011-05-07 09:12:20 +02:00
if key . startswith ( " dateTime " ) :
2011-05-05 14:42:04 +02:00
try :
dictionary [ key ] = datetime . strptime ( dictionary [ key ] , " %a % b %d % H: % M: % S % Y " )
2011-05-23 11:25:45 +02:00
except KeyboardInterrupt :
raise
2011-05-05 14:42:04 +02:00
except :
pass
2011-05-05 15:17:21 +02:00
return Photo ( path , None , dictionary )
2011-05-05 13:04:40 +02:00
def to_dict ( self ) :
photo = { " name " : self . name , " date " : self . date }
photo . update ( self . attributes )
return photo
class PhotoAlbumEncoder ( json . JSONEncoder ) :
def default ( self , obj ) :
if isinstance ( obj , datetime ) :
2011-05-05 14:42:04 +02:00
return obj . strftime ( " %a % b %d % H: % M: % S % Y " )
2011-05-05 13:04:40 +02:00
if isinstance ( obj , Album ) or isinstance ( obj , Photo ) :
return obj . to_dict ( )
return json . JSONEncoder . default ( self , obj )
2011-05-06 13:58:11 +02:00