"""Media accessors All media accessor __init__s take a `root` argument, which should be a path to the root of the media directory. Alternatively, `root` can be a custom MediaFile subclass. Most __init__s take an ORM object as a second argument. Their various methods take a number of arguments specifying exactly which file you want (such as the female sprite, backsprite, etc.). ValueError is raised when the specified file cannot be found. The accessors use fallbacks: for example Bulbasaur's males and females look the same, so if you request Bulbasaur's female sprite, it will give you the common image. Or for a Pokemon without individual form sprites, you will get the common base sprite. Or for versions witout shiny Pokemon, you will always get the non-shiny version (that's how shiny Pokemon looked there!). However arguments such as `animated` don't use fallbacks. You can set `strict` to True to disable these fallbacks and cause ValueError to be raised when the exact specific file you asked for is not found. This is useful for listing non-duplicate sprites, for example. Use keyword arguments when calling the media-getting methods, unless noted otherwise. The returned "file" objects have useful attributes like relative_path, path, and open(). All images are in the PNG format, except animations (GIF). All sounds are OGGs. """ import os from functools import partial import six class MediaFile(object): """Represents a file: picture, sound, etc. Attributes: path_elements: List of directory/file names that make up relative_path relative_path: Filesystem path relative to the root path: Absolute path to the file exists: True if the file exists media_available: false if no media is available at the given root. open(): Open the file """ def __init__(self, root, *path_elements): self.path_elements = path_elements self.root = root @property def relative_path(self): return os.path.join(*self.path_elements) @property def path(self): return os.path.join(self.root, *self.path_elements) def open(self): """Open this file for reading, in the appropriate mode (i.e. binary) """ return open(self.path, 'rb') @property def exists(self): return os.path.exists(self.path) @property def media_available(self): return os.path.isdir(self.root) def __eq__(self, other): return self.path == other.path def __ne__(self, other): return self.path != other.path def __str__(self): return '' % self.relative_path class BaseMedia(object): def __init__(self, root): if isinstance(root, six.string_types): self.file_class = partial(MediaFile, root) else: self.file_class = root @property def available(self): return self.file_class().media_available def from_path_elements(self, path_elements, basename, extension, surely_exists=False): filename = basename + extension path_elements = [self.toplevel_dir] + path_elements + [filename] mfile = self.file_class(*path_elements) if surely_exists or mfile.exists: return mfile else: raise ValueError('File %s not found' % mfile.path) class _BasePokemonMedia(BaseMedia): toplevel_dir = 'pokemon' has_gender_differences = False is_species = False is_proper = False introduced_in = 0 # Info about of what's inside the pokemon main sprite directories, so we # don't have to check directory existence all the time. _pokemon_sprite_info = { 'red-blue': (1, set('back gray'.split())), 'red-green': (1, set('back gray'.split())), 'yellow': (1, set('back gray gbc'.split())), 'gold': (2, set('back shiny'.split())), 'silver': (2, set('back shiny'.split())), 'crystal': (2, set('animated back shiny'.split())), 'ruby-sapphire': (3, set('back shiny'.split())), 'emerald': (3, set('animated back shiny frame2'.split())), 'firered-leafgreen': (3, set('back shiny'.split())), 'diamond-pearl': (4, set('back shiny female frame2'.split())), 'platinum': (4, set('back shiny female frame2'.split())), 'heartgold-soulsilver': (4, set('back shiny female frame2'.split())), 'black-white': (5, set('back shiny female'.split())), } def __init__(self, root, species_id, form_postfix=None): BaseMedia.__init__(self, root) self.species_id = str(species_id) self.form_postfix = form_postfix def _get_file(self, path_elements, extension, strict, surely_exists=False): basename = str(self.species_id) if self.form_postfix: fullname = basename + self.form_postfix try: return self.from_path_elements( path_elements, fullname, extension, surely_exists=surely_exists) except ValueError: if strict: raise return self.from_path_elements(path_elements, basename, extension, surely_exists=surely_exists) def sprite(self, version='black-white', # The media directories are in this order: animated=False, back=False, color=None, shiny=False, female=False, frame=None, strict=False, ): """Get a main sprite sprite for a pokemon. Everything except version should be given as a keyword argument. Either specify version as an ORM object, or give the version path as a string (which is the only way to get 'red-green'). Leave the default for the latest version. animated: get a GIF animation (currently Crystal & Emerald only) back: get a backsprite instead of a front one color: can be 'color' (RGBY only) or 'gbc' (Yellow only) shiny: get a shiny sprite. In old versions, gives a normal sprite unless `strict` is set female: get a female sprite instead of male. For pokemon with no sexual dimorphism, gets the common sprite unless `strict` is set. frame: set to 2 to get the second frame of the animation (Emerald, DPP, and HG/SS only) If the sprite is not found, raise a ValueError. """ if isinstance(version, six.string_types): version_dir = version try: generation, info = self._pokemon_sprite_info[version_dir] except KeyError: raise ValueError('Version directory %s not found', version_dir) else: version_dir = version.identifier try: generation, info = self._pokemon_sprite_info[version_dir] except KeyError: version_group = version.version_group version_dir = '-'.join( v.identifier for v in version_group.versions) try: generation, info = self._pokemon_sprite_info[version_dir] except KeyError: raise ValueError('Version directory %s not found', version_dir) if generation < self.introduced_in: raise ValueError("Pokemon %s didn't exist in %s" % ( self.species_id, version_dir)) path_elements = ['main-sprites', version_dir] if animated: if 'animated' not in info: raise ValueError("No animated sprites for %s" % version_dir) path_elements.append('animated') extension = '.gif' else: extension = '.png' if back: if version_dir == 'emerald': # Emerald backsprites are the same as ruby/sapphire if strict: raise ValueError("Emerald uses R/S backsprites") if animated: raise ValueError("No animated backsprites for Emerald") path_elements[1] = version_dir = 'ruby-sapphire' if version_dir == 'crystal' and animated: raise ValueError("No animated backsprites for Crystal") path_elements.append('back') if color == 'gray': if 'gray' not in info: raise ValueError("No grayscale sprites for %s" % version_dir) path_elements.append('gray') elif color == 'gbc': if 'gbc' not in info: raise ValueError("No GBC sprites for %s" % version_dir) path_elements.append('gbc') elif color: raise ValueError("Unknown color scheme: %s" % color) if shiny: if 'shiny' in info: path_elements.append('shiny') elif strict: raise ValueError("No shiny sprites for %s" % version_dir) if female: female_sprite = self.has_gender_differences # Chimecho's female back frame 2 sprite has one hand in # a slightly different pose, in Platinum and HGSS # (we have duplicate sprites frame 1, for convenience) if self.species_id == '358' and back and version_dir in ( 'platinum', 'heartgold-soulsilver'): female_sprite = True female_sprite = female_sprite and 'female' in info if female_sprite: path_elements.append('female') elif strict: raise ValueError( 'Pokemon %s has no gender differences' % self.species_id) if not frame or frame == 1: pass elif frame == 2: if 'frame2' in info: path_elements.append('frame%s' % frame) else: raise ValueError("No frame 2 for %s" % version_dir) else: raise ValueError("Bad frame %s" % frame) return self._get_file(path_elements, extension, strict=strict, # Avoid a stat in the common case surely_exists=(self.is_species and version_dir == 'black-white' and not back and not female)) def _maybe_female(self, path_elements, female, strict): if female: if self.has_gender_differences: elements = path_elements + ['female'] try: return self._get_file(elements, '.png', strict=strict) except ValueError: if strict: raise elif strict: raise ValueError( 'Pokemon %s has no gender differences' % self.species_id) return self._get_file(path_elements, '.png', strict=strict) def icon(self, female=False, strict=False): """Get the Pokemon's menu icon""" return self._maybe_female(['icons'], female, strict) def sugimori(self, female=False, strict=False): """Get the Pokemon's official art, drawn by Ken Sugimori""" return self._maybe_female(['sugimori'], female, strict) def overworld(self, direction='down', shiny=False, female=False, frame=1, strict=False, ): """Get an overworld sprite direction: 'up', 'down', 'left', or 'right' shiny: true for a shiny sprite female: true for female sprite (or the common one for both M & F) frame: 2 for the second animation frame strict: disable fallback for `female` """ path_elements = ['overworld'] if shiny: path_elements.append('shiny') if female: if self.has_gender_differences: path_elements.append('female') elif strict: raise ValueError('No female overworld sprite') else: female = False path_elements.append(direction) if frame and frame > 1: path_elements.append('frame%s' % frame) try: return self._get_file(path_elements, '.png', strict=strict) except ValueError: if female and not strict: path_elements.remove('female') return self._get_file(path_elements, '.png', strict=strict) else: raise def footprint(self, strict=False): """Get the Pokemon's footprint""" return self._get_file(['footprints'], '.png', strict=strict) def trozei(self, strict=False): """Get the Pokemon's animated Trozei sprite""" return self._get_file(['trozei'], '.gif', strict=strict) def cry(self, strict=False): """Get the Pokemon's cry""" return self._get_file(['cries'], '.ogg', strict=strict) def cropped_sprite(self, strict=False): """Get the Pokemon's cropped sprite""" return self._get_file(['cropped'], '.png', strict=strict) class PokemonFormMedia(_BasePokemonMedia): """Media related to a PokemonForm """ is_proper = True def __init__(self, root, pokemon_form): species_id = pokemon_form.species.id if pokemon_form.form_identifier: form_postfix = '-' + pokemon_form.form_identifier else: form_postfix = None _BasePokemonMedia.__init__(self, root, species_id, form_postfix) self.form = pokemon_form species = pokemon_form.species self.has_gender_differences = species.has_gender_differences self.introduced_in = pokemon_form.version_group.generation_id class PokemonSpeciesMedia(_BasePokemonMedia): """Media related to a PokemonSpecies """ is_species = True is_proper = True def __init__(self, root, species): _BasePokemonMedia.__init__(self, root, species.id) self.has_gender_differences = species.has_gender_differences self.introduced_in = species.generation_id class UnknownPokemonMedia(_BasePokemonMedia): """Media related to the unknown Pokemon ("?") Note that not a lot of files are available for it. """ def __init__(self, root): _BasePokemonMedia.__init__(self, root, '0') class EggMedia(_BasePokemonMedia): """Media related to a pokemon egg Note that not a lot of files are available for these. Give a Manaphy as `species` to get the Manaphy egg. """ def __init__(self, root, species=None): if species and species.identifier == 'manaphy': postfix = '-manaphy' else: postfix = None _BasePokemonMedia.__init__(self, root, 'egg', postfix) class SubstituteMedia(_BasePokemonMedia): """Media related to the Substitute sprite Note that not a lot of files are available for Substitute. """ def __init__(self, root): _BasePokemonMedia.__init__(self, root, 'substitute') class _BaseItemMedia(BaseMedia): toplevel_dir = 'items' def underground(self, rotation=0): """Get the item's sprite as it appears in the Sinnoh underground Rotation can be 0, 90, 180, or 270. """ if rotation: basename = self.identifier + '-%s' % rotation else: basename = self.identifier return self.from_path_elements(['underground'], basename, '.png') class ItemMedia(_BaseItemMedia): """Media related to an item """ def __init__(self, root, item): _BaseItemMedia.__init__(self, root) self.item = item self.identifier = item.identifier def sprite(self, version=None): """Get the item's sprite If version is not given, use the latest version. """ identifier = self.identifier # Handle machines # We check the identifier, so that we don't query the machine # information for any item. if identifier.startswith(('tm', 'hm')): try: int(identifier[2:]) except ValueError: # Not really a TM/HM pass else: machines = self.item.machines if version: try: machine = [ m for m in machines if m.version_group == version.version_group ][0] except IndexError: raise ValueError("%s doesn't exist in %s" % ( identifier, version.identifier)) else: # They're ordered, so get the last one machine = machines[-1] type_identifier = machine.move.type.identifier identifier = identifier[:2] + '-' + type_identifier elif identifier.startswith('data-card-'): try: int(identifier[10:]) except ValueError: # Not a real data card??? pass else: identifier = 'data-card' if version is not None: generation_id = version.generation.id if generation_id <= 3 and identifier == 'dowsing-mchn': identifier = 'itemfinder' try: gen = 'gen%s' % generation_id return self.from_path_elements([gen], identifier, '.png') except ValueError: pass return self.from_path_elements([], identifier, '.png', surely_exists=True) def underground(self, rotation=0): """Get the item's sprite as it appears in the Sinnoh underground Rotation can be 0, 90, 180, or 270. """ if not self.item.appears_underground: raise ValueError("%s doesn't appear underground" % self.identifier) return super(ItemMedia, self).underground(rotation=rotation) def berry_image(self): """Get a berry's big sprite """ if not self.item.berry: raise ValueError("%s is not a berry" % self.identifier) return self.from_path_elements(['berries'], self.identifier, '.png') class UndergroundRockMedia(_BaseItemMedia): """Media related to a rock in the Sinnoh underground rock_type can be one of: i, ii, o, o-big, s, t, z """ def __init__(self, root, rock_type): _BaseItemMedia.__init__(self, root) self.identifier = 'rock-%s' % rock_type class UndergroundSphereMedia(_BaseItemMedia): """Media related to a sphere in the Sinnoh underground color can be one of: red, blue, green, pale, prism """ def __init__(self, root, color, big=False): _BaseItemMedia.__init__(self, root) self.identifier = '%s-sphere' % color if big: self.identifier += '-big' class _SimpleIconMedia(BaseMedia): def __init__(self, root, thing): BaseMedia.__init__(self, root) self.identifier = thing.identifier def icon(self): return self.from_path_elements([], self.identifier, '.png') class DamageClassMedia(_SimpleIconMedia): toplevel_dir = 'damage-classes' class HabitatMedia(_SimpleIconMedia): toplevel_dir = 'habitats' class ShapeMedia(_SimpleIconMedia): toplevel_dir = 'shapes' class ItemPocketMedia(_SimpleIconMedia): toplevel_dir = 'item-pockets' def icon(self, selected=False): if selected: return self.from_path_elements( ['selected'], self.identifier, '.png') else: return self.from_path_elements([], self.identifier, '.png') class _LanguageIconMedia(_SimpleIconMedia): def icon(self, lang='en'): return self.from_path_elements([lang], self.identifier, '.png') class ContestTypeMedia(_LanguageIconMedia): toplevel_dir = 'contest-types' class TypeMedia(_LanguageIconMedia): toplevel_dir = 'types' ''' XXX: No accessors for: chrome fonts ribbons '''