示例#1
0
    def failed(self, error=None, traceback=None):
        self.status = 'F'
        self.finished_at = timezone.now()
        self.save()

        if error:
            logger.error(error)
示例#2
0
 def clear_dir(self, path):
     for the_file in os.listdir(path):
         file_path = os.path.join(path, the_file)
         try:
             if os.path.isfile(file_path):
                 os.remove(file_path)
             elif os.path.isdir(file_path):
                 shutil.rmtree(file_path)
         except Exception as e:
             logger.error(e)
示例#3
0
 def __process_task(self, task):
     try:
         logger.info(f'Running task: {task.type} - {task.subject_id}')
         task.start()
         self.runner(task.subject_id)
         task.complete()
     except Exception:
         logger.error(
             f'Error processing task: {task.type} - {task.subject_id}')
         traceback.print_exc()
         task.failed()
示例#4
0
def generate_jpeg(path):
    logger.debug(f'Generating JPEG for raw file {path}')
    basename = os.path.basename(path)
    temp_dir = tempfile.mkdtemp()
    temp_input_path = Path(temp_dir) / basename
    shutil.copyfile(path, temp_input_path)

    valid_image = False
    process_params = None
    external_version = None

    # Handle Canon's CR3 format since their thumbnails are proprietary.
    mimetype = get_mimetype(temp_input_path)
    if mimetype == 'image/x-canon-cr3':
        logger.debug('File type detected as Canon Raw v3')
        subprocess.Popen([
            'exiftool', '-b', '-JpgFromRaw', '-w', 'jpg', '-ext', 'CR3',
            temp_input_path, '-execute', '-tagsfromfile', temp_input_path,
            '-ext', 'jpg', Path(temp_dir)],
            stdout=subprocess.PIPE,
            stdin=subprocess.PIPE,
            stderr=subprocess.PIPE).communicate()
        exiftool_output = __get_exiftool_image(temp_dir, basename)
        # Clean up the original file without tags.
        if 'original' in exiftool_output:
            os.remove(exiftool_output['original'])
        # Set the input file.
        if 'output' in exiftool_output:
            temp_output_path = exiftool_output['output']
        else:
            temp_output_path = None
        process_params = 'exiftool -b -JpgFromRaw'
        external_version = __exiftool_version()
    elif mimetype in ['image/heif', 'image/heic']:
        logger.debug('File type detected as HIEF/HEIC')
        temp_output_path = Path(temp_dir) / 'out.jpg'
        subprocess.run(['heif-convert', '-q', '90', temp_input_path, temp_output_path])
        process_params = 'heif-convert -q 90'
        external_version = __heif_convert_version()
    else:
        logger.debug('Attempting to extract JPEG using dcraw')
        # Try to extract the JPEG that might be inside the raw file
        subprocess.run(['dcraw', '-e', temp_input_path])
        temp_output_path = __get_generated_image(temp_dir, basename)
        process_params = 'dcraw -e'
        external_version = __dcraw_version()

    # Check the JPEGs dimensions are close enough to the raw's dimensions
    if temp_output_path:
        if __has_acceptable_dimensions(temp_input_path, temp_output_path):
            logger.debug('JPEG file looks good so far')
            valid_image = True
        else:
            __delete_file_silently(temp_output_path)

    # Next try to use embedded profile to generate an image
    if not valid_image:
        logger.debug('Attempting to generate JPEG with dcraw using embedded color profile')
        subprocess.run(['dcraw', '-p embed', temp_input_path])
        temp_output_path = __get_generated_image(temp_dir, basename)

        if temp_output_path:
            if __has_acceptable_dimensions(temp_input_path, temp_output_path):
                logger.debug('JPEG file looks good so far')
                valid_image = True
                process_params = 'dcraw -p embed'
            else:
                __delete_file_silently(temp_output_path)

    # Finally try to use the embedded whitebalance to generate an image
    if not valid_image:
        logger.debug('Attempting to generate JPEG with dcraw using embedded white balance')
        subprocess.run(['dcraw', '-w', temp_input_path])
        temp_output_path = __get_generated_image(temp_dir, basename)

        if temp_output_path:
            if __has_acceptable_dimensions(temp_input_path, temp_output_path, True):
                logger.debug('JPEG file looks good so far')
                valid_image = True
                process_params = 'dcraw -w'
            else:
                __delete_file_silently(temp_output_path)

    # If extracted image isn't a JPEG then we need to convert it
    if valid_image:
        valid_image = identified_as_jpeg(temp_output_path)

        if not valid_image:
            logger.debug('JPEG didn\'t pass test, attempting bitmap conversion')
            jpeg_path = tempfile.mktemp()
            bitmap_to_jpeg(temp_output_path, jpeg_path)

            if identified_as_jpeg(jpeg_path):
                logger.debug('JPEG file now passes test')
                temp_output_path = jpeg_path
                valid_image = True

    # Move the outputted file to a new temporary location
    if valid_image:
        logger.debug('I\'m happy with the JPEG so moving it to a new location')
        final_path = tempfile.mktemp()
        os.rename(temp_output_path, final_path)

    # Delete the temporary working directory
    logger.debug('Deleting temporary files')
    shutil.rmtree(temp_dir)

    if valid_image:
        logger.debug(f'Returning info about JPEG which is temporarily located here: {final_path}')
        return (final_path, RAW_PROCESS_VERSION, process_params, external_version)

    logger.error('Couldn\'t make JPEG from raw file')
    return (None, RAW_PROCESS_VERSION, None, None)
