Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[STCC-155-224] rename media files #155

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Scratch2Catrobat Converter
short_name: S2CC
version: 0.10.0
build_name: Aegean cat
build_number: 1002
build_number: 1008
build_type: S2CC

;-------------------------------------------------------------------------------
Expand Down
40 changes: 20 additions & 20 deletions src/scratchtocatrobat/converter/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def _placeholder_for_unmapped_blocks_to(*args):
return catbricks.NoteBrick(UNSUPPORTED_SCRATCH_BLOCK_NOTE_MESSAGE_PREFIX_TEMPLATE.format(_arguments_string(args)))

def _key_to_broadcast_message(key_name):
return "key " + key_name + " pressed"
return "key_" + key_name + "_pressed"

def _get_existing_sprite_with_name(sprite_list, name):
for sprite in sprite_list:
Expand Down Expand Up @@ -577,16 +577,16 @@ def _key_filename_for(key):
assert key is not None
key_path = _key_image_path_for(key)
# TODO: extract method, already used once
return common.md5_hash(key_path) + "_" + _key_to_broadcast_message(key) + os.path.splitext(key_path)[1]
key_name = _key_to_broadcast_message(key).replace(" ", "_")
_, ext = os.path.splitext(key_path)
return key_name + ext

def _generate_mouse_filename():
mouse_path = _mouse_image_path()
return common.md5_hash(mouse_path) + "_" + MOUSE_SPRITE_FILENAME
def _get_mouse_filename():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to this function in converter.py line 688 was not adapted to the new name so there is an error when converting a project with mouse cursor. This should be fixed.

return MOUSE_SPRITE_FILENAME

def generated_variable_name(variable_name):
return _GENERATED_VARIABLE_PREFIX + variable_name


def _sound_length_variable_name_for(resource_name):
return generated_variable_name(_SOUND_LENGTH_VARIABLE_NAME_FORMAT.format(resource_name))

Expand Down Expand Up @@ -668,8 +668,10 @@ def _add_global_user_lists_to(self, catrobat_scene):
catrobat_scene.project.userLists.add(global_user_list)

def _add_converted_sprites_to(self, catrobat_scene):
# avoid duplicate filenames -> extend with unique identifier
duplicate_filename_set = set()
for scratch_object in self.scratch_project.objects:
catr_sprite = self._scratch_object_converter(scratch_object)
catr_sprite = self._scratch_object_converter(scratch_object, duplicate_filename_set)
catrobat_scene.addSprite(catr_sprite)

def add_cursor_sprite_to(self, catrobat_scene, upcoming_sprites):
Expand All @@ -684,7 +686,7 @@ def add_cursor_sprite_to(self, catrobat_scene, upcoming_sprites):

look = catcommon.LookData()
look.setName(MOUSE_SPRITE_NAME)
mouse_filename = _generate_mouse_filename()
mouse_filename = _get_mouse_filename()
look.fileName = mouse_filename
sprite.getLookList().add(look)

