def test_rescale_image_dimensions_to_desired_width(self): pil = PILHandler(300, 150, 1.75) self.assertRaises(TypeError, pil.rescale_image_dimensions_to_desired_width) self.assertRaises(TypeError, pil.rescale_image_dimensions_to_desired_width, None) self.assertRaises(TypeError, pil.rescale_image_dimensions_to_desired_width, None, None) self.assertRaises(TypeError, pil.rescale_image_dimensions_to_desired_width, None, None, None) self.assertRaises(TypeError, pil.rescale_image_dimensions_to_desired_width, None, None, None, None) a, b, c, d = pil.rescale_image_dimensions_to_desired_width( 500, 400, 200, 1) self.assertEqual(200, a) self.assertEqual(160, b) self.assertEqual(200, c) self.assertEqual(160, d) a, b, c, d = pil.rescale_image_dimensions_to_desired_width( 500, 400, 200, 2) self.assertEqual(200, a) self.assertEqual(160, b) self.assertEqual(400, c) self.assertEqual(320, d)
def test_get_geo_coordinates(self): pil = PILHandler(300, 150, 1.75) self.assertRaises(TypeError, pil.get_geo_coordinates) self.assertRaises(TypeError, pil.get_geo_coordinates, None) self.assertRaises(TypeError, pil.get_geo_coordinates, '/somewhere/foo.jpg') source_file = path.join(self.base_path, 'testdata', 'im-geo.jpg') lat, lon = pil.get_geo_coordinates(source_file) self.assertEqual(52.51611111111111, lat) self.assertEqual(13.375, lon)
def test_create_album_thumbnail_if_not_present(self): pil = PILHandler(300, 150, 1.75) self.assertRaises(TypeError, pil.create_album_thumbnail_if_not_present) self.assertRaises(TypeError, pil.create_album_thumbnail_if_not_present, None) self.assertRaises(TypeError, pil.create_album_thumbnail_if_not_present, None, None) self.assertRaises(TypeError, pil.create_album_thumbnail_if_not_present, '/somewhere/foo.jpg', '/somewhere/foo2.jpg') source_file = path.join(self.base_path, 'testdata', 'image.jpg') target_file = path.join(self.base_path, 'testdata', 'album-trg.jpg') self.addCleanup(remove, target_file) width, height = pil.create_album_thumbnail_if_not_present( source_file, target_file) self.assertTrue(path.exists(target_file)) self.assertEqual(300, width) self.assertEqual(150, height) im = Image.open(target_file) self.assertEqual((525, 262), im.size)
def __init__(self, main_images_path, static_path): """Constructor""" if main_images_path is None: raise TypeError ('main_images_path not set.') if static_path is None: raise TypeError ('static_path not set.') main_images_path = path.abspath(main_images_path) self.static_path = path.abspath(static_path) self.main_images_path = main_images_path self.static_images_path = path.join(self.static_path, 'images') self.static_fullsize_path = path.join ( self.static_images_path, 'full_size') self.static_athumbs_path = path.join ( self.static_images_path, 'album_thumbs') self.static_ithumbs_path = path.join ( self.static_images_path, 'image_thumbs') self.pil_handler = PILHandler (pyntrest_config.IMAGE_THUMB_WIDTH, pyntrest_config.IMAGE_THUMB_HEIGHT, pyntrest_config.UPSCALE_FACTOR) self.upgrade_static_files()
class PyntrestHandler (): """Instances of this class handle the main processing of local albums and images in Pyntrest""" pil_handler = None main_images_path = None static_path = None static_images_path = None static_fullsize_path = None static_athumbs_path = None static_ithumbs_path = None def __init__(self, main_images_path, static_path): """Constructor""" if main_images_path is None: raise TypeError ('main_images_path not set.') if static_path is None: raise TypeError ('static_path not set.') main_images_path = path.abspath(main_images_path) self.static_path = path.abspath(static_path) self.main_images_path = main_images_path self.static_images_path = path.join(self.static_path, 'images') self.static_fullsize_path = path.join ( self.static_images_path, 'full_size') self.static_athumbs_path = path.join ( self.static_images_path, 'album_thumbs') self.static_ithumbs_path = path.join ( self.static_images_path, 'image_thumbs') self.pil_handler = PILHandler (pyntrest_config.IMAGE_THUMB_WIDTH, pyntrest_config.IMAGE_THUMB_HEIGHT, pyntrest_config.UPSCALE_FACTOR) self.upgrade_static_files() def set_main_images_path (self, main_images_path): """Reset main images path to another location. Used from within unit tests.""" self.main_images_path = main_images_path def on_startup(self): """Called once on startup to invoke the creation of all necessary thumbnails and copying all the images into the folder that serves static content""" print ('Preparing Pyntrest...') global current_subdir # declare global counter current_subdir = 1 mkdirs (self.static_images_path) mkdirs (self.static_athumbs_path) mkdirs (self.static_ithumbs_path) mkdirs (self.static_fullsize_path) # Obtain number of sub directories number_of_subdirs = 0 for dirname, _, _ in walk (self.main_images_path): if not '\.' in str(dirname) and not '/.' in str(dirname): number_of_subdirs += 1 print ('Found {0} non-hidden directories...'.format(number_of_subdirs)) # Call recursive method self.on_startup_prepare_folder( self.main_images_path, '/', number_of_subdirs) print ('Preparation of Pyntrest done...') def on_startup_prepare_folder (self, current_album_path_abs, request_path, number_of_subdirs): """Recursive method to send a simulated HTTP GET request to invoke content creation on the related subfolder""" global current_subdir # declare global print ('{}/{} :: Creating data for path \'{}\' and virtual path \'{}\'' .format(current_subdir, number_of_subdirs, current_album_path_abs, request_path)) current_subdir = current_subdir + 1 self.generate_view_context(request_path) subdirs = get_immediate_subdirectories(current_album_path_abs) for subdir in subdirs: self.on_startup_prepare_folder( path.join (current_album_path_abs, subdir), request_path + subdir + '/', number_of_subdirs) def check_if_album_exists (self, request_path): """Converts the given request path to the related absolute file system path to the user's image folder and tests whether this folder exists.""" local_albumpath_abs = convert_url_path_to_local_filesystem_path( self.main_images_path, request_path) if (path.exists(local_albumpath_abs) and path.isdir(local_albumpath_abs)): return True else: return False def generate_view_context_bookify(self): """Forwarding the view context request to the_bookify subinstance""" return pyntrest_bookify.generate_view_context(self) def generate_view_context(self, request_path): """The core "magic" method that reads the requested album filesystem path related to the virtual album path from the request and obtains all necessary information to create a neat web photo album""" local_albumpath_abs = convert_url_path_to_local_filesystem_path( self.main_images_path, request_path) local_albumpath_rel = convert_url_path_to_local_filesystem_path( '', request_path) mkdirs(path.join (self.static_fullsize_path, local_albumpath_rel)) mkdirs(path.join (self.static_ithumbs_path, local_albumpath_rel)) (album_title, album_description, album_cover, reversed_sorting, hide_cover, mods_on_top, _) = read_optional_album_metadata ( local_albumpath_abs, pyntrest_config.META_INI_FILE_PATTERN) # setup sub albums subalbums = [] for subalbum_name in get_immediate_subdirectories(local_albumpath_abs): self.process_subalbum (subalbums, subalbum_name, local_albumpath_rel, local_albumpath_abs) # sort subalbums by path if (mods_on_top): subalbums = sorted(subalbums, key=lambda subalbum: subalbum.last_modified, reverse=True) else: subalbums = sorted(subalbums, key=lambda subalbum: subalbum.path, reverse=False) # setup images images = [] for image_name in listdir(local_albumpath_abs): subimage = self.process_subimage(image_name, local_albumpath_rel, local_albumpath_abs) if subimage is None: continue if (album_cover is None or hide_cover is False): images.append(subimage) else: if album_cover == image_name: pass # ignore else: images.append(subimage) # update image descriptions image_descriptions = read_optional_image_metadata( local_albumpath_abs, pyntrest_config.META_INI_FILE_PATTERN) for image in images: basename = path.basename(image.location).lower() try: image_description = image_descriptions[basename] image.description = image_description except KeyError: pass # Ignore # sort images by path images = sorted(images, key=lambda albumimage: albumimage.location, reverse=reversed_sorting) # setup breadcrumb breadcrumbs = [] breadcrumb_paths = get_absolute_breadcrumb_filesystem_paths ( request_path) local_albumpath_abs = self.main_images_path path_string = '' for breadcrumb_path in breadcrumb_paths: local_albumpath_abs = path.join(local_albumpath_abs, breadcrumb_path) path_string = path_string + '/' + breadcrumb_path path_string = sub ('[/]+' , '/', path_string) album_title, album_description, _, _, _, _, _= ( read_optional_album_metadata (local_albumpath_abs, pyntrest_config.META_INI_FILE_PATTERN)) url_path = path_string if pyntrest_config.EXTERNAL_BASE_URL is not None: url_path = pyntrest_config.EXTERNAL_BASE_URL + url_path web_path = WebPath(title=album_title, path=url_path) breadcrumbs.append(web_path) page_title = breadcrumbs[0].title breadcrumbs[0].title = pyntrest_config.WORDING_HOME # check for intro text intro_content = None intro_file = None subfiles = get_immediate_subfiles(local_albumpath_abs) for subfile in subfiles: matching = match(pyntrest_config.INTRO_MD_FILE_PATTERN, subfile.lower()) if matching != None: intro_file = path.join(local_albumpath_abs, subfile) if intro_file and path.isfile(intro_file): intro_content, _ = get_html_content(intro_file) # show header? show_breadcrumb = True first_page = len(breadcrumbs) == 1 try: if first_page and pyntrest_config.SUPPRESS_BREADCRUMB_ON_HOME: show_breadcrumb = False except AttributeError: pass # just show it. context = { 'page_title': page_title, 'col_width': pyntrest_config.IMAGE_THUMB_WIDTH, 'col_height' : pyntrest_config.IMAGE_THUMB_HEIGHT, 'images': images, 'show_breadcrum': False, 'first_page': first_page, 'subalbums': subalbums, 'album_title' : album_title, 'album_description' : album_description, 'lang_images' : pyntrest_config.WORDING_IMAGES, 'lang_albums' : pyntrest_config.WORDING_ALBUM, 'breadcrumbs' : breadcrumbs, 'show_breadcrumb': show_breadcrumb, 'show_headings' : pyntrest_config.SHOW_ALBUM_IMAGES_WORDINGS, 'intro_content' : intro_content} return context def process_subalbum (self, subalbums, subalbum_name, local_albumpath_rel, local_albumpath_abs): """Uses the given sub-album name and creates a Album model for it, including the search for optional descriptions and album covers and the creation of all necessary thumbnails. Finally it appends the Album to the provided sub-album list.""" local_subalbumpath_abs = path.join(local_albumpath_abs, subalbum_name) modified, lastmodified = is_modified( local_subalbumpath_abs, False, pyntrest_config.MAX_AGE_OF_NEW_IMAGES_H, pyntrest_config.HIGHLIGHT_NEW_IMAGES, pyntrest_config.IMAGE_FILE_PATTERN) meta_title, meta_description, meta_cover, _, _, _, _ = ( read_optional_album_metadata (local_subalbumpath_abs, pyntrest_config.META_INI_FILE_PATTERN)) local_subalbumcover_abs = None # find and set cover image # If sub album cover manually set.. if meta_cover: # Check for existence.. cover_candidate = path.join(local_subalbumpath_abs, meta_cover) # on windows we also allow uncased matches (or .. lower()) if path.exists(cover_candidate) or path.exists(cover_candidate.lower()): local_subalbumcover_abs = cover_candidate # if no album cover was set manually or if it did not exist... if not local_subalbumcover_abs: # ... get first in folder for cover_candidate in listdir(local_subalbumpath_abs): if pyntrest_config.IMAGE_FILE_PATTERN.match( cover_candidate.lower()): local_subalbumcover_abs = path.join( local_subalbumpath_abs, cover_candidate) break subalbum_webpath = path.join(local_albumpath_rel, subalbum_name) subalbum_webpath = '/' + sub('\\\\', '/', subalbum_webpath) # If still no album cover was found then this is an empty album hence # we need to let the template use a default image if not local_subalbumcover_abs: # setup template context subalbum = Album(title=meta_title, description=meta_description, path=subalbum_webpath, width=pyntrest_config.IMAGE_THUMB_WIDTH, height=pyntrest_config.IMAGE_THUMB_HEIGHT, cover=None, modified=modified, last_modified=lastmodified) else: # otherwise prepare it for static serving image_basename = path.basename(local_subalbumcover_abs) local_thumbnail_folder_abs = path.join ( self.static_athumbs_path, local_albumpath_rel, subalbum_name) thumbnail_webpath = path.join (local_albumpath_rel, subalbum_name, image_basename) target_file = path.join(local_thumbnail_folder_abs, image_basename) mkdirs(local_thumbnail_folder_abs) self.pil_handler.create_album_thumbnail_if_not_present( local_subalbumcover_abs, target_file) # setup template context subalbum = Album( title=meta_title, description=meta_description, path=subalbum_webpath, width=pyntrest_config.IMAGE_THUMB_WIDTH, height=pyntrest_config.IMAGE_THUMB_HEIGHT, cover=thumbnail_webpath, modified=modified, last_modified=lastmodified) # append subalbum to context subalbums.append(subalbum) def process_subimage (self, image_name, local_albumpath_rel, local_albumpath_abs): """Uses the given sub-image path and creates a AlbumImage model for it, including the creation of all necessary thumbnails and the check for whether the file is a Youtube video hook. Finally it appends the AlbumImg to the provided image list.""" local_imagepath_abs = path.join(local_albumpath_abs, image_name) modified, _ = is_modified( local_imagepath_abs, True, pyntrest_config.MAX_AGE_OF_NEW_IMAGES_H, pyntrest_config.HIGHLIGHT_NEW_IMAGES) ####################################################################### if pyntrest_config.IMAGE_FILE_PATTERN.match( local_imagepath_abs.lower()): static_fullsize_path_abs = path.join ( self.static_fullsize_path, local_albumpath_rel, image_name) static_thumb_path_abs = path.join ( self.static_ithumbs_path, local_albumpath_rel, image_name) # copy full size image to static folder if not path.exists(static_fullsize_path_abs): self.pil_handler.copy_with_rotation(local_imagepath_abs, static_fullsize_path_abs) # calculate image size for masonry and copy to static folder width, height = ( self.pil_handler.create_image_thumbnail_if_not_present( static_fullsize_path_abs, static_thumb_path_abs)) latitude, longitude = ( self.pil_handler.get_geo_coordinates( local_imagepath_abs )) geocoord = None if latitude and longitude: #print '{0}: {1}, {2}'.format(image_name, latitude, longitude) geocoord = '{0}, {1}'.format(latitude, longitude) # add image to template context albumimage = AlbumImage(type='img', location=path.join(local_albumpath_rel, image_name), title=image_name, width=width, height=height, modified=modified, geocoord=geocoord) return albumimage ####################################################################### elif pyntrest_config.YOUTUBE_INI_FILE_PATTERN.match( local_imagepath_abs): youtube_id = read_youtube_ini_file (local_imagepath_abs) # .75 is the fixed Youtube thumbnail width to height ratio thumb_height = int( float (pyntrest_config.IMAGE_THUMB_WIDTH) * 0.75) albumimage = AlbumImage(type='you', location=path.join( local_albumpath_rel, image_name), title=image_name, width=pyntrest_config.IMAGE_THUMB_WIDTH, height=thumb_height, youtubeid=youtube_id, modified=modified) return albumimage ####################################################################### elif (pyntrest_config.TEXT_MD_FILE_PATTERN.match( local_imagepath_abs.lower()) and not (pyntrest_config.INTRO_MD_FILE_PATTERN.match( local_imagepath_abs.lower()))): html_content, title = get_html_content(local_imagepath_abs) divid="".join(choice('abcdefghijklmnopqrstuvwxyz') for _ in range(16)) albumimage = AlbumImage(type='txt', location=path.join( local_albumpath_rel, image_name), title=title, width=pyntrest_config.IMAGE_THUMB_WIDTH, height=pyntrest_config.IMAGE_THUMB_HEIGHT/2, modified=modified, text_content=html_content, divid=divid) return albumimage def upgrade_static_files(self): """This method allows for updating a selection of static files with corresponding files residing in a hidden .pyntrest folder in your main image folder. This comes in handy when you want to update the CSS or favicon without touching the core implementation.""" pyn_config_folder = path.join(self.main_images_path, '.pyntrest') # these files can be overridden changeable_files = [ path.join('res', 'favicon.png'), path.join('res', 'favicon-apple.png'), path.join('css', 'pyntrest-main.css'), path.join('index.html'), path.join('bookify.html'), ] for ch_file in changeable_files: # the changeable file at its final destination if 'index' in ch_file: exis_file = path.join(TEMPLATE_DIRS[0], 'pyntrest', ch_file) else: exis_file = path.join(self.static_path, ch_file) # the candidate file from the main images folder cand_file = path.join(pyn_config_folder, ch_file) if not file_exists(exis_file) and not file_exists(cand_file): # no target file and no custom file --> copy from default print ('Creating file \'{}\' from default.'.format(exis_file)) copyfile(exis_file + '.default', exis_file) elif not file_exists(exis_file) and file_exists(cand_file): # no target file but custom file --> copy from custom print ('Creating file \'{}\' from version at \'{}\'.' .format(exis_file, cand_file)) copyfile(cand_file, exis_file) #print 'staticfile candidate = {}'.format(cand_file) if not file_exists(cand_file): continue # nothing to compare # get modified / created dates efile_ts = max( path.getctime(exis_file), path.getmtime(exis_file)) cfile_ts = max( path.getctime(cand_file), path.getmtime(cand_file)) if cfile_ts >= efile_ts: print ( 'Updating file \'{}\' with newer version at \'{}\' [{} >> {}].' .format(ch_file, cand_file, efile_ts, cfile_ts)) copyfile(cand_file, exis_file) else: pass