示例#5
0
def record_photo(path, library, inotify_event_type=None):
    logger.info(f'Recording photo {path}')

    mimetype = get_mimetype(path)

    if not imghdr.what(
            path) and not mimetype in MIMETYPE_WHITELIST and subprocess.run(
                ['dcraw', '-i', path]).returncode:
        logger.error(f'File is not a supported type: {path} ({mimetype})')
        return None

    if type(library) == Library:
        library_id = library.id
    else:
        library_id = str(library)
    try:
        photo_file = PhotoFile.objects.get(path=path)
    except PhotoFile.DoesNotExist:
        photo_file = PhotoFile()

    if inotify_event_type in ['DELETE', 'MOVED_FROM']:
        if PhotoFile.objects.filter(path=path).exists():
            return delete_photo_record(photo_file)
        else:
            return True

    file_modified_at = datetime.fromtimestamp(os.stat(path).st_mtime, tz=utc)

    if photo_file and photo_file.file_modified_at == file_modified_at:
        return True

    metadata = PhotoMetadata(path)
    date_taken = None
    possible_date_keys = [
        'Create Date', 'Date/Time Original', 'Date Time Original', 'Date/Time',
        'Date Time', 'GPS Date/Time', 'File Modification Date/Time'
    ]
    for date_key in possible_date_keys:
        date_taken = parse_datetime(metadata.get(date_key))
        if date_taken:
            break
    # If EXIF data not found.
    date_taken = date_taken or datetime.strptime(
        time.ctime(os.path.getctime(path)), "%a %b %d %H:%M:%S %Y")

    camera = None
    camera_make = metadata.get('Make', '')[:Camera.make.field.max_length]
    camera_model = metadata.get('Camera Model Name', '')
    if camera_model:
        camera_model = camera_model.replace(camera_make, '').strip()
    camera_model = camera_model[:Camera.model.field.max_length]
    if camera_make and camera_model:
        try:
            camera = Camera.objects.get(library_id=library_id,
                                        make=camera_make,
                                        model=camera_model)
            if date_taken < camera.earliest_photo:
                camera.earliest_photo = date_taken
                camera.save()
            if date_taken > camera.latest_photo:
                camera.latest_photo = date_taken
                camera.save()
        except Camera.DoesNotExist:
            camera = Camera(library_id=library_id,
                            make=camera_make,
                            model=camera_model,
                            earliest_photo=date_taken,
                            latest_photo=date_taken)
            camera.save()

    lens = None
    lens_name = metadata.get('Lens ID')
    if lens_name:
        try:
            lens = Lens.objects.get(name=lens_name)
            if date_taken < lens.earliest_photo:
                lens.earliest_photo = date_taken
                lens.save()
            if date_taken > lens.latest_photo:
                lens.latest_photo = date_taken
                lens.save()
        except Lens.DoesNotExist:
            lens = Lens(library_id=library_id,
                        name=lens_name,
                        earliest_photo=date_taken,
                        latest_photo=date_taken)
            lens.save()

    photo = None
    if date_taken:
        try:
            # TODO: Match on file number/file name as well
            photo = Photo.objects.get(taken_at=date_taken)
        except Photo.DoesNotExist:
            pass

    latitude = None
    longitude = None
    if metadata.get('GPS Position'):
        latitude, longitude = parse_gps_location(metadata.get('GPS Position'))

    iso_speed = None
    if metadata.get('ISO'):
        try:
            iso_speed = int(re.search(r'[0-9]+', metadata.get('ISO')).group(0))
        except AttributeError:
            pass
    if not photo:
        # Save Photo
        aperture = None
        aperturestr = metadata.get('Aperture')
        if aperturestr:
            try:
                aperture = Decimal(aperturestr)
                if aperture.is_infinite():
                    aperture = None
            except:
                pass

        photo = Photo(
            library_id=library_id,
            taken_at=date_taken,
            taken_by=metadata.get(
                'Artist', '')[:Photo.taken_by.field.max_length] or None,
            aperture=aperture,
            exposure=metadata.get(
                'Exposure Time', '')[:Photo.exposure.field.max_length] or None,
            iso_speed=iso_speed,
            focal_length=metadata.get('Focal Length')
            and metadata.get('Focal Length').split(' ', 1)[0] or None,
            flash=metadata.get('Flash')
            and 'on' in metadata.get('Flash').lower() or False,
            metering_mode=metadata.get(
                'Metering Mode', '')[:Photo.metering_mode.field.max_length]
            or None,
            drive_mode=metadata.get(
                'Drive Mode', '')[:Photo.drive_mode.field.max_length] or None,
            shooting_mode=metadata.get(
                'Shooting Mode', '')[:Photo.shooting_mode.field.max_length]
            or None,
            camera=camera,
            lens=lens,
            latitude=latitude,
            longitude=longitude,
            altitude=metadata.get('GPS Altitude')
            and metadata.get('GPS Altitude').split(' ')[0],
            star_rating=metadata.get('Rating'))
        photo.save()

        for subject in metadata.get('Subject', '').split(','):
            subject = subject.strip()
            if subject:
                tag, _ = Tag.objects.get_or_create(library_id=library_id,
                                                   name=subject,
                                                   type="G")
                PhotoTag.objects.create(photo=photo, tag=tag, confidence=1.0)
    else:
        for photo_file in photo.files.all():
            if not os.path.exists(photo_file.path):
                photo_file.delete()

    width = metadata.get('Image Width')
    height = metadata.get('Image Height')
    if metadata.get('Orientation') in [
            'Rotate 90 CW', 'Rotate 270 CCW', 'Rotate 90 CCW', 'Rotate 270 CW'
    ]:
        old_width = width
        width = height
        height = old_width

    # Save PhotoFile
    photo_file.photo = photo
    photo_file.path = path
    photo_file.width = width
    photo_file.height = height
    photo_file.mimetype = mimetype
    photo_file.file_modified_at = file_modified_at
    photo_file.bytes = os.stat(path).st_size
    photo_file.preferred = False  # TODO
    photo_file.save()

    # Create task to ensure JPEG version of file exists (used for thumbnailing, analysing etc.)
    Task(type='ensure_raw_processed',
         subject_id=photo.id,
         complete_with_children=True,
         library=photo.library).save()

    return photo