X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=mediagoblin%2Fmedia_types%2Fimage%2Fprocessing.py;h=e6a34ca004e44a3f011f3fcdcba96b1d27629948;hb=45ab3e07ef26199572207f5d826e6d912eb5b336;hp=9eb8fa1669cfccca9ab858ce0fa9fc37ca29df22;hpb=e8e444a85e2b16583587ff5a074f0ab1ffbaca85;p=mediagoblin.git diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py index 9eb8fa16..e6a34ca0 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -1,5 +1,5 @@ # GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -16,87 +16,117 @@ import Image import os +import logging from mediagoblin import mg_globals as mgg +from mediagoblin.decorators import get_workbench from mediagoblin.processing import BadMediaFail, \ - create_pub_filepath, THUMB_SIZE, MEDIUM_SIZE -from mediagoblin.media_types.image.EXIF import process_file -from mediagoblin.tools.translate import pass_to_ugettext as _ + create_pub_filepath, FilenameBuilder +from mediagoblin.tools.exif import exif_fix_image_orientation, \ + extract_exif, clean_exif, get_gps_data, get_useful, \ + exif_image_needs_rotation -def process_image(entry): +_log = logging.getLogger(__name__) + + +def resize_image(entry, filename, new_path, exif_tags, workdir, new_size, + size_limits=(0, 0)): """ - Code to process an image + Store a resized version of an image and return its pathname. + + Arguments: + entry -- the entry for the image to resize + filename -- the filename of the original image being resized + new_path -- public file path for the new resized image + exif_tags -- EXIF data for the original image + workdir -- directory path for storing converted image files + new_size -- 2-tuple size for the resized image + """ + try: + resized = Image.open(filename) + except IOError: + raise BadMediaFail() + resized = exif_fix_image_orientation(resized, exif_tags) # Fix orientation + resized.thumbnail(new_size, Image.ANTIALIAS) + + # Copy the new file to the conversion subdir, then remotely. + tmp_resized_filename = os.path.join(workdir, new_path[-1]) + with file(tmp_resized_filename, 'w') as resized_file: + resized.save(resized_file) + mgg.public_store.copy_local_to_storage(tmp_resized_filename, new_path) + + +SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg'] + + +def sniff_handler(media_file, **kw): + if kw.get('media') is not None: # That's a double negative! + name, ext = os.path.splitext(kw['media'].filename) + clean_ext = ext[1:].lower() # Strip the . from ext and make lowercase + + if clean_ext in SUPPORTED_FILETYPES: + _log.info('Found file extension in supported filetypes') + return True + else: + _log.debug('Media present, extension not found in {0}'.format( + SUPPORTED_FILETYPES)) + else: + _log.warning('Need additional information (keyword argument \'media\')' + ' to be able to handle sniffing') + + return False + + +@get_workbench +def process_image(entry, workbench=None): + """Code to process an image. Will be run by celery. + + A Workbench() represents a local tempory dir. It is automatically + cleaned up when this function exits. """ - workbench = mgg.workbench_manager.create_workbench() # Conversions subdirectory to avoid collisions conversions_subdir = os.path.join( workbench.dir, 'conversions') os.mkdir(conversions_subdir) - queued_filepath = entry.queued_media_file queued_filename = workbench.localized_file( mgg.queue_store, queued_filepath, 'source') - - filename_bits = os.path.splitext(queued_filename) - basename = os.path.split(filename_bits[0])[1] - extension = filename_bits[1].lower() + name_builder = FilenameBuilder(queued_filename) # EXIF extraction exif_tags = extract_exif(queued_filename) gps_data = get_gps_data(exif_tags) - try: - thumb = Image.open(queued_filename) - except IOError: - raise BadMediaFail() - - thumb = exif_fix_image_orientation(thumb, exif_tags) - - thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS) - - # Copy the thumb to the conversion subdir, then remotely. - thumb_filename = 'thumbnail' + extension - thumb_filepath = create_pub_filepath(entry, thumb_filename) - tmp_thumb_filename = os.path.join( - conversions_subdir, thumb_filename) - with file(tmp_thumb_filename, 'w') as thumb_file: - thumb.save(thumb_file) - mgg.public_store.copy_local_to_storage( - tmp_thumb_filename, thumb_filepath) + # Always create a small thumbnail + thumb_filepath = create_pub_filepath( + entry, name_builder.fill('{basename}.thumbnail{ext}')) + resize_image(entry, queued_filename, thumb_filepath, + exif_tags, conversions_subdir, + (mgg.global_config['media:thumb']['max_width'], + mgg.global_config['media:thumb']['max_height'])) # If the size of the original file exceeds the specified size of a `medium` - # file, a `medium.jpg` files is created and later associated with the media + # file, a `.medium.jpg` files is created and later associated with the media # entry. medium = Image.open(queued_filename) - # Fox orientation - medium = exif_fix_image_orientation(medium, exif_tags) - - if medium.size[0] > MEDIUM_SIZE[0] or medium.size[1] > MEDIUM_SIZE[1]: - medium.thumbnail(MEDIUM_SIZE, Image.ANTIALIAS) - - medium_filename = 'medium' + extension - medium_filepath = create_pub_filepath(entry, medium_filename) - tmp_medium_filename = os.path.join( - conversions_subdir, medium_filename) - - with file(tmp_medium_filename, 'w') as medium_file: - medium.save(medium_file) - - mgg.public_store.copy_local_to_storage( - tmp_medium_filename, medium_filepath) - - # we have to re-read because unlike PIL, not everything reads - # things in string representation :) - queued_file = file(queued_filename, 'rb') - - with queued_file: - #create_pub_filepath(entry, queued_filepath[-1]) - original_filepath = create_pub_filepath(entry, basename + extension) - - with mgg.public_store.get_file(original_filepath, 'wb') \ - as original_file: - original_file.write(queued_file.read()) + if medium.size[0] > mgg.global_config['media:medium']['max_width'] \ + or medium.size[1] > mgg.global_config['media:medium']['max_height'] \ + or exif_image_needs_rotation(exif_tags): + medium_filepath = create_pub_filepath( + entry, name_builder.fill('{basename}.medium{ext}')) + resize_image( + entry, queued_filename, medium_filepath, + exif_tags, conversions_subdir, + (mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height'])) + else: + medium_filepath = None + + # Copy our queued local workbench to its final destination + original_filepath = create_pub_filepath( + entry, name_builder.fill('{basename}{ext}')) + mgg.public_store.copy_local_to_storage(queued_filename, original_filepath) # Remove queued media file from storage and database mgg.queue_store.delete_file(queued_filepath) @@ -104,116 +134,21 @@ def process_image(entry): # Insert media file information into database media_files_dict = entry.setdefault('media_files', {}) - media_files_dict['thumb'] = thumb_filepath - media_files_dict['original'] = original_filepath - media_files_dict['medium'] = medium_filepath + media_files_dict[u'thumb'] = thumb_filepath + media_files_dict[u'original'] = original_filepath + if medium_filepath: + media_files_dict[u'medium'] = medium_filepath # Insert exif data into database - media_data = entry.setdefault('media_data', {}) - media_data['exif'] = clean_exif(exif_tags) - media_data['gps'] = gps_data - - # clean up workbench - workbench.destroy_self() - -def exif_fix_image_orientation(im, exif_tags): - """ - Translate any EXIF orientation to raw orientation - - Cons: - - REDUCES IMAGE QUALITY by recompressig it - - Pros: - - Cures my neck pain - """ - # Rotate image - if 'Image Orientation' in exif_tags: - rotation_map = { - 3: 180, - 6: 270, - 8: 90} - orientation = exif_tags['Image Orientation'].values[0] - if orientation in rotation_map.keys(): - im = im.rotate( - rotation_map[orientation]) - - return im - -def extract_exif(filename): - """ - Returns EXIF tags found in file at ``filename`` - """ - exif_tags = {} - - try: - image = open(filename) - exif_tags = process_file(image) - except IOError: - BadMediaFail(_('Could not read the image file.')) - - return exif_tags - -def clean_exif(exif): - # Clean the result from anything the database cannot handle + exif_all = clean_exif(exif_tags) - # Discard any JPEG thumbnail, for database compatibility - # and that I cannot see a case when we would use it. - # It takes up some space too. - disabled_tags = [ - 'Thumbnail JPEGInterchangeFormatLength', - 'JPEGThumbnail', - 'Thumbnail JPEGInterchangeFormat'] + if len(exif_all): + entry.media_data_init(exif_all=exif_all) - clean_exif = {} - - for key, value in exif.items(): - if not key in disabled_tags: - clean_exif[key] = str(value) - - return clean_exif - - -def get_gps_data(exif): - """ - Processes EXIF data returned by EXIF.py - """ - if not 'Image GPSInfo' in exif: - return False - - gps_data = {} - - try: - dms_data = { - 'latitude': exif['GPS GPSLatitude'], - 'longitude': exif['GPS GPSLongitude']} - - for key, dat in dms_data.items(): - gps_data[key] = ( - lambda v: - float(v[0].num) / float(v[0].den) \ - + (float(v[1].num) / float(v[1].den) / 60 )\ - + (float(v[2].num) / float(v[2].den) / (60 * 60)) - )(dat.values) - except KeyError: - pass - - try: - gps_data['direction'] = ( - lambda d: - float(d.num) / float(d.den) - )(exif['GPS GPSImgDirection'].values[0]) - except KeyError: - pass - - try: - gps_data['altitude'] = ( - lambda a: - float(a.num) / float(a.den) - )(exif['GPS GPSAltitude'].values[0]) - except KeyError: - pass - - return gps_data + if len(gps_data): + for key in list(gps_data.keys()): + gps_data['gps_' + key] = gps_data.pop(key) + entry.media_data_init(**gps_data) if __name__ == '__main__': @@ -224,9 +159,8 @@ if __name__ == '__main__': result = extract_exif(sys.argv[1]) gps = get_gps_data(result) - - import pdb - pdb.set_trace() + clean = clean_exif(result) + useful = get_useful(clean) print pp.pprint( - result) + clean)