def test_can_save_stretched_editable_image_as_png_to_disk(self):
        dummy_png = self.img_proc_pngfile_paths()[0]
        buffered_image = img_proc.read_editable_image_from_disk(dummy_png)
        assert isinstance(buffered_image, java.awt.image.BufferedImage)
        font = img_proc.create_font(self._allowed_font_names[0], 14.0, bold=False, italic=False)
        helvetica_font_value = 3
        helvetica_font =  img_proc.create_font(self._allowed_font_names[helvetica_font_value], 14.0, bold=False, italic=False)
      
        
        # check whether the left-outline of letter "H" in "Hello world" is NOT present in the image!
        for i in range(0, 8):
            rgb = buffered_image.getRGB(11, i)
            red = rgb >> 16 & int("0x000000FF", 16)
            green = rgb >> 8 & int("0x000000FF", 16)
            blue = rgb & int("0x000000FF", 16)
            alpha = (rgb>>24) & 0xff
            assert red == 0 and green == 0 and blue == 0 and alpha == 0
        buffered_image = img_proc.add_text_to_image(buffered_image, "Hello world!", helvetica_font, Color.BLUE, 10.0, 10.0, 10.0, 95, 30)
        #buffered_image = img_proc.add_text_to_image(buffered_image, "Franz ist hier!?", marker_font, Color.BLUE,10.0, 20.0)
        #buffered_image = img_proc.add_text_to_image(buffered_image, "iloveKF", donegal_font, Color.GREEN,10.0, 32.0)

        #TODO make new Test for stretched pixels
        output_path = self.img_proc_pngfile_output_path("test_stretched.png")
        try:
            img_proc.save_editable_image_as_png_to_disk(buffered_image, output_path, overwrite=True)
            assert os.path.isfile(output_path)
            assert imghdr.what(output_path) == 'png'
            # Reload the image from disk now and check if left-outline of letter "H" is still present!
            new_buffered_image = img_proc.read_editable_image_from_disk(output_path)
            assert isinstance(new_buffered_image, java.awt.image.BufferedImage)
            #TODO make the same check as before you reload the image from disk
        except Exception, e:
            raise e
    def convert(self, progress_bar = None):
        all_used_resources = []
        unconverted_media_resources = []
        converted_media_resources_paths = set()

        # TODO: remove this block later {
        for scratch_md5_name, src_path in self.scratch_project.md5_to_resource_path_map.iteritems():
            if scratch_md5_name in self.scratch_project.unused_resource_names:
                log.info("Ignoring unused resource file: %s", src_path)
                if progress_bar != None:
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                continue
        # }

        for scratch_object in self.scratch_project.objects:
            project_base_path = self.scratch_project.project_base_path

            for costume_info in scratch_object.get_costumes():
                costume_file_name = costume_info[JsonKeys.COSTUME_MD5]
                costume_src_path = os.path.join(project_base_path, costume_file_name)
                file_ext = os.path.splitext(costume_file_name)[1].lower()

                if not os.path.exists(costume_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    costume_src_path = self.scratch_project.md5_to_resource_path_map[costume_file_name]

                assert os.path.exists(costume_src_path), "Not existing: {}".format(costume_src_path)
                assert file_ext in {".png", ".svg", ".jpg", ".gif"}, \
                       "Unsupported image file extension: %s" % costume_src_path
                ispng = file_ext == ".png"
                is_unconverted = file_ext == ".svg"

                resource_info = {
                    "scratch_md5_name": costume_file_name,
                    "src_path": costume_src_path,
                    "dest_path": self.images_path,
                    "media_type": MediaType.UNCONVERTED_SVG if is_unconverted else MediaType.IMAGE,
                    "info": costume_info
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and costume_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    #TODO: background gets scaled too, shouldn't be the case
                    if ispng:
                        isStageCostume = scratch_object.name == "Stage"
                        self.convertPNG(isStageCostume, costume_info,costume_src_path, costume_src_path)
                    converted_media_resources_paths.add(costume_src_path)
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)


            for sound_info in scratch_object.get_sounds():
                sound_file_name = sound_info[JsonKeys.SOUND_MD5]
                sound_src_path = os.path.join(project_base_path, sound_file_name)
                file_ext = os.path.splitext(sound_file_name)[1].lower()

                if not os.path.exists(sound_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    sound_src_path = self.scratch_project.md5_to_resource_path_map[sound_file_name]

                assert os.path.exists(sound_src_path), "Not existing: {}".format(sound_src_path)
                assert file_ext in {".wav", ".mp3"}, "Unsupported sound file extension: %s" % sound_src_path

                is_unconverted = file_ext == ".wav" and not wavconverter.is_android_compatible_wav(sound_src_path)
                resource_info = {
                    "scratch_md5_name": sound_file_name,
                    "src_path": sound_src_path,
                    "dest_path": self.sounds_path,
                    "media_type": MediaType.UNCONVERTED_WAV if is_unconverted else MediaType.AUDIO,
                    "info": sound_info
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and sound_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                    converted_media_resources_paths.add(sound_src_path)


        # schedule concurrent conversions (one conversion per thread)
        new_src_paths = {}
        resource_index = 0
        num_total_resources = len(unconverted_media_resources)
        reference_index = 0
        media_srces = []
        while resource_index < num_total_resources:
            num_next_resources = min(MAX_CONCURRENT_THREADS, (num_total_resources - resource_index))
            next_resources_end_index = resource_index + num_next_resources
            threads = []
            for index in range(resource_index, next_resources_end_index):
                assert index == reference_index
                reference_index += 1
                data = unconverted_media_resources[index]
                if data["src_path"] in media_srces:
                    continue
                else:
                    media_srces.append(data["src_path"])
                kwargs = {
                    "data": data,
                    "new_src_paths": new_src_paths,
                    "progress_bar": progress_bar
                }
                threads.append(_MediaResourceConverterThread(kwargs=kwargs))
            for thread in threads: thread.start()
            for thread in threads: thread.join()
            resource_index = next_resources_end_index
        assert reference_index == resource_index and reference_index == num_total_resources

        converted_media_files_to_be_removed = set()
        duplicate_filename_set = set()
        for resource_info in all_used_resources:
            # reconstruct the temporary catrobat filenames -> catrobat.media_objects_in(self.catrobat_file)
            current_filename = helpers.create_catrobat_md5_filename(resource_info["scratch_md5_name"], duplicate_filename_set)

            # check if path changed after conversion
            old_src_path = resource_info["src_path"]
            if old_src_path in new_src_paths:
                src_path = new_src_paths[old_src_path]
            else:
                src_path = old_src_path

            if resource_info["media_type"] in { MediaType.IMAGE, MediaType.UNCONVERTED_SVG }:
                costume_info = resource_info["info"]
                if "text" in costume_info:
                    text = costume_info[JsonKeys.COSTUME_TEXT]
                    x, y, width, height = costume_info[JsonKeys.COSTUME_TEXT_RECT]
                    # TODO: extract RGBA
                    # text_color = costume_info[JsonKeys.COSTUME_TEXT_COLOR]
                    font_name = "NO FONT"
                    font_style = "regular"
                    font_scaling_factor = costume_info[JsonKeys.COSTUME_RESOLUTION] if JsonKeys.COSTUME_RESOLUTION in costume_info else 1
                    if len(costume_info[JsonKeys.COSTUME_FONT_NAME].split()) == 2 :
                        [font_name, font_style] = costume_info[JsonKeys.COSTUME_FONT_NAME].split()
                    else:
                        log.warning("font JSON parameters wrong '{0}', replacing with known font '{1}'".format(costume_info[JsonKeys.COSTUME_FONT_NAME], image_processing._supported_fonts_path_mapping.keys()[0]))
                        font_scaling_factor = font_scaling_factor * 1.1 # the original font might be smaller, better scale it down than cut it off
                    if(font_name not in image_processing._supported_fonts_path_mapping):
                        log.warning("font name '{0}' unknown, replacing with known font '{1}'".format(font_name, image_processing._supported_fonts_path_mapping.keys()[0]))
                        font_name = image_processing._supported_fonts_path_mapping.keys()[0]
                        font_scaling_factor = font_scaling_factor * 1.1 # the original font might be smaller, better scale it down than cut it off
                    is_bold = font_style == "Bold"
                    is_italic = font_style == "Italic"
                    font_size = float(costume_info[JsonKeys.COSTUME_FONT_SIZE]) / float(font_scaling_factor)
                    image_file_path = src_path
                    font = image_processing.create_font(font_name, font_size, is_bold, is_italic)
                    assert font is not None
                    editable_image = image_processing.read_editable_image_from_disk(image_file_path)
                    fonty = float(y) + (height * float(font_scaling_factor) / 2.0) # I think this might not work if we rotate something outside of the picture
                    editable_image = image_processing.add_text_to_image(editable_image, text, font, Color.BLACK, float(x), float(fonty), float(width), float(height))

                    # TODO: create duplicate...
                    # TODO: move test_converter.py to converter-python-package...
                    image_processing.save_editable_image_as_png_to_disk(editable_image, image_file_path, overwrite=True)

            current_basename, _ = os.path.splitext(current_filename)
            self.file_rename_map[current_basename] = {}
            self.file_rename_map[current_basename]["src_path"] = src_path
            self.file_rename_map[current_basename]["dst_path"] = resource_info["dest_path"]
            self.file_rename_map[current_basename]["media_type"] = resource_info["media_type"]

            if resource_info["media_type"] in { MediaType.UNCONVERTED_SVG, MediaType.UNCONVERTED_WAV }:
                converted_media_files_to_be_removed.add(src_path)

        self.rename_media_files_and_copy()

        # delete converted png files -> only temporary saved
        for media_file_to_be_removed in converted_media_files_to_be_removed:
            os.remove(media_file_to_be_removed)
Пример #3
0
    def convert(self, progress_bar=None):
        all_used_resources = []
        unconverted_media_resources = []
        converted_media_resources_paths = set()

        # TODO: remove this block later {
        for scratch_md5_name, src_path in self.scratch_project.md5_to_resource_path_map.iteritems(
        ):
            if scratch_md5_name in self.scratch_project.unused_resource_names:
                log.info("Ignoring unused resource file: %s", src_path)
                if progress_bar != None:
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                continue
        # }

        for scratch_object in self.scratch_project.objects:
            project_base_path = self.scratch_project.project_base_path

            for costume_info in scratch_object.get_costumes():
                costume_file_name = costume_info[JsonKeys.COSTUME_MD5]
                costume_src_path = os.path.join(project_base_path,
                                                costume_file_name)
                file_ext = os.path.splitext(costume_file_name)[1].lower()

                if not os.path.exists(costume_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    costume_src_path = self.scratch_project.md5_to_resource_path_map[
                        costume_file_name]

                assert os.path.exists(
                    costume_src_path), "Not existing: {}".format(
                        costume_src_path)
                assert file_ext in {".png", ".svg", ".jpg", ".gif"}, \
                       "Unsupported image file extension: %s" % costume_src_path

                is_unconverted = file_ext == ".svg"
                resource_info = {
                    "scratch_md5_name":
                    costume_file_name,
                    "src_path":
                    costume_src_path,
                    "dest_path":
                    self.images_path,
                    "media_type":
                    MediaType.UNCONVERTED_SVG
                    if is_unconverted else MediaType.IMAGE,
                    "info":
                    costume_info
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and costume_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                    converted_media_resources_paths.add(costume_src_path)

            for sound_info in scratch_object.get_sounds():
                sound_file_name = sound_info[JsonKeys.SOUND_MD5]
                sound_src_path = os.path.join(project_base_path,
                                              sound_file_name)
                file_ext = os.path.splitext(sound_file_name)[1].lower()

                if not os.path.exists(sound_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    sound_src_path = self.scratch_project.md5_to_resource_path_map[
                        sound_file_name]

                assert os.path.exists(
                    sound_src_path), "Not existing: {}".format(sound_src_path)
                assert file_ext in {
                    ".wav", ".mp3"
                }, "Unsupported sound file extension: %s" % sound_src_path

                is_unconverted = file_ext == ".wav" and not wavconverter.is_android_compatible_wav(
                    sound_src_path)
                resource_info = {
                    "scratch_md5_name":
                    sound_file_name,
                    "src_path":
                    sound_src_path,
                    "dest_path":
                    self.sounds_path,
                    "media_type":
                    MediaType.UNCONVERTED_WAV
                    if is_unconverted else MediaType.AUDIO,
                    "info":
                    sound_info
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and sound_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                    converted_media_resources_paths.add(sound_src_path)

        # schedule concurrent conversions (one conversion per thread)
        new_src_paths = {}
        resource_index = 0
        num_total_resources = len(unconverted_media_resources)
        reference_index = 0
        all_unconverted_src_media_paths = set()
        while resource_index < num_total_resources:
            num_next_resources = min(MAX_CONCURRENT_THREADS,
                                     (num_total_resources - resource_index))
            next_resources_end_index = resource_index + num_next_resources
            threads = []
            for index in range(resource_index, next_resources_end_index):
                assert index == reference_index
                reference_index += 1
                data = unconverted_media_resources[index]
                should_update_progress_bar = not data[
                    "src_path"] in all_unconverted_src_media_paths
                all_unconverted_src_media_paths.add(data["src_path"])
                kwargs = {
                    "data":
                    data,
                    "new_src_paths":
                    new_src_paths,
                    "progress_bar":
                    progress_bar if should_update_progress_bar else None
                }
                threads.append(_MediaResourceConverterThread(kwargs=kwargs))
            for thread in threads:
                thread.start()
            for thread in threads:
                thread.join()
            resource_index = next_resources_end_index
        assert reference_index == resource_index and reference_index == num_total_resources

        converted_media_files_to_be_removed = set()
        for resource_info in all_used_resources:
            scratch_md5_name = resource_info["scratch_md5_name"]

            # check if path changed after conversion
            old_src_path = resource_info["src_path"]
            if old_src_path in new_src_paths:
                src_path = new_src_paths[old_src_path]
            else:
                src_path = old_src_path

            if resource_info["media_type"] in {
                    MediaType.IMAGE, MediaType.UNCONVERTED_SVG
            }:
                costume_info = resource_info["info"]
                if "text" in costume_info:
                    text = costume_info[JsonKeys.COSTUME_TEXT]
                    x, y, width, height = costume_info[
                        JsonKeys.COSTUME_TEXT_RECT]
                    # TODO: extract RGBA
                    # text_color = costume_info[JsonKeys.COSTUME_TEXT_COLOR]
                    [font_name, font_style
                     ] = costume_info[JsonKeys.COSTUME_FONT_NAME].split()
                    is_bold = font_style == "Bold"
                    is_italic = font_style == "Italic"
                    font_size = float(costume_info[JsonKeys.COSTUME_FONT_SIZE])
                    image_file_path = src_path
                    font = image_processing.create_font(
                        font_name, font_size, is_bold, is_italic)
                    assert font is not None
                    editable_image = image_processing.read_editable_image_from_disk(
                        image_file_path)
                    editable_image = image_processing.add_text_to_image(
                        editable_image, text, font, Color.BLACK, float(x),
                        float(y), float(width), float(height))
                    # TODO: create duplicate...
                    # TODO: move test_converter.py to converter-python-package...
                    image_processing.save_editable_image_as_png_to_disk(
                        editable_image, image_file_path, overwrite=True)

            self._copy_media_file(scratch_md5_name, src_path,
                                  resource_info["dest_path"],
                                  resource_info["media_type"])

            if resource_info["media_type"] in {
                    MediaType.UNCONVERTED_SVG, MediaType.UNCONVERTED_WAV
            }:
                converted_media_files_to_be_removed.add(src_path)

        self._update_file_names_of_converted_media_files()

        for media_file_to_be_removed in converted_media_files_to_be_removed:
            os.remove(media_file_to_be_removed)
Пример #4
0
    def convert(self, progress_bar = None):
        all_used_resources = []
        unconverted_media_resources = []
        converted_media_resources_paths = set()

        # TODO: remove this block later {
        for scratch_md5_name, src_path in self.scratch_project.md5_to_resource_path_map.iteritems():
            if scratch_md5_name in self.scratch_project.unused_resource_names:
                log.info("Ignoring unused resource file: %s", src_path)
                if progress_bar != None:
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                continue
        # }

        for scratch_object in self.scratch_project.objects:
            project_base_path = self.scratch_project.project_base_path

            for costume_info in scratch_object.get_costumes():
                costume_file_name = costume_info[JsonKeys.COSTUME_MD5]
                costume_src_path = os.path.join(project_base_path, costume_file_name)
                file_ext = os.path.splitext(costume_file_name)[1].lower()

                if not os.path.exists(costume_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    costume_src_path = self.scratch_project.md5_to_resource_path_map[costume_file_name]

                assert os.path.exists(costume_src_path), "Not existing: {}".format(costume_src_path)
                assert file_ext in {".png", ".svg", ".jpg", ".gif"}, \
                       "Unsupported image file extension: %s" % costume_src_path
                ispng = file_ext == ".png"
                is_unconverted = file_ext == ".svg"

                resource_info = {
                    "scratch_md5_name": costume_file_name,
                    "src_path": costume_src_path,
                    "dest_path": self.images_path,
                    "media_type": MediaType.UNCONVERTED_SVG if is_unconverted else MediaType.IMAGE,
                    "info": costume_info
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and costume_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    #TODO: background gets scaled too, shouldn't be the case
                    if ispng:
                        isStageCostume = scratch_object.name == "Stage"
                        self.convertPNG(isStageCostume, costume_info,costume_src_path, costume_src_path)
                    converted_media_resources_paths.add(costume_src_path)
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)


            for sound_info in scratch_object.get_sounds():
                sound_file_name = sound_info[JsonKeys.SOUND_MD5]
                sound_src_path = os.path.join(project_base_path, sound_file_name)
                file_ext = os.path.splitext(sound_file_name)[1].lower()

                if not os.path.exists(sound_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    sound_src_path = self.scratch_project.md5_to_resource_path_map[sound_file_name]

                assert os.path.exists(sound_src_path), "Not existing: {}".format(sound_src_path)
                assert file_ext in {".wav", ".mp3"}, "Unsupported sound file extension: %s" % sound_src_path

                is_unconverted = file_ext == ".wav" and not wavconverter.is_android_compatible_wav(sound_src_path)
                resource_info = {
                    "scratch_md5_name": sound_file_name,
                    "src_path": sound_src_path,
                    "dest_path": self.sounds_path,
                    "media_type": MediaType.UNCONVERTED_WAV if is_unconverted else MediaType.AUDIO,
                    "info": sound_info
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and sound_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                    converted_media_resources_paths.add(sound_src_path)


        # schedule concurrent conversions (one conversion per thread)
        new_src_paths = {}
        resource_index = 0
        num_total_resources = len(unconverted_media_resources)
        reference_index = 0
        media_srces = []
        while resource_index < num_total_resources:
            num_next_resources = min(MAX_CONCURRENT_THREADS, (num_total_resources - resource_index))
            next_resources_end_index = resource_index + num_next_resources
            threads = []
            for index in range(resource_index, next_resources_end_index):
                assert index == reference_index
                reference_index += 1
                data = unconverted_media_resources[index]
                if data["src_path"] in media_srces:
                    continue
                else:
                    media_srces.append(data["src_path"])
                kwargs = {
                    "data": data,
                    "new_src_paths": new_src_paths,
                    "progress_bar": progress_bar
                }
                threads.append(_MediaResourceConverterThread(kwargs=kwargs))
            for thread in threads: thread.start()
            for thread in threads: thread.join()
            resource_index = next_resources_end_index
        assert reference_index == resource_index and reference_index == num_total_resources

        converted_media_files_to_be_removed = set()
        for resource_info in all_used_resources:
            scratch_md5_name = resource_info["scratch_md5_name"]

            # check if path changed after conversion
            old_src_path = resource_info["src_path"]
            if old_src_path in new_src_paths:
                src_path = new_src_paths[old_src_path]
            else:
                src_path = old_src_path

            if resource_info["media_type"] in { MediaType.IMAGE, MediaType.UNCONVERTED_SVG }:
                costume_info = resource_info["info"]
                if "text" in costume_info:
                    text = costume_info[JsonKeys.COSTUME_TEXT]
                    x, y, width, height = costume_info[JsonKeys.COSTUME_TEXT_RECT]
                    # TODO: extract RGBA
                    # text_color = costume_info[JsonKeys.COSTUME_TEXT_COLOR]
                    font_name = "NO FONT"
                    font_style = "regular"
                    font_scaling_factor = costume_info[JsonKeys.COSTUME_RESOLUTION] if JsonKeys.COSTUME_RESOLUTION in costume_info else 1
                    if len(costume_info[JsonKeys.COSTUME_FONT_NAME].split()) == 2 :
                        [font_name, font_style] = costume_info[JsonKeys.COSTUME_FONT_NAME].split()
                    else:
                        log.warning("font JSON parameters wrong '{0}', replacing with known font '{1}'".format(costume_info[JsonKeys.COSTUME_FONT_NAME], image_processing._supported_fonts_path_mapping.keys()[0]))
                        font_scaling_factor = font_scaling_factor * 1.1 # the original font might be smaller, better scale it down than cut it off
                    if(font_name not in image_processing._supported_fonts_path_mapping):
                        log.warning("font name '{0}' unknown, replacing with known font '{1}'".format(font_name, image_processing._supported_fonts_path_mapping.keys()[0]))
                        font_name = image_processing._supported_fonts_path_mapping.keys()[0]
                        font_scaling_factor = font_scaling_factor * 1.1 # the original font might be smaller, better scale it down than cut it off
                    is_bold = font_style == "Bold"
                    is_italic = font_style == "Italic"
                    font_size = float(costume_info[JsonKeys.COSTUME_FONT_SIZE]) / float(font_scaling_factor)
                    image_file_path = src_path
                    font = image_processing.create_font(font_name, font_size, is_bold, is_italic)
                    assert font is not None
                    editable_image = image_processing.read_editable_image_from_disk(image_file_path)
                    fonty = float(y) + (height * float(font_scaling_factor) / 2.0) # I think this might not work if we rotate something outside of the picture
                    editable_image = image_processing.add_text_to_image(editable_image, text, font, Color.BLACK, float(x), float(fonty), float(width), float(height))

                    # TODO: create duplicate...
                    # TODO: move test_converter.py to converter-python-package...
                    image_processing.save_editable_image_as_png_to_disk(editable_image, image_file_path, overwrite=True)

            self._copy_media_file(scratch_md5_name, src_path, resource_info["dest_path"],
                                  resource_info["media_type"])

            if resource_info["media_type"] in { MediaType.UNCONVERTED_SVG, MediaType.UNCONVERTED_WAV }:
                converted_media_files_to_be_removed.add(src_path)

        self._update_file_names_of_converted_media_files()

        for media_file_to_be_removed in converted_media_files_to_be_removed:
            os.remove(media_file_to_be_removed)
    def convert(self, progress_bar=None):
        all_used_resources = []
        unconverted_media_resources = []
        converted_media_resources_paths = set()

        # TODO: remove this block later {
        for scratch_md5_name, src_path in self.scratch_project.md5_to_resource_path_map.iteritems():
            if scratch_md5_name in self.scratch_project.unused_resource_names:
                log.info("Ignoring unused resource file: %s", src_path)
                if progress_bar != None:
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                continue
        # }

        for scratch_object in self.scratch_project.objects:
            project_base_path = self.scratch_project.project_base_path

            for costume_info in scratch_object.get_costumes():
                costume_file_name = costume_info[JsonKeys.COSTUME_MD5]
                costume_src_path = os.path.join(project_base_path, costume_file_name)
                file_ext = os.path.splitext(costume_file_name)[1].lower()

                if not os.path.exists(costume_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    costume_src_path = self.scratch_project.md5_to_resource_path_map[costume_file_name]

                assert os.path.exists(costume_src_path), "Not existing: {}".format(costume_src_path)
                assert file_ext in {".png", ".svg", ".jpg", ".gif"}, (
                    "Unsupported image file extension: %s" % costume_src_path
                )

                is_unconverted = file_ext == ".svg"
                resource_info = {
                    "scratch_md5_name": costume_file_name,
                    "src_path": costume_src_path,
                    "dest_path": self.images_path,
                    "media_type": MediaType.UNCONVERTED_SVG if is_unconverted else MediaType.IMAGE,
                    "info": costume_info,
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and costume_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                    converted_media_resources_paths.add(costume_src_path)

            for sound_info in scratch_object.get_sounds():
                sound_file_name = sound_info[JsonKeys.SOUND_MD5]
                sound_src_path = os.path.join(project_base_path, sound_file_name)
                file_ext = os.path.splitext(sound_file_name)[1].lower()

                if not os.path.exists(sound_src_path):
                    # media files of local projects are NOT named by their hash-value -> change name
                    sound_src_path = self.scratch_project.md5_to_resource_path_map[sound_file_name]

                assert os.path.exists(sound_src_path), "Not existing: {}".format(sound_src_path)
                assert file_ext in {".wav", ".mp3"}, "Unsupported sound file extension: %s" % sound_src_path

                is_unconverted = file_ext == ".wav" and not wavconverter.is_android_compatible_wav(sound_src_path)
                resource_info = {
                    "scratch_md5_name": sound_file_name,
                    "src_path": sound_src_path,
                    "dest_path": self.sounds_path,
                    "media_type": MediaType.UNCONVERTED_WAV if is_unconverted else MediaType.AUDIO,
                    "info": sound_info,
                }

                all_used_resources.append(resource_info)

                if is_unconverted:
                    unconverted_media_resources.append(resource_info)
                elif progress_bar != None and sound_src_path not in converted_media_resources_paths:
                    # update progress bar for all those media files that don't have to be converted
                    progress_bar.update(ProgressType.CONVERT_MEDIA_FILE)
                    converted_media_resources_paths.add(sound_src_path)

        # schedule concurrent conversions (one conversion per thread)
        new_src_paths = {}
        resource_index = 0
        num_total_resources = len(unconverted_media_resources)
        reference_index = 0
        all_unconverted_src_media_paths = set()
        while resource_index < num_total_resources:
            num_next_resources = min(MAX_CONCURRENT_THREADS, (num_total_resources - resource_index))
            next_resources_end_index = resource_index + num_next_resources
            threads = []
            for index in range(resource_index, next_resources_end_index):
                assert index == reference_index
                reference_index += 1
                data = unconverted_media_resources[index]
                should_update_progress_bar = not data["src_path"] in all_unconverted_src_media_paths
                all_unconverted_src_media_paths.add(data["src_path"])
                kwargs = {
                    "data": data,
                    "new_src_paths": new_src_paths,
                    "progress_bar": progress_bar if should_update_progress_bar else None,
                }
                threads.append(_MediaResourceConverterThread(kwargs=kwargs))
            for thread in threads:
                thread.start()
            for thread in threads:
                thread.join()
            resource_index = next_resources_end_index
        assert reference_index == resource_index and reference_index == num_total_resources

        converted_media_files_to_be_removed = set()
        for resource_info in all_used_resources:
            scratch_md5_name = resource_info["scratch_md5_name"]

            # check if path changed after conversion
            old_src_path = resource_info["src_path"]
            if old_src_path in new_src_paths:
                src_path = new_src_paths[old_src_path]
            else:
                src_path = old_src_path

            if resource_info["media_type"] in {MediaType.IMAGE, MediaType.UNCONVERTED_SVG}:
                costume_info = resource_info["info"]
                if "text" in costume_info:
                    text = costume_info[JsonKeys.COSTUME_TEXT]
                    x, y, width, height = costume_info[JsonKeys.COSTUME_TEXT_RECT]
                    # TODO: extract RGBA
                    # text_color = costume_info[JsonKeys.COSTUME_TEXT_COLOR]
                    [font_name, font_style] = costume_info[JsonKeys.COSTUME_FONT_NAME].split()
                    is_bold = font_style == "Bold"
                    is_italic = font_style == "Italic"
                    font_size = float(costume_info[JsonKeys.COSTUME_FONT_SIZE])
                    image_file_path = src_path
                    font = image_processing.create_font(font_name, font_size, is_bold, is_italic)
                    assert font is not None
                    editable_image = image_processing.read_editable_image_from_disk(image_file_path)
                    editable_image = image_processing.add_text_to_image(
                        editable_image, text, font, Color.BLACK, float(x), float(y), float(width), float(height)
                    )
                    # TODO: create duplicate...
                    # TODO: move test_converter.py to converter-python-package...
                    image_processing.save_editable_image_as_png_to_disk(editable_image, image_file_path, overwrite=True)

            self._copy_media_file(scratch_md5_name, src_path, resource_info["dest_path"], resource_info["media_type"])

            if resource_info["media_type"] in {MediaType.UNCONVERTED_SVG, MediaType.UNCONVERTED_WAV}:
                converted_media_files_to_be_removed.add(src_path)

        self._update_file_names_of_converted_media_files()

        for media_file_to_be_removed in converted_media_files_to_be_removed:
            os.remove(media_file_to_be_removed)