Expand Down Expand Up @@ -1010,10 +1012,10 @@ def __init__(self, catrobat_project, scratch_project, progress_bar=None, context
self._progress_bar = progress_bar
self._context = context

def __call__(self, scratch_object):
return self._catrobat_sprite_from(scratch_object)
def __call__(self, scratch_object, duplicate_filename_set):
return self._catrobat_sprite_from(scratch_object, duplicate_filename_set)

def _catrobat_sprite_from(self, scratch_object):
def _catrobat_sprite_from(self, scratch_object, duplicate_filename_set):
if not isinstance(scratch_object, scratch.Object):
raise common.ScratchtobatError("Input must be of type={}, but is={}".format(scratch.Object, type(scratch_object)))
sprite_name = scratch_object.name
Expand Down Expand Up @@ -1047,10 +1049,10 @@ def _catrobat_sprite_from(self, scratch_object):
costume_resolution = current_costume_resolution
elif current_costume_resolution != costume_resolution:
log.warning("Costume resolution not same for all costumes")
sprite_looks.add(self._catrobat_look_from(scratch_costume))
sprite_looks.add(self._catrobat_look_from(scratch_costume, duplicate_filename_set))
sprite_sounds = sprite.getSoundList()
for scratch_sound in scratch_object.get_sounds():
sprite_sounds.add(self._catrobat_sound_from(scratch_sound))
sprite_sounds.add(self._catrobat_sound_from(scratch_sound, duplicate_filename_set))

if not scratch_object.is_stage() and scratch_object.get_lists() is not None:
for user_list_data in scratch_object.get_lists():
Expand Down Expand Up @@ -1103,7 +1105,7 @@ def _catrobat_sprite_from(self, scratch_object):
return sprite

@staticmethod
def _catrobat_look_from(scratch_costume):
def _catrobat_look_from(scratch_costume, duplicate_filename_set):
if not scratch_costume or not (isinstance(scratch_costume, dict) and all(_ in scratch_costume for _ in (scratchkeys.COSTUME_MD5, scratchkeys.COSTUME_NAME))):
raise common.ScratchtobatError("Wrong input, must be costume dict: {}".format(scratch_costume))
look = catcommon.LookData()
Expand All @@ -1114,12 +1116,11 @@ def _catrobat_look_from(scratch_costume):

assert scratchkeys.COSTUME_MD5 in scratch_costume
costume_md5_filename = scratch_costume[scratchkeys.COSTUME_MD5]
costume_resource_name = scratch_costume[scratchkeys.COSTUME_NAME]
look.fileName = (mediaconverter.catrobat_resource_file_name_for(costume_md5_filename, costume_resource_name))
look.fileName = helpers.create_catrobat_md5_filename(costume_md5_filename, duplicate_filename_set)
return look

@staticmethod
def _catrobat_sound_from(scratch_sound):
def _catrobat_sound_from(scratch_sound, duplicate_filename_set):
soundinfo = catcommon.SoundInfo()

assert scratchkeys.SOUND_NAME in scratch_sound
Expand All @@ -1128,8 +1129,7 @@ def _catrobat_sound_from(scratch_sound):

assert scratchkeys.SOUND_MD5 in scratch_sound
sound_md5_filename = scratch_sound[scratchkeys.SOUND_MD5]
sound_resource_name = scratch_sound[scratchkeys.SOUND_NAME]
soundinfo.fileName = (mediaconverter.catrobat_resource_file_name_for(sound_md5_filename, sound_resource_name))
soundinfo.fileName = helpers.create_catrobat_md5_filename(sound_md5_filename, duplicate_filename_set)
return soundinfo

@staticmethod
Expand Down Expand Up @@ -1392,7 +1392,7 @@ def write_program_source(catrobat_program, context):
for sprite in catrobat_program.getDefaultScene().spriteList:
if sprite.name == MOUSE_SPRITE_NAME:
mouse_img_path = _mouse_image_path()
shutil.copyfile(mouse_img_path, os.path.join(images_path, _generate_mouse_filename()))
shutil.copyfile(mouse_img_path, os.path.join(images_path, _get_mouse_filename()))
break

def download_automatic_screenshot_if_available(output_dir, scratch_project):
Expand Down
78 changes: 37 additions & 41 deletions src/scratchtocatrobat/converter/mediaconverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,6 @@ class MediaType(object):
UNCONVERTED_WAV = 4


def catrobat_resource_file_name_for(scratch_md5_name, scratch_resource_name):
assert os.path.basename(scratch_md5_name) == scratch_md5_name \
and len(os.path.splitext(scratch_md5_name)[0]) == 32, \
"Must be MD5 hash with file ext: " + scratch_md5_name

# remove unsupported unicode characters from filename
# if isinstance(scratch_resource_name, unicode):
# scratch_resource_name = unicodedata.normalize('NFKD', scratch_resource_name).encode('ascii','ignore')
# if (scratch_resource_name == None) or (len(scratch_resource_name) == 0):
# scratch_resource_name = "unicode_replaced"
resource_ext = os.path.splitext(scratch_md5_name)[1]
return scratch_md5_name.replace(resource_ext, "_" + scratch_resource_name.replace("/",'') + resource_ext)


def _resource_name_for(file_path):
return common.md5_hash(file_path) + os.path.splitext(file_path)[1]


class _MediaResourceConverterThread(Thread):

def run(self):
Expand Down Expand Up @@ -95,7 +77,7 @@ def __init__(self, scratch_project, catrobat_program, images_path, sounds_path):
self.catrobat_program = catrobat_program
self.images_path = images_path
self.sounds_path = sounds_path
self.renamed_files_map = {}
self.file_rename_map = {}


def convert(self, progress_bar = None):
Expand Down Expand Up @@ -213,8 +195,10 @@ def convert(self, progress_bar = None):
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:
scratch_md5_name = resource_info["scratch_md5_name"]
# 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"]
Expand Down Expand Up @@ -256,39 +240,51 @@ def convert(self, progress_bar = None):
# 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"])
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._update_file_names_of_converted_media_files()
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)

# rename the media files and copy them to the catrobat project directory
def rename_media_files_and_copy(self):

def _update_file_names_of_converted_media_files(self):
for (old_file_name, new_file_name) in self.renamed_files_map.iteritems():
look_data_or_sound_infos = filter(lambda info: info.fileName == old_file_name,
catrobat.media_objects_in(self.catrobat_program))
# assert len(look_data_or_sound_infos) > 0
def create_new_file_name(provided_file, index_helper, file_type):
_, ext = os.path.splitext(provided_file)
if file_type in {MediaType.UNCONVERTED_SVG, MediaType.IMAGE}:
return "img_#" + str(index_helper.assign_image_index()) + ext
else:
return "snd_#" + str(index_helper.assign_sound_index()) + ext

