def convert_to_jp2(self) -> None: try: if not self.image_data.valid_image_ext(): raise AviJp2ProcessorError('Source image is not a .tiff or .tif') kdu_args = self.__calculate_kdu_options() + self.__calculate_kdu_recipe() input_file = str(self.image_data.image_src_path) if self.image_data.src_quality == 'color': self.logger.debug('Adding icc profile to image') input_file = self.convert_icc_profile() self.logger.debug('Successfully added icc profile') self.logger.debug('Pre validating image at {}'.format(input_file)) try: validation.check_image_suitable_for_jp2_conversion( input_file, require_icc_profile_for_colour=True, require_icc_profile_for_greyscale=False) except ValidationError as v_e: msg = f'ValidationError: {v_e}' raise AviJp2ProcessorError(msg) from v_e self.logger.debug('image {} is able to be converted to jp2!'.format(input_file)) with Image.open(input_file) as input_pil: if input_pil.mode == 'RGBA': if kakadu.ALPHA_OPTION not in kdu_args: kdu_args += [kakadu.ALPHA_OPTION] self.logger.debug('Kakadu args are {}'.format(kdu_args)) self.logger.debug('Preparing to output jp2...') try: self.kakadu.kdu_compress(input_file, self.destination_file, kakadu_options=kdu_args) except (KakaduError, OSError) as kdu_e: msg = f'{kdu_e.__class__.__name__} {kdu_e}' raise AviJp2ProcessorError(msg) from kdu_e finally: # Deletes the tmp file created from the convert_icc_profile method. if Path(input_file).exists() and input_file != str(self.image_data.image_src_path): self.logger.debug('Removing {}'.format(input_file)) os.unlink(input_file) self.logger.debug('Successfully converted to jp2!') self.__set_success_result() except AviJp2ProcessorError as avi_ex: msg = str(avi_ex) self.__set_error_result(msg) self.logger.error('Error occured processing file for Jp2 conversion!') self.logger.error('Check result and logs for more details.')
def test_creates_correct_files_greyscale(self): validation.check_image_suitable_for_jp2_conversion(filepaths.GREYSCALE_TIF, require_icc_profile_for_greyscale=True) with temporary_folder() as output_folder: get_derivatives_generator().generate_derivatives_from_tiff(filepaths.GREYSCALE_TIF, output_folder, check_lossless=True, save_embedded_metadata=False) jpg_file = os.path.join(output_folder, 'full.jpg') jp2_file = os.path.join(output_folder, 'full_lossless.jp2') assert os.path.isfile(jpg_file) assert os.path.isfile(jp2_file) assert len(os.listdir(output_folder)) == 2 assert image_files_match(jpg_file, filepaths.RESIZED_JPG_FROM_GREYSCALE_TIF) assert image_files_match(jp2_file, filepaths.LOSSLESS_JP2_FROM_GREYSCALE_TIF_XMP)
def generate_derivatives_from_jpg(self, jpg_filepath, output_folder, save_embedded_metadata=True, check_lossless=True): """ Extracts the embedded metadata, creates a copy of the JPEG file and a validated JPEG2000 file. Stores all in the given folder. :param jpg_filepath: The path to the source JPEG file. :param output_folder: The folder where the derivatives will be stored :param save_embedded_metadata: If true, metadata will be extracted from the image file and preserved in a separate xml file :param check_lossless: If true, check the created JPEG2000 file is visually identical to the TIFF created from the source file :return: filepaths of created files """ self.log.debug("Processing {0}".format(jpg_filepath)) self.log.info( "There may be some loss in converting from jpg to jpg2000, as jpg compression is lossy. " "The lossless check is against the tiff created from the jpg") source_file_name = os.path.basename(jpg_filepath) validation.check_image_suitable_for_jp2_conversion( jpg_filepath, require_icc_profile_for_colour=self.require_icc_profile_for_colour, require_icc_profile_for_greyscale=self. require_icc_profile_for_greyscale) output_jpg_filepath = os.path.join( output_folder, self._get_filename(DEFAULT_JPG_FILENAME, source_file_name)) shutil.copy(jpg_filepath, output_jpg_filepath) generated_files = [output_jpg_filepath] if save_embedded_metadata: embedded_metadata_file_path = os.path.join( output_folder, self._get_filename(DEFAULT_EMBEDDED_METADATA_FILENAME, source_file_name)) self.converter.extract_xmp_to_sidecar_file( jpg_filepath, embedded_metadata_file_path) self.log.debug('Extracted metadata file {0} generated'.format( embedded_metadata_file_path)) generated_files += [embedded_metadata_file_path] with tempfile.NamedTemporaryFile( prefix='image-processing_', suffix='.tif') as scratch_tiff_file_obj: scratch_tiff_filepath = scratch_tiff_file_obj.name self.converter.convert_to_tiff(jpg_filepath, scratch_tiff_filepath) validation.check_colour_profiles_match(jpg_filepath, scratch_tiff_filepath) lossless_filepath = os.path.join( output_folder, self._get_filename(DEFAULT_LOSSLESS_JP2_FILENAME, source_file_name)) self.generate_jp2_from_tiff(scratch_tiff_filepath, lossless_filepath) self.validate_jp2_conversion(scratch_tiff_filepath, lossless_filepath, check_lossless=check_lossless) generated_files.append(lossless_filepath) self.log.debug( "Successfully generated derivatives for {0} in {1}".format( jpg_filepath, output_folder)) return generated_files
def generate_derivatives_from_tiff(self, tiff_filepath, output_folder, include_tiff=False, save_embedded_metadata=True, create_jpg_as_thumbnail=True, check_lossless=True): """ Extracts the embedded metadata, creates a JPEG file and a validated JPEG2000 file. Stores all in the given folder. :param create_jpg_as_thumbnail: create the JPG as a resized thumbnail, not a high quality image. Parameters for resize and quality are set on a class level :param tiff_filepath: :param output_folder: the folder where the related dc.xml will be stored :param include_tiff: Include copy of source tiff file in derivatives :param save_embedded_metadata: If true, metadata will be extracted from the image file and preserved in a separate xml file :param check_lossless: If true, check the created jpg2000 file is visually identical to the source file :return: filepaths of created files """ self.log.debug("Processing {0}".format(tiff_filepath)) source_file_name = os.path.basename(tiff_filepath) validation.check_image_suitable_for_jp2_conversion( tiff_filepath, require_icc_profile_for_colour=self.require_icc_profile_for_colour, require_icc_profile_for_greyscale=self. require_icc_profile_for_greyscale) with Image.open(tiff_filepath) as tiff_pil: if tiff_pil.mode == 'RGBA': # some RGBA tiffs don't convert properly back from jp2 - kakadu warns about unassociated alpha channels check_lossless = True with tempfile.NamedTemporaryFile(prefix='image-processing_', suffix='.tif') as temp_tiff_file_obj: # only work from a temporary file if we need to - e.g. if the tiff filepath is invalid, # or if we need to normalise the tiff. Otherwise just use the original tiff temp_tiff_filepath = temp_tiff_file_obj.name if os.path.splitext(tiff_filepath)[1].lower() not in [ '.tif', '.tiff' ]: shutil.copy(tiff_filepath, temp_tiff_filepath) normalised_tiff_filepath = temp_tiff_filepath else: normalised_tiff_filepath = tiff_filepath jpeg_filepath = os.path.join( output_folder, self._get_filename(DEFAULT_JPG_FILENAME, source_file_name)) jpg_quality = None if create_jpg_as_thumbnail else self.jpg_high_quality_value jpg_resize = self.jpg_thumbnail_resize_value if create_jpg_as_thumbnail else None self.converter.convert_to_jpg(normalised_tiff_filepath, jpeg_filepath, quality=jpg_quality, resize=jpg_resize) self.log.debug('jpeg file {0} generated'.format(jpeg_filepath)) generated_files = [jpeg_filepath] if save_embedded_metadata: embedded_metadata_file_path = os.path.join( output_folder, self._get_filename(DEFAULT_EMBEDDED_METADATA_FILENAME, source_file_name)) self.converter.extract_xmp_to_sidecar_file( tiff_filepath, embedded_metadata_file_path) self.log.debug('Extracted metadata file {0} generated'.format( embedded_metadata_file_path)) generated_files += [embedded_metadata_file_path] if include_tiff: output_tiff_filepath = os.path.join( output_folder, self._get_filename(DEFAULT_TIFF_FILENAME, source_file_name)) shutil.copy(tiff_filepath, output_tiff_filepath) generated_files += [output_tiff_filepath] lossless_filepath = os.path.join( output_folder, self._get_filename(DEFAULT_LOSSLESS_JP2_FILENAME, source_file_name)) self.generate_jp2_from_tiff(normalised_tiff_filepath, lossless_filepath) self.validate_jp2_conversion(normalised_tiff_filepath, lossless_filepath, check_lossless=check_lossless) generated_files.append(lossless_filepath) self.log.debug( "Successfully generated derivatives for {0} in {1}".format( tiff_filepath, output_folder)) return generated_files