def test_can_convert_android_incompatible_to_compatible_wav_file(self): for wav_path in self.adpcm_wavfile_paths(): assert not wavconverter.is_android_compatible_wav(wav_path) converted_wav_path = wavconverter.convert_to_android_compatible_wav( wav_path) assert wavconverter.is_android_compatible_wav(converted_wav_path) os.remove(converted_wav_path)
def test_can_convert_android_incompatible_to_compatible_wav_file(self): for wav_path in self.adpcm_wavfile_paths(): assert not wavconverter.is_android_compatible_wav(wav_path) converted_wav_path = os.path.join(self._testresult_folder_path, os.path.basename(wav_path)) wavconverter.convert_to_android_compatible_wav(wav_path, converted_wav_path) assert wavconverter.is_android_compatible_wav(converted_wav_path)
def test_fail_on_missing_sox_binary(self): saved_path_env = os.environ[_ENV_PATH] os.environ[_ENV_PATH] = "" dummy_wav = self.adpcm_wavfile_paths()[0] try: wavconverter.is_android_compatible_wav(dummy_wav) self.fail("Expected exception 'EnvironmentError' not thrown") except EnvironmentError: try: wavconverter.convert_to_android_compatible_wav(dummy_wav, "dummy.wav") self.fail("Expected exception 'EnvironmentError' not thrown") except EnvironmentError: pass finally: assert not os.path.exists("dummy.wav") finally: os.environ[_ENV_PATH] = saved_path_env
def test_fail_on_missing_sox_binary(self): saved_path_env = os.environ[_ENV_PATH] os.environ[_ENV_PATH] = "" dummy_wav = self.adpcm_wavfile_paths()[0] output_path = None try: wavconverter.is_android_compatible_wav(dummy_wav) self.fail("Expected exception 'EnvironmentError' not thrown") except EnvironmentError: try: output_path = wavconverter.convert_to_android_compatible_wav(dummy_wav) self.fail("Expected exception 'EnvironmentError' not thrown") except EnvironmentError: pass finally: if output_path is not None: output_file_exists = os.path.exists(output_path) if output_file_exists: os.remove(output_path) assert not output_file_exists finally: os.environ[_ENV_PATH] = saved_path_env
def write_mediafiles(original_to_converted_catrobat_resource_file_name): def resource_name_for(file_path): return common.md5_hash(file_path) + os.path.splitext(file_path)[1] 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) continue file_ext = os.path.splitext(scratch_md5_name)[1].lower() converted_file = False # TODO: refactor to a MediaConverter class if file_ext in {".png", ".svg", ".jpg", ".gif"}: target_dir = images_path if file_ext == ".svg": # converting svg to png -> new md5 and filename src_path = svgtopng.convert(src_path) if not os.path.exists(src_path): assert False, "Not existing: {}. Available files in directory: {}".format(src_path, os.listdir(os.path.dirname(src_path))) converted_file = True elif file_ext in {".wav", ".mp3"}: target_dir = sounds_path if file_ext == ".wav": if not wavconverter.is_android_compatible_wav(src_path): temp_path = src_path.replace(".wav", "converted.wav") wavconverter.convert_to_android_compatible_wav(src_path, temp_path) src_path = temp_path converted_file = True else: assert file_ext in {".json"}, "Unknown media file extension: %s" % src_path continue assert os.path.exists(src_path), "Not existing: {}. Available files in directory: {}".format(src_path, os.listdir(os.path.dirname(src_path))) # for Catrobat separate file is needed for resources which are used multiple times but with different names for scratch_resource_name in self.scratch_project.find_all_resource_names_for(scratch_md5_name): catrobat_resource_file_name = self._catrobat_resource_file_name_for(scratch_md5_name, scratch_resource_name) if converted_file: original_resource_file_name = catrobat_resource_file_name converted_scratch_md5_name = resource_name_for(src_path) converted_resource_file_name = self._catrobat_resource_file_name_for(converted_scratch_md5_name, scratch_resource_name) catrobat_resource_file_name = original_to_converted_catrobat_resource_file_name[original_resource_file_name] = converted_resource_file_name assert catrobat_resource_file_name != original_resource_file_name shutil.copyfile(src_path, os.path.join(target_dir, catrobat_resource_file_name)) if converted_file: os.remove(src_path)
def test_fail_on_missing_sox_binary(self): saved_path_env = os.environ[_ENV_PATH] os.environ[_ENV_PATH] = "" dummy_wav = self.adpcm_wavfile_paths()[0] output_path = None try: wavconverter.is_android_compatible_wav(dummy_wav) self.fail("Expected exception 'EnvironmentError' not thrown") except EnvironmentError: try: output_path = wavconverter.convert_to_android_compatible_wav( dummy_wav) self.fail("Expected exception 'EnvironmentError' not thrown") except EnvironmentError: pass finally: if output_path is not None: output_file_exists = os.path.exists(output_path) if output_file_exists: os.remove(output_path) assert not output_file_exists finally: os.environ[_ENV_PATH] = saved_path_env
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)
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)
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 test_can_convert_android_incompatible_to_compatible_wav_file(self): for wav_path in self.adpcm_wavfile_paths(): assert not wavconverter.is_android_compatible_wav(wav_path) converted_wav_path = wavconverter.convert_to_android_compatible_wav(wav_path) assert wavconverter.is_android_compatible_wav(converted_wav_path) os.remove(converted_wav_path)
def test_can_detect_android_compatible_wav_file(self): for wav_path in self.pcm_wavfile_paths(): assert wavconverter.is_android_compatible_wav(wav_path)
def test_can_detect_android_compatible_wav_file(self): for wav_path in self.pcm_wavfile_paths(): assert wavconverter.is_android_compatible_wav(wav_path)
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)