media_file_index_helper = helpers.MediaFileIndex()
for info in catrobat.media_objects_in(self.catrobat_program):
basename, _ = os.path.splitext(info.fileName)

# ignore these files, already correctly provided by the converter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought: key sprites are now the only ones left being saved with a hash in their name. If those are not necessary, it would be cleaner to remove those hashes too. This could maybe be done in _key_filename_for in converter.py.

if any(x in basename for x in ["key", "mouse"]):
continue

for info in look_data_or_sound_infos:
info.fileName = new_file_name
assert basename in self.file_rename_map and \
"src_path" in self.file_rename_map[basename] and \
"dst_path" in self.file_rename_map[basename] and \
"media_type" in self.file_rename_map[basename]


def _copy_media_file(self, scratch_md5_name, src_path, dest_path, media_type):
# 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):
new_file_name = catrobat_resource_file_name_for(scratch_md5_name, scratch_resource_name)
if media_type in { MediaType.UNCONVERTED_SVG, MediaType.UNCONVERTED_WAV }:
old_file_name = new_file_name
converted_scratch_md5_name = _resource_name_for(src_path)
new_file_name = catrobat_resource_file_name_for(converted_scratch_md5_name,
scratch_resource_name)
self.renamed_files_map[old_file_name] = new_file_name
shutil.copyfile(src_path, os.path.join(dest_path, new_file_name))
src_path = self.file_rename_map[basename]["src_path"]
dst_path = self.file_rename_map[basename]["dst_path"]
media_type = self.file_rename_map[basename]["media_type"]
new_file_name = create_new_file_name(src_path, media_file_index_helper, media_type)
shutil.copyfile(src_path, os.path.join(dst_path, new_file_name))
info.fileName = new_file_name

def resize_png(self, path_in, path_out, bitmapResolution):
import java.awt.image.BufferedImage
Expand Down
8 changes: 4 additions & 4 deletions src/scratchtocatrobat/converter/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2486,7 +2486,7 @@ def test_key_pressed_block(self):

key_w = default_scene.spriteList[2]
assert key_w != None
assert key_w.name == 'key w pressed'
assert key_w.name == 'key_w_pressed'

scripts = key_w.getScriptList()
assert len(scripts) == 3
Expand Down Expand Up @@ -2548,7 +2548,7 @@ def test_key_pressed_script(self):

broadcast_script = spritescripts[1]
assert isinstance(broadcast_script, catbase.BroadcastScript)
assert broadcast_script.getBroadcastMessage() == 'key space pressed'
assert broadcast_script.getBroadcastMessage() == 'key_space_pressed'

sprite_brick_list = spritescripts[1].getBrickList()
assert len(sprite_brick_list) == 1
Expand All @@ -2558,7 +2558,7 @@ def test_key_pressed_script(self):

key_space = default_scene.spriteList[2]
assert key_space != None
assert key_space.name == 'key space pressed'
assert key_space.name == 'key_space_pressed'

keyscripts = key_space.getScriptList()
assert len(keyscripts) == 2
Expand All @@ -2569,7 +2569,7 @@ def test_key_pressed_script(self):

broadcast_brick = key_brick_list[0]
assert isinstance(broadcast_brick, catbricks.BroadcastBrick)
assert broadcast_brick.getBroadcastMessage() == 'key space pressed'
assert broadcast_brick.getBroadcastMessage() == 'key_space_pressed'



Expand Down
36 changes: 36 additions & 0 deletions src/scratchtocatrobat/tools/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,39 @@ def update(self, progress_type, increment=1):
self._output_stream.write("{}{}{}\n".format(ProgressBar.START_PROGRESS_INDICATOR, \
round(percentage, 2), \
ProgressBar.END_PROGRESS_INDICATOR))


# create a unique name for a catrobat media file -> name derived from scratch md5-hash
# since one file might be used for multiple objects, the md5-hash is extended with a unique identifier
# name used until all files, converted or unconverted, are copied to the catrobat project directory
def create_catrobat_md5_filename(scratch_md5_name, duplicate_file_set):
filename, ext = os.path.splitext(scratch_md5_name)
current_filename = filename + "_#0" + ext
next_index = 1
while current_filename in duplicate_file_set:
current_filename = filename + "_#" + str(next_index) + ext
next_index += 1
duplicate_file_set.add(current_filename)
return current_filename


class MediaFileIndex:
def __init__(self):
self.img_idx = 0
self.snd_idx = 0

def increment_image_index(self):
self.img_idx += 1

def increment_sound_index(self):
self.snd_idx += 1

def assign_image_index(self):
current_image_index = self.img_idx
self.increment_image_index()
return current_image_index

def assign_sound_index(self):
current_sound_index = self.snd_idx
self.increment_sound_index()
return current_sound_index