def get_folder_path_definition(self): # If we've done this already then return it immediately without # incurring any extra work if self.cached_folder_path_definition is not None: return self.cached_folder_path_definition config = load_config() # If Directory is in the config we assume full_path and its # corresponding values (date, location) are also present if ('Directory' not in config): return self.default_folder_path_definition config_directory = config['Directory'] path_parts = re.search('\%([^/]+)\/\%([^/]+)', config_directory['full_path']) if not path_parts or len(path_parts.groups()) != 2: return self.default_folder_path_definition path_part_groups = path_parts.groups() self.cached_folder_path_definition = [ (path_part_groups[0], config_directory[path_part_groups[0]]), (path_part_groups[1], config_directory[path_part_groups[1]]), ] return self.cached_folder_path_definition
def get_file_name_definition(self): """Returns a list of folder definitions. Each element in the list represents a folder. Fallback folders are supported and are nested lists. Return values take the following form. [ ('date', '%Y-%m-%d'), [ ('location', '%city'), ('album', ''), ('"Unknown Location", '') ] ] :returns: list """ # If we've done this already then return it immediately without # incurring any extra work if self.cached_file_name_definition is not None: return self.cached_file_name_definition config = load_config() # If File is in the config we assume name and its # corresponding values are also present config_file = self.default_file_name_definition if('File' in config): config_file = config['File'] # Find all subpatterns of name that map to the components of the file's # name. # I.e. %date-%original_name-%title.%extension => ['date', 'original_name', 'title', 'extension'] #noqa path_parts = re.findall( '(\%[a-z_]+)', config_file['name'] ) if not path_parts or len(path_parts) == 0: return (config_file['name'], self.default_file_name_definition) self.cached_file_name_definition = [] for part in path_parts: if part in config_file: part = part[1:] self.cached_file_name_definition.append( [(part, config_file[part])] ) else: this_part = [] for p in part.split('|'): p = p[1:] this_part.append( (p, config_file[p] if p in config_file else '') ) self.cached_file_name_definition.append(this_part) self.cached_file_name_definition = (config_file['name'], self.cached_file_name_definition) return self.cached_file_name_definition
def get_folder_path_definition(self): """Returns a list of folder definitions. Each element in the list represents a folder. Fallback folders are supported and are nested lists. Return values take the following form. [ ('date', '%Y-%m-%d'), [ ('location', '%city'), ('album', ''), ('"Unknown Location", '') ] ] :returns: list """ # If we've done this already then return it immediately without # incurring any extra work if self.cached_folder_path_definition is not None: return self.cached_folder_path_definition config = load_config() # If Directory is in the config we assume full_path and its # corresponding values (date, location) are also present config_directory = self.default_folder_path_definition if ('Directory' in config): config_directory = config['Directory'] # Find all subpatterns of full_path that map to directories. # I.e. %foo/%bar => ['foo', 'bar'] # I.e. %foo/%bar|%example|"something" => ['foo', 'bar|example|"something"'] path_parts = re.findall('(\%[^/]+)', config_directory['full_path']) if not path_parts or len(path_parts) == 0: return self.default_folder_path_definition self.cached_folder_path_definition = [] for part in path_parts: if part in config_directory: part = part[1:] self.cached_folder_path_definition.append([ (part, config_directory[part]) ]) elif part in self.default_parts: part = part[1:] self.cached_folder_path_definition.append([(part, '')]) else: this_part = [] for p in part.split('|'): p = p[1:] this_part.append( (p, config_directory[p] if p in config_directory else '')) self.cached_folder_path_definition.append(this_part) return self.cached_folder_path_definition
def test_load_config_singleton_no_file(): if hasattr(load_config, 'config'): del load_config.config config = load_config() if hasattr(load_config, 'config'): del load_config.config assert config == {}, config
def get_dynamic_path(self, part, mask, metadata): """Parse a specific folder's name given a mask and metadata. :param part: Name of the part as defined in the path (i.e. date from %date) :param mask: Mask representing the template for the path (i.e. %city %state :param metadata: Metadata dictionary. :returns: str """ # Each part has its own custom logic and we evaluate a single part and return # the evaluated string. if part in ('custom'): custom_parts = re.findall('(%[a-z_]+)', mask) folder = mask for i in custom_parts: folder = folder.replace( i, self.get_dynamic_path(i[1:], i, metadata) ) return folder elif part in ('date'): config = load_config() # If Directory is in the config we assume full_path and its # corresponding values (date, location) are also present config_directory = self.default_folder_path_definition if('Directory' in config): config_directory = config['Directory'] date_mask = '' if 'date' in config_directory: date_mask = config_directory['date'] return time.strftime(date_mask, metadata['date_taken']) elif part in ('day', 'month', 'year'): return time.strftime(mask, metadata['date_taken']) elif part in ('location', 'city', 'state', 'country'): place_name = geolocation.place_name( metadata['latitude'], metadata['longitude'] ) location_parts = re.findall('(%[^%]+)', mask) parsed_folder_name = self.parse_mask_for_location( mask, location_parts, place_name, ) return parsed_folder_name elif part in ('album', 'camera_make', 'camera_model'): if metadata[part]: return metadata[part] elif part.startswith('"') and part.endswith('"'): # Fallback string return part[1:-1] return ''
def test_load_config_singleton_success(): with open('%s/config.ini-singleton-success' % gettempdir(), 'w') as f: f.write(""" [MapQuest] key=your-api-key-goes-here prefer_english_names=False """) if hasattr(load_config, 'config'): del load_config.config config = load_config() assert config['MapQuest']['key'] == 'your-api-key-goes-here', config.get( 'MapQuest', 'key') config.set('MapQuest', 'key', 'new-value') config = load_config() if hasattr(load_config, 'config'): del load_config.config assert config['MapQuest']['key'] == 'new-value', config.get( 'MapQuest', 'key')
def get_key(): global __KEY__ if __KEY__ is not None: return __KEY__ config_file = '%s/config.ini' % constants.application_directory if not path.exists(config_file): return None config = load_config() if('MapQuest' not in config): return None __KEY__ = config['MapQuest']['key'] return __KEY__
def _import(destination, source, file, album_from_folder, trash, allow_duplicates, debug, exclude_regex, paths): """Import files or directories by reading their EXIF and organizing them accordingly. """ constants.debug = debug has_errors = False result = Result() destination = _decode(destination) destination = os.path.abspath(os.path.expanduser(destination)) files = set() paths = set(paths) if source: source = _decode(source) paths.add(source) if file: paths.add(file) # if no exclude list was passed in we check if there's a config if len(exclude_regex) == 0: config = load_config() if 'Exclusions' in config: exclude_regex = [ value for key, value in config.items('Exclusions') ] exclude_regex_list = set(exclude_regex) for path in paths: path = os.path.expanduser(path) if os.path.isdir(path): files.update( FILESYSTEM.get_all_files(path, None, exclude_regex_list)) else: if not FILESYSTEM.should_exclude(path, exclude_regex_list, True): files.add(path) for current_file in files: dest_path = import_file(current_file, destination, album_from_folder, trash, allow_duplicates) result.append((current_file, dest_path)) has_errors = has_errors is True or not dest_path result.write() if has_errors: sys.exit(1)
def get_prefer_english_names(): global __PREFER_ENGLISH_NAMES__ if __PREFER_ENGLISH_NAMES__ is not None: return __PREFER_ENGLISH_NAMES__ config_file = '%s/config.ini' % constants.application_directory if not path.exists(config_file): return False config = load_config() if ('MapQuest' not in config): return False if ('prefer_english_names' not in config['MapQuest']): return False __PREFER_ENGLISH_NAMES__ = bool(config['MapQuest']['prefer_english_names']) return __PREFER_ENGLISH_NAMES__
def get_file_name(self, metadata): """Generate file name for a photo or video using its metadata. Originally we hardcoded the file name to include an ISO date format. We use an ISO8601-like format for the file name prefix. Instead of colons as the separator for hours, minutes and seconds we use a hyphen. https://en.wikipedia.org/wiki/ISO_8601#General_principles PR #225 made the file name customizable and fixed issues #107 #110 #111. https://github.com/jmathai/elodie/pull/225 :param media: A Photo or Video instance :type media: :class:`~elodie.media.photo.Photo` or :class:`~elodie.media.video.Video` :returns: str or None for non-photo or non-videos """ if(metadata is None): return None # Get the name template and definition. # Name template is in the form %date-%original_name-%title.%extension # Definition is in the form # [ # [('date', '%Y-%m-%d_%H-%M-%S')], # [('original_name', '')], [('title', '')], // contains a fallback # [('extension', '')] # ] name_template, definition = self.get_file_name_definition() name = name_template for parts in definition: this_value = None for this_part in parts: part, mask = this_part if part in ('date', 'day', 'month', 'year'): this_value = time.strftime(mask, metadata['date_taken']) break elif part in ('location', 'city', 'state', 'country'): place_name = geolocation.place_name( metadata['latitude'], metadata['longitude'] ) location_parts = re.findall('(%[^%]+)', mask) this_value = self.parse_mask_for_location( mask, location_parts, place_name, ) break elif part in ('album', 'extension', 'title'): if metadata[part]: this_value = re.sub(self.whitespace_regex, '-', metadata[part].strip()) break elif part in ('original_name'): # First we check if we have metadata['original_name']. # We have to do this for backwards compatibility because # we original did not store this back into EXIF. if metadata[part]: this_value = os.path.splitext(metadata['original_name'])[0] else: # We didn't always store original_name so this is # for backwards compatability. # We want to remove the hardcoded date prefix we used # to add to the name. # This helps when re-running the program on file # which were already processed. this_value = re.sub( '^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-', '', metadata['base_name'] ) if(len(this_value) == 0): this_value = metadata['base_name'] # Lastly we want to sanitize the name this_value = re.sub(self.whitespace_regex, '-', this_value.strip()) elif part.startswith('"') and part.endswith('"'): this_value = part[1:-1] break # Here we replace the placeholder with it's corresponding value. # Check if this_value was not set so that the placeholder # can be removed completely. # For example, %title- will be replaced with '' # Else replace the placeholder (i.e. %title) with the value. if this_value is None: name = re.sub( #'[^a-z_]+%{}'.format(part), '[^a-zA-Z0-9_]+%{}'.format(part), '', name, ) else: name = re.sub( '%{}'.format(part), this_value, name, ) config = load_config() if('File' in config and 'capitalization' in config['File'] and config['File']['capitalization'] == 'upper'): return name.upper() else: return name.lower()