def get_save_dir(self, force_new=False): if self.download_folder is None or force_new: # we must change the folder name, because it has not been set manually fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH) if not fn_template: fn_template = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH) # Find a unique folder name for this podcast download_folder = self.find_unique_folder_name(fn_template) # Try removing the download folder if it has been created previously if self.download_folder is not None: folder = os.path.join(gpodder.downloads, self.download_folder) try: os.rmdir(folder) except OSError: logger.info("Old download folder is kept for %s", self.url) logger.info("Updating download_folder of %s to %s", self.url, download_folder) self.download_folder = download_folder self.save() save_dir = os.path.join(gpodder.downloads, self.download_folder) # Avoid encoding errors for OS-specific functions (bug 1570) save_dir = util.sanitize_encoding(save_dir) # Create save_dir if it does not yet exist if not util.make_directory(save_dir): logger.error("Could not create save_dir: %s", save_dir) return save_dir
def make_filename(self, current_filename, title, sortdate, podcast_title): dirname = os.path.dirname(current_filename) filename = os.path.basename(current_filename) basename, ext = os.path.splitext(filename) ext = '.' + util.sanitize_filename(ext, PodcastEpisode.MAX_FILENAME_LENGTH) new_basename = [] new_basename.append(title) if self.config.add_podcast_title: new_basename.insert(0, podcast_title) if self.config.add_sortdate: new_basename.insert(0, sortdate) new_basename = ' - '.join(new_basename) # Remove unwanted characters and shorten filename (#494) new_basename = util.sanitize_filename( new_basename, PodcastEpisode.MAX_FILENAME_LENGTH) # add extension after sanitization, to keep it even if filename is longer than limit # (it's unlikely that new_basename + ext is longer than is allowed on platform). new_filename = os.path.join(dirname, new_basename + ext) if new_filename == current_filename: return current_filename for filename in util.generate_names(new_filename): # Avoid filename collisions if not os.path.exists(filename): return filename
def get_save_dir(self, force_new=False): if self.download_folder is None or force_new: # we must change the folder name, because it has not been set manually fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH) if not fn_template: fn_template = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH) # Find a unique folder name for this podcast download_folder = self.find_unique_folder_name(fn_template) # Try renaming the download folder if it has been created previously if self.download_folder is not None: old_folder = os.path.join(self.model.core.downloads, self.download_folder) new_folder = os.path.join(self.model.core.downloads, download_folder) try: os.rename(old_folder, new_folder) except Exception as ex: logger.info('Cannot rename old download folder: %s', old_folder, exc_info=True) logger.info('Updating download_folder of %s to %s', self.url, download_folder) self.download_folder = download_folder self.save() save_dir = os.path.join(self.model.core.downloads, self.download_folder) # Create save_dir if it does not yet exist if not util.make_directory(save_dir): logger.error('Could not create save_dir: %s', save_dir) return save_dir
def make_filename(self, current_filename, title, sortdate, podcast_title): dirname = os.path.dirname(current_filename) filename = os.path.basename(current_filename) basename, ext = os.path.splitext(filename) ext = '.' + util.sanitize_filename(ext, PodcastEpisode.MAX_FILENAME_LENGTH) new_basename = [] new_basename.append(title) if self.config.add_podcast_title: new_basename.insert(0, podcast_title) if self.config.add_sortdate: new_basename.insert(0, sortdate) new_basename = ' - '.join(new_basename) # Remove unwanted characters and shorten filename (#494) new_basename = util.sanitize_filename(new_basename, PodcastEpisode.MAX_FILENAME_LENGTH) # add extension after sanitization, to keep it even if filename is longer than limit # (it's unlikely that new_basename + ext is longer than is allowed on platform). new_filename = os.path.join(dirname, new_basename + ext) if new_filename == current_filename: return current_filename for filename in util.generate_names(new_filename): # Avoid filename collisions if not os.path.exists(filename): return filename
def get_save_dir(self, force_new=False): if self.download_folder is None or force_new: # we must change the folder name, because it has not been set manually fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH) if not fn_template: fn_template = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH) # Find a unique folder name for this podcast download_folder = self.find_unique_folder_name(fn_template) # Try removing the download folder if it has been created previously if self.download_folder is not None: folder = os.path.join(gpodder.downloads, self.download_folder) try: os.rmdir(folder) except OSError: logger.info('Old download folder is kept for %s', self.url) logger.info('Updating download_folder of %s to %s', self.url, download_folder) self.download_folder = download_folder self.save() save_dir = os.path.join(gpodder.downloads, self.download_folder) # Create save_dir if it does not yet exist if not util.make_directory(save_dir): logger.error('Could not create save_dir: %s', save_dir) return save_dir
def add_track(self, episode): self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore')) if self._config.fssync_channel_subfolders: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename(folder, self._config.mp3_player_max_filename_length) folder = os.path.join(self.destination, folder) else: folder = self.destination from_file = util.sanitize_encoding(self.convert_track(episode)) filename_base = util.sanitize_filename(episode.sync_filename(self._config.custom_sync_name_enabled, self._config.custom_sync_name), self._config.mp3_player_max_filename_length) to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) to_file = os.path.join(folder, to_file) if not os.path.exists(folder): try: os.makedirs(folder) except: log('Cannot create folder on MP3 player: %s', folder, sender=self) return False if self._config.mp3_player_use_scrobbler_log and not episode.is_played: # FIXME: This misses some things when channel.title<>album tag which is what # the scrobbling entity will be using. if [episode.channel.title, episode.title] in self.scrobbler_log: log('Marking "%s" from "%s" as played', episode.title, episode.channel.title, sender=self) episode.mark(is_played=True) if self._config.rockbox_copy_coverart and not os.path.exists(os.path.join(folder, 'cover.bmp')): log('Creating Rockbox album art for "%s"', episode.channel.title, sender=self) self.copy_player_cover_art(folder, from_file, \ 'cover.bmp', 'BMP', self._config.rockbox_coverart_size) if self._config.custom_player_copy_coverart \ and not os.path.exists(os.path.join(folder, \ self._config.custom_player_coverart_name)): log('Creating custom player album art for "%s"', episode.channel.title, sender=self) self.copy_player_cover_art(folder, from_file, \ self._config.custom_player_coverart_name, \ self._config.custom_player_coverart_format, \ self._config.custom_player_coverart_size) if not os.path.exists(to_file): log('Copying %s => %s', os.path.basename(from_file), to_file.decode(util.encoding), sender=self) return self.copy_file_progress(from_file, to_file) return True
def get_save_dir(self): urldigest = hashlib.md5(self.url).hexdigest() sanitizedurl = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH) if self.foldername is None or (self.auto_foldername and (self.foldername == urldigest or self.foldername.startswith(sanitizedurl))): # we must change the folder name, because it has not been set manually fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH) # if this is an empty string, try the basename if len(fn_template) == 0: log('That is one ugly feed you have here! (Report this to bugs.gpodder.org: %s)', self.url, sender=self) fn_template = util.sanitize_filename(os.path.basename(self.url), self.MAX_FOLDERNAME_LENGTH) # If the basename is also empty, use the first 6 md5 hexdigest chars of the URL if len(fn_template) == 0: log('That is one REALLY ugly feed you have here! (Report this to bugs.gpodder.org: %s)', self.url, sender=self) fn_template = urldigest # no need for sanitize_filename here # Find a unique folder name for this podcast wanted_foldername = self.find_unique_folder_name(fn_template) # if the foldername has not been set, check if the (old) md5 filename exists if self.foldername is None and os.path.exists(os.path.join(self.download_dir, urldigest)): log('Found pre-0.15.0 download folder for %s: %s', self.title, urldigest, sender=self) self.foldername = urldigest # we have a valid, new folder name in "current_try" -> use that! if self.foldername is not None and wanted_foldername != self.foldername: # there might be an old download folder crawling around - move it! new_folder_name = os.path.join(self.download_dir, wanted_foldername) old_folder_name = os.path.join(self.download_dir, self.foldername) if os.path.exists(old_folder_name): if not os.path.exists(new_folder_name): # Old folder exists, new folder does not -> simply rename log('Renaming %s => %s', old_folder_name, new_folder_name, sender=self) os.rename(old_folder_name, new_folder_name) else: # Both folders exist -> move files and delete old folder log('Moving files from %s to %s', old_folder_name, new_folder_name, sender=self) for file in glob.glob(os.path.join(old_folder_name, '*')): shutil.move(file, new_folder_name) log('Removing %s', old_folder_name, sender=self) shutil.rmtree(old_folder_name, ignore_errors=True) log('Updating foldername of %s to "%s".', self.url, wanted_foldername, sender=self) self.foldername = wanted_foldername self.save() save_dir = os.path.join(self.download_dir, self.foldername) # Create save_dir if it does not yet exist if not util.make_directory( save_dir): log( 'Could not create save_dir: %s', save_dir, sender = self) return save_dir
def add_track(self, episode, reporthook=None): self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore')) if self._config.device_sync.one_folder_per_podcast: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename( folder, self._config.device_sync.max_filename_length) folder = os.path.join(self.destination, folder) else: folder = self.destination folder = util.sanitize_encoding(folder) filename = episode.local_filename(create=False) # The file has to exist, if we ought to transfer it, and therefore, # local_filename(create=False) must never return None as filename assert filename is not None from_file = util.sanitize_encoding(filename) filename_base = util.sanitize_filename( episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) to_file = util.sanitize_encoding(os.path.join(folder, to_file)) if not os.path.exists(folder): try: os.makedirs(folder) except: logger.error('Cannot create folder on MP3 player: %s', folder) return False if not os.path.exists(to_file): logger.info('Copying %s => %s', os.path.basename(from_file), to_file.decode(util.encoding)) self.copy_file_progress(from_file, to_file, reporthook) return True
def add_track(self, episode,reporthook=None): self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore')) if self._config.device_sync.one_folder_per_podcast: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename(folder, self._config.device_sync.max_filename_length) folder = os.path.join(self.destination, folder) else: folder = self.destination folder = util.sanitize_encoding(folder) filename = episode.local_filename(create=False) # The file has to exist, if we ought to transfer it, and therefore, # local_filename(create=False) must never return None as filename assert filename is not None from_file = util.sanitize_encoding(filename) filename_base = util.sanitize_filename(episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) to_file = util.sanitize_encoding(os.path.join(folder, to_file)) if not os.path.exists(folder): try: os.makedirs(folder) except: logger.error('Cannot create folder on MP3 player: %s', folder) return False if not os.path.exists(to_file): logger.info('Copying %s => %s', os.path.basename(from_file), to_file.decode(util.encoding)) self.copy_file_progress(from_file, to_file, reporthook) return True
def make_filename(self, current_filename, title, sortdate, podcast_title): dirname = os.path.dirname(current_filename) filename = os.path.basename(current_filename) basename, ext = os.path.splitext(filename) new_basename = [] new_basename.append(util.sanitize_encoding(title) + ext) if self.config.add_podcast_title: new_basename.insert(0, podcast_title) if self.config.add_sortdate: new_basename.insert(0, sortdate) new_basename = ' - '.join(new_basename) # On Windows, force ASCII encoding for filenames (bug 1724) new_basename = util.sanitize_filename(new_basename, use_ascii=gpodder.ui.win32) new_filename = os.path.join(dirname, new_basename) if new_filename == current_filename: return current_filename for filename in util.generate_names(new_filename): # Avoid filename collisions if not os.path.exists(filename): return filename
def __init__(self, config, playlist_name): self._config = config self.linebreak = '\r\n' self.playlist_file = util.sanitize_filename( playlist_name, self._config.device_sync.max_filename_length) + '.m3u' device_folder = util.new_gio_file( self._config.device_sync.device_folder) self.playlist_folder = device_folder.resolve_relative_path( self._config.device_sync.playlists.folder) self.mountpoint = None try: self.mountpoint = self.playlist_folder.find_enclosing_mount( ).get_root() except GLib.Error as err: logger.error('find_enclosing_mount folder %s failed: %s', self.playlist_folder.get_uri(), err.message) if not self.mountpoint: self.mountpoint = self.playlist_folder logger.warning( 'could not find mount point for MP3 player - using %s as MP3 player root', self.mountpoint.get_uri()) self.playlist_absolute_filename = self.playlist_folder.resolve_relative_path( self.playlist_file)
def add_track(self, episode): self.notify('status', _('Adding %s...') % episode.title) filename = str(self.convert_track(episode)) log("sending " + filename + " (" + episode.title + ").", sender=self) try: # verify free space needed = util.calculate_size(filename) free = self.get_free_space() if needed > free: log('Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed), sender=self) self.cancelled = True return False # fill metadata metadata = pymtp.LIBMTP_Track() metadata.title = str(episode.title) metadata.artist = str(episode.channel.title) metadata.album = str(episode.channel.title) metadata.genre = "podcast" metadata.date = self.__date_to_mtp(episode.pubDate) metadata.duration = get_track_length(str(filename)) # send the file self.__MTPDevice.send_track_from_file(filename, util.sanitize_filename(metadata.title)+episode.extension(), metadata, 0, callback=self.__callback) except: log('unable to add episode %s', episode.title, sender=self, traceback=True) return False return True
def write_m3u(self, episodes): """ write the list into the playlist on the device """ logger.info('Writing playlist file: %s', self.playlist_file) if not util.make_directory(self.playlist_folder): raise IOError( _('Folder %s could not be created.') % self.playlist_folder, _('Error writing playlist')) else: fp = open(os.path.join(self.playlist_folder, self.playlist_file), 'w') fp.write('#EXTM3U%s' % self.linebreak) for current_episode in episodes: filename_base = util.sanitize_filename( current_episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) filename = filename_base + os.path.splitext( current_episode.local_filename(create=False))[1].lower() filename = self.get_filename_for_playlist(current_episode) fp.write(self.build_extinf(filename)) filename = self.get_absolute_filename_for_playlist( current_episode) fp.write(filename) fp.write(self.linebreak) fp.close()
def episode_on_device(self, episode): e = util.sanitize_filename( episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) return self._track_on_device(e)
def episode_on_device(self, episode): e = util.sanitize_filename( episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name ), self._config.device_sync.max_filename_length, ) return self._track_on_device(e)
def add_track(self, episode): self.notify('status', _('Adding %s...') % episode.title) filename = str(self.convert_track(episode)) logger.info("sending %s (%s).", filename, episode.title) try: # verify free space needed = util.calculate_size(filename) free = self.get_free_space() if needed > free: logger.error('Not enough space on device %s: %s available, but ' 'need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed)) self.cancelled = True return False # fill metadata metadata = pymtp.LIBMTP_Track() metadata.title = str(episode.title) metadata.artist = str(episode.channel.title) metadata.album = str(episode.channel.title) metadata.genre = "podcast" metadata.date = self.__date_to_mtp(episode.published) metadata.duration = get_track_length(str(filename)) folder_name = '' if episode.mimetype.startswith('audio/') and self._config.mtp_audio_folder: folder_name = self._config.mtp_audio_folder if episode.mimetype.startswith('video/') and self._config.mtp_video_folder: folder_name = self._config.mtp_video_folder if episode.mimetype.startswith('image/') and self._config.mtp_image_folder: folder_name = self._config.mtp_image_folder if folder_name != '' and self._config.mtp_podcast_folders: folder_name += os.path.sep + str(episode.channel.title) # log('Target MTP folder: %s' % folder_name) if folder_name == '': folder_id = 0 else: folder_id = self.__MTPDevice.mkdir(folder_name) # send the file to_file = util.sanitize_filename(metadata.title) + episode.extension() self.__MTPDevice.send_track_from_file(filename, to_file, metadata, folder_id, callback=self.__callback) if gpodder.user_hooks is not None: gpodder.user_hooks.on_file_copied_to_mtp(self, filename, to_file) except: logger.error('unable to add episode %s', episode.title) return False return True
def __init__(self, config, playlist_name): self._config=config self.linebreak = '\r\n' self.playlist_file=util.sanitize_filename(playlist_name + '.m3u') self.playlist_folder = os.path.join(self._config.device_sync.device_folder, self._config.device_sync.playlists.folder) self.mountpoint = util.find_mount_point(util.sanitize_encoding(self.playlist_folder)) if self.mountpoint == '/': self.mountpoint = self.playlist_folder logger.warning('MP3 player resides on / - using %s as MP3 player root', self.mountpoint) self.playlist_absolute_filename=os.path.join(self.playlist_folder, self.playlist_file)
def __init__(self, config, playlist_name): self._config = config self.linebreak = '\r\n' self.playlist_file = util.sanitize_filename(playlist_name, self._config.device_sync.max_filename_length) + '.m3u' self.playlist_folder = os.path.join(self._config.device_sync.device_folder, self._config.device_sync.playlists.folder) self.mountpoint = util.find_mount_point(self.playlist_folder) if self.mountpoint == '/': self.mountpoint = self.playlist_folder logger.warning('MP3 player resides on / - using %s as MP3 player root', self.mountpoint) self.playlist_absolute_filename = os.path.join(self.playlist_folder, self.playlist_file)
def get_filename_for_playlist(self, episode): """ get the filename for the given episode for the playlist """ filename_base = util.sanitize_filename(episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) filename = filename_base + os.path.splitext(episode.local_filename(create=False))[1].lower() return filename
def get_absolute_filename_for_playlist(self, episode): """ get the filename including full path for the given episode for the playlist """ filename = self.get_filename_for_playlist(episode) if self._config.device_sync.one_folder_per_podcast: filename = os.path.join(util.sanitize_filename(episode.channel.title), filename) if self._config.device_sync.playlist.absolute_path: filename = os.path.join(util.relpath(self.mountpoint, self._config.device_sync.device_folder), filename) return filename
def get_episode_folder_on_device(self, episode): if self._config.device_sync.one_folder_per_podcast: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename( folder, self._config.device_sync.max_filename_length) folder = os.path.join(self.destination, folder) else: folder = self.destination return folder
def get_episode_folder_on_device(self, episode): if self._config.device_sync.one_folder_per_podcast: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename(folder, self._config.device_sync.max_filename_length) folder = os.path.join(self.destination, folder) else: folder = self.destination return util.sanitize_encoding(folder)
def add_track(self, episode): self.notify('status', _('Adding %s...') % episode.title) filename = str(self.convert_track(episode)) log("sending %s (%s).", filename, episode.title, sender=self) try: # verify free space needed = util.calculate_size(filename) free = self.get_free_space() if needed > free: log('Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed), sender=self) self.cancelled = True return False # fill metadata metadata = pymtp.LIBMTP_Track() metadata.title = str(episode.title) metadata.artist = str(episode.channel.title) metadata.album = str(episode.channel.title) metadata.genre = "podcast" metadata.date = self.__date_to_mtp(episode.pubDate) metadata.duration = get_track_length(str(filename)) folder_name = '' if episode.mimetype.startswith('audio/') and self._config.mtp_audio_folder: folder_name = self._config.mtp_audio_folder if episode.mimetype.startswith('video/') and self._config.mtp_video_folder: folder_name = self._config.mtp_video_folder if episode.mimetype.startswith('image/') and self._config.mtp_image_folder: folder_name = self._config.mtp_image_folder if folder_name != '' and self._config.mtp_podcast_folders: folder_name += os.path.sep + str(episode.channel.title) # log('Target MTP folder: %s' % folder_name) if folder_name == '': folder_id = 0 else: folder_id = self.__MTPDevice.mkdir(folder_name) # send the file to_file = util.sanitize_filename(metadata.title) + episode.extension() self.__MTPDevice.send_track_from_file(filename, to_file, metadata, folder_id, callback=self.__callback) if gpodder.user_hooks is not None: gpodder.user_hooks.on_file_copied_to_mtp(self, filename, to_file) except: log('unable to add episode %s', episode.title, sender=self, traceback=True) return False return True
def find_unique_file_name(self, url, filename, extension): current_try = util.sanitize_filename(filename, self.MAX_FILENAME_LENGTH)+extension next_try_id = 2 lookup_url = None if self.filename == current_try and current_try is not None: # We already have this filename - good! return current_try while self.db.episode_filename_exists(current_try): current_try = '%s (%d)%s' % (filename, next_try_id, extension) next_try_id += 1 return current_try
def episode_foldername_on_device(config, episode): """ :param gpodder.config.Config config: configuration (for sync options) :param gpodder.model.PodcastEpisode episode: episode to get folder name for :return str: folder name to save episode to on device """ if config.device_sync.one_folder_per_podcast: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename(folder, config.device_sync.max_filename_length) else: folder = None return folder
def find_unique_folder_name(self, foldername): # Remove trailing dots to avoid errors on Windows (bug 600) foldername = foldername.strip().rstrip('.') current_try = util.sanitize_filename(foldername, \ self.MAX_FOLDERNAME_LENGTH) next_try_id = 2 while True: if self.db.channel_foldername_exists(current_try): current_try = '%s (%d)' % (foldername, next_try_id) next_try_id += 1 else: return current_try
def episode_foldername_on_device(config, episode): """ :param gpodder.config.Config config: configuration (for sync options) :param gpodder.model.PodcastEpisode episode: episode to get folder name for :return str: folder name to save episode to on device """ if config.device_sync.one_folder_per_podcast: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename(folder, config.device_sync.max_filename_length) else: folder = None return folder
def make_filename(self, current_filename, title): dirname = os.path.dirname(current_filename) filename = os.path.basename(current_filename) basename, ext = os.path.splitext(filename) new_basename = util.sanitize_encoding(title) + ext new_basename = util.sanitize_filename(new_basename) new_filename = os.path.join(dirname, new_basename) if new_filename == current_filename: return current_filename for filename in util.generate_names(new_filename): # Avoid filename collisions if not os.path.exists(filename): return filename
def update_m3u_playlist(self, downloaded_episodes=None): if gl.config.create_m3u_playlists: if downloaded_episodes is None: downloaded_episodes=self.load_downloaded_episodes() fn=util.sanitize_filename(self.title) if len(fn) == 0: fn=os.path.basename(self.save_dir) m3u_filename=os.path.join(gl.downloaddir, fn+'.m3u') log('Writing playlist to %s', m3u_filename, sender=self) f=open(m3u_filename, 'w') f.write('#EXTM3U\n') for episode in sorted(downloaded_episodes): filename=episode.local_filename() if os.path.dirname(filename).startswith(os.path.dirname(m3u_filename)): filename=filename[len(os.path.dirname(m3u_filename)+os.sep):] f.write('#EXTINF:0,'+self.title+' - '+episode.title+' ('+episode.pubDate+')\n') f.write(filename+'\n') f.close()
def make_filename(self, current_filename, title, sortdate = ''): dirname = os.path.dirname(current_filename) filename = os.path.basename(current_filename) basename, ext = os.path.splitext(filename) new_basename = sortdate + util.sanitize_encoding(title) + ext # On Windows, force ASCII encoding for filenames (bug 1724) new_basename = util.sanitize_filename(new_basename, use_ascii=gpodder.ui.win32) new_filename = os.path.join(dirname, new_basename) if new_filename == current_filename: return current_filename for filename in util.generate_names(new_filename): # Avoid filename collisions if not os.path.exists(filename): return filename
def make_filename(self, current_filename, title): dirname = os.path.dirname(current_filename) filename = os.path.basename(current_filename) basename, ext = os.path.splitext(filename) new_basename = util.sanitize_encoding(title) + ext # On Windows, force ASCII encoding for filenames (bug 1724) new_basename = util.sanitize_filename(new_basename, use_ascii=gpodder.ui.win32) new_filename = os.path.join(dirname, new_basename) if new_filename == current_filename: return current_filename for filename in util.generate_names(new_filename): # Avoid filename collisions if not os.path.exists(filename): return filename
def get_episode_file_on_device(self, episode): # get the local file from_file = util.sanitize_encoding(episode.local_filename(create=False)) # get the formated base name filename_base = util.sanitize_filename(episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) # add the file extension to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) return to_file
def get_episode_file_on_device(self, episode): # get the local file from_file = util.sanitize_encoding(episode.local_filename(create=False)) # get the formated base name filename_base = util.sanitize_filename(episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) # add the file extension to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) return to_file
def write_m3u(self, episodes): """ write the list into the playlist on the device """ logger.info('Writing playlist file: %s', self.playlist_file) if not util.make_directory(self.playlist_folder): raise IOError(_('Folder %s could not be created.') % self.playlist_folder, _('Error writing playlist')) else: fp = open(os.path.join(self.playlist_folder, self.playlist_file), 'w') fp.write('#EXTM3U%s' % self.linebreak) for current_episode in episodes: filename_base = util.sanitize_filename(current_episode.sync_filename( self._config.device_sync.custom_sync_name_enabled, self._config.device_sync.custom_sync_name), self._config.device_sync.max_filename_length) filename = filename_base + os.path.splitext(current_episode.local_filename(create=False))[1].lower() filename = self.get_filename_for_playlist(current_episode) fp.write(self.build_extinf(filename)) filename = self.get_absolute_filename_for_playlist(current_episode) fp.write(filename) fp.write(self.linebreak) fp.close()
def episode_filename_on_device(config, episode): """ :param gpodder.config.Config config: configuration (for sync options) :param gpodder.model.PodcastEpisode episode: episode to get filename for :return str: basename minus extension to use to save episode on device """ # get the local file from_file = episode.local_filename(create=False) # get the formated base name filename_base = util.sanitize_filename(episode.sync_filename( config.device_sync.custom_sync_name_enabled, config.device_sync.custom_sync_name), config.device_sync.max_filename_length) # add the file extension to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) return to_file
def episode_filename_on_device(config, episode): """ :param gpodder.config.Config config: configuration (for sync options) :param gpodder.model.PodcastEpisode episode: episode to get filename for :return str: basename minus extension to use to save episode on device """ # get the local file from_file = episode.local_filename(create=False) # get the formated base name filename_base = util.sanitize_filename( episode.sync_filename(config.device_sync.custom_sync_name_enabled, config.device_sync.custom_sync_name), config.device_sync.max_filename_length) # add the file extension to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) return to_file
def local_filename(self, create, force_update=False, check_only=False, template=None, return_wanted_filename=False): """Get (and possibly generate) the local saving filename Pass create=True if you want this function to generate a new filename if none exists. You only want to do this when planning to create/download the file after calling this function. Normally, you should pass create=False. This will only create a filename when the file already exists from a previous version of gPodder (where we used md5 filenames). If the file does not exist (and the filename also does not exist), this function will return None. If you pass force_update=True to this function, it will try to find a new (better) filename and move the current file if this is the case. This is useful if (during the download) you get more information about the file, e.g. the mimetype and you want to include this information in the file name generation process. If check_only=True is passed to this function, it will never try to rename the file, even if would be a good idea. Use this if you only want to check if a file exists. If "template" is specified, it should be a filename that is to be used as a template for generating the "real" filename. The generated filename is stored in the database for future access. If return_wanted_filename is True, the filename will not be written to the database, but simply returned by this function (for use by the "import external downloads" feature). """ if self.download_filename is None and (check_only or not create): return None ext = self.extension(may_call_local_filename=False).encode("utf-8", "ignore") if not check_only and (force_update or not self.download_filename): # Avoid and catch gPodder bug 1440 and similar situations if template == "": logger.warn("Empty template. Report this podcast URL %s", self.channel.url) template = None # Try to find a new filename for the current file if template is not None: # If template is specified, trust the template's extension episode_filename, ext = os.path.splitext(template) else: episode_filename, _ = util.filename_from_url(self.url) fn_template = util.sanitize_filename(episode_filename, self.MAX_FILENAME_LENGTH) if "redirect" in fn_template and template is None: # This looks like a redirection URL - force URL resolving! logger.warn("Looks like a redirection to me: %s", self.url) url = util.get_real_url(self.channel.authenticate_url(self.url)) logger.info("Redirection resolved to: %s", url) episode_filename, _ = util.filename_from_url(url) fn_template = util.sanitize_filename(episode_filename, self.MAX_FILENAME_LENGTH) # Use title for YouTube, Vimeo and Soundcloud downloads if ( youtube.is_video_link(self.url) or vimeo.is_video_link(self.url) or escapist_videos.is_video_link(self.url) or fn_template == "stream" ): sanitized = util.sanitize_filename(self.title, self.MAX_FILENAME_LENGTH) if sanitized: fn_template = sanitized # If the basename is empty, use the md5 hexdigest of the URL if not fn_template or fn_template.startswith("redirect."): logger.error("Report this feed: Podcast %s, episode %s", self.channel.url, self.url) fn_template = hashlib.md5(self.url).hexdigest() # Find a unique filename for this episode wanted_filename = self.find_unique_file_name(fn_template, ext) if return_wanted_filename: # return the calculated filename without updating the database return wanted_filename # The old file exists, but we have decided to want a different filename if self.download_filename and wanted_filename != self.download_filename: # there might be an old download folder crawling around - move it! new_file_name = os.path.join(self.channel.save_dir, wanted_filename) old_file_name = os.path.join(self.channel.save_dir, self.download_filename) if os.path.exists(old_file_name) and not os.path.exists(new_file_name): logger.info("Renaming %s => %s", old_file_name, new_file_name) os.rename(old_file_name, new_file_name) elif force_update and not os.path.exists(old_file_name): # When we call force_update, the file might not yet exist when we # call it from the downloading code before saving the file logger.info("Choosing new filename: %s", new_file_name) else: logger.warn("%s exists or %s does not", new_file_name, old_file_name) logger.info('Updating filename of %s to "%s".', self.url, wanted_filename) elif self.download_filename is None: logger.info("Setting download filename: %s", wanted_filename) self.download_filename = wanted_filename self.save() return os.path.join( util.sanitize_encoding(self.channel.save_dir), util.sanitize_encoding(self.download_filename) )
def add_track(self, episode): self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore')) if self._config.fssync_channel_subfolders: # Add channel title as subfolder folder = episode.channel.title # Clean up the folder name for use on limited devices folder = util.sanitize_filename(folder, self._config.mp3_player_max_filename_length) folder = os.path.join(self.destination, folder) else: folder = self.destination folder = util.sanitize_encoding(folder) from_file = util.sanitize_encoding(self.convert_track(episode)) filename_base = util.sanitize_filename(episode.sync_filename(self._config.custom_sync_name_enabled, self._config.custom_sync_name), self._config.mp3_player_max_filename_length) to_file = filename_base + os.path.splitext(from_file)[1].lower() # dirty workaround: on bad (empty) episode titles, # we simply use the from_file basename # (please, podcast authors, FIX YOUR RSS FEEDS!) if os.path.splitext(to_file)[0] == '': to_file = os.path.basename(from_file) to_file = util.sanitize_encoding(os.path.join(folder, to_file)) if not os.path.exists(folder): try: os.makedirs(folder) except: log('Cannot create folder on MP3 player: %s', folder, sender=self) return False if self._config.mp3_player_use_scrobbler_log and not episode.is_played: # FIXME: This misses some things when channel.title<>album tag which is what # the scrobbling entity will be using. if [episode.channel.title, episode.title] in self.scrobbler_log: log('Marking "%s" from "%s" as played', episode.title, episode.channel.title, sender=self) episode.mark(is_played=True) if self._config.rockbox_copy_coverart and not os.path.exists(os.path.join(folder, 'cover.bmp')): log('Creating Rockbox album art for "%s"', episode.channel.title, sender=self) self.copy_player_cover_art(folder, from_file, \ 'cover.bmp', 'BMP', self._config.rockbox_coverart_size) if self._config.custom_player_copy_coverart \ and not os.path.exists(os.path.join(folder, \ self._config.custom_player_coverart_name)): log('Creating custom player album art for "%s"', episode.channel.title, sender=self) self.copy_player_cover_art(folder, from_file, \ self._config.custom_player_coverart_name, \ self._config.custom_player_coverart_format, \ self._config.custom_player_coverart_size) if not os.path.exists(to_file): log('Copying %s => %s', os.path.basename(from_file), to_file.decode(util.encoding), sender=self) copied = self.copy_file_progress(from_file, to_file) if copied and gpodder.user_hooks is not None: gpodder.user_hooks.on_file_copied_to_filesystem(self, from_file, to_file) return copied return True
def local_filename(self, create, force_update=False, check_only=False, template=None): """Get (and possibly generate) the local saving filename Pass create=True if you want this function to generate a new filename if none exists. You only want to do this when planning to create/download the file after calling this function. Normally, you should pass create=False. This will only create a filename when the file already exists from a previous version of gPodder (where we used md5 filenames). If the file does not exist (and the filename also does not exist), this function will return None. If you pass force_update=True to this function, it will try to find a new (better) filename and move the current file if this is the case. This is useful if (during the download) you get more information about the file, e.g. the mimetype and you want to include this information in the file name generation process. If check_only=True is passed to this function, it will never try to rename the file, even if would be a good idea. Use this if you only want to check if a file exists. If "template" is specified, it should be a filename that is to be used as a template for generating the "real" filename. The generated filename is stored in the database for future access. """ ext = self.extension(may_call_local_filename=False).encode('utf-8', 'ignore') # For compatibility with already-downloaded episodes, we # have to know md5 filenames if they are downloaded already urldigest = hashlib.md5(self.url).hexdigest() if not create and self.filename is None: urldigest_filename = os.path.join(self.channel.save_dir, urldigest+ext) if os.path.exists(urldigest_filename): # The file exists, so set it up in our database log('Recovering pre-0.15.0 file: %s', urldigest_filename, sender=self) self.filename = urldigest+ext self.auto_filename = 1 self.save() return urldigest_filename return None # We only want to check if the file exists, so don't try to # rename the file, even if it would be reasonable. See also: # http://bugs.gpodder.org/attachment.cgi?id=236 if check_only: if self.filename is None: return None else: return os.path.join(self.channel.save_dir, self.filename) if self.filename is None or force_update or (self.auto_filename and self.filename == urldigest+ext): # Try to find a new filename for the current file if template is not None: # If template is specified, trust the template's extension episode_filename, ext = os.path.splitext(template) else: episode_filename, extension_UNUSED = util.filename_from_url(self.url) fn_template = util.sanitize_filename(episode_filename, self.MAX_FILENAME_LENGTH) if 'redirect' in fn_template and template is None: # This looks like a redirection URL - force URL resolving! log('Looks like a redirection to me: %s', self.url, sender=self) url = util.get_real_url(self.channel.authenticate_url(self.url)) log('Redirection resolved to: %s', url, sender=self) (episode_filename, extension_UNUSED) = util.filename_from_url(url) fn_template = util.sanitize_filename(episode_filename, self.MAX_FILENAME_LENGTH) # Use the video title for YouTube downloads for yt_url in ('http://youtube.com/', 'http://www.youtube.com/'): if self.url.startswith(yt_url): fn_template = util.sanitize_filename(os.path.basename(self.title), self.MAX_FILENAME_LENGTH) # If the basename is empty, use the md5 hexdigest of the URL if len(fn_template) == 0 or fn_template.startswith('redirect.'): log('Report to bugs.gpodder.org: Podcast at %s with episode URL: %s', self.channel.url, self.url, sender=self) fn_template = urldigest # Find a unique filename for this episode wanted_filename = self.find_unique_file_name(self.url, fn_template, ext) # We populate the filename field the first time - does the old file still exist? if self.filename is None and os.path.exists(os.path.join(self.channel.save_dir, urldigest+ext)): log('Found pre-0.15.0 downloaded file: %s', urldigest, sender=self) self.filename = urldigest+ext # The old file exists, but we have decided to want a different filename if self.filename is not None and wanted_filename != self.filename: # there might be an old download folder crawling around - move it! new_file_name = os.path.join(self.channel.save_dir, wanted_filename) old_file_name = os.path.join(self.channel.save_dir, self.filename) if os.path.exists(old_file_name) and not os.path.exists(new_file_name): log('Renaming %s => %s', old_file_name, new_file_name, sender=self) os.rename(old_file_name, new_file_name) elif force_update and not os.path.exists(old_file_name): # When we call force_update, the file might not yet exist when we # call it from the downloading code before saving the file log('Choosing new filename: %s', new_file_name, sender=self) else: log('Warning: %s exists or %s does not.', new_file_name, old_file_name, sender=self) log('Updating filename of %s to "%s".', self.url, wanted_filename, sender=self) elif self.filename is None: log('Setting filename to "%s".', wanted_filename, sender=self) else: log('Should update filename. Stays the same (%s). Good!', \ wanted_filename, sender=self) self.filename = wanted_filename self.save() self.db.commit() return os.path.join(self.channel.save_dir, self.filename)
def local_filename(self, create, force_update=False, check_only=False, template=None, return_wanted_filename=False): """Get (and possibly generate) the local saving filename Pass create=True if you want this function to generate a new filename if none exists. You only want to do this when planning to create/download the file after calling this function. Normally, you should pass create=False. This will only create a filename when the file already exists from a previous version of gPodder (where we used md5 filenames). If the file does not exist (and the filename also does not exist), this function will return None. If you pass force_update=True to this function, it will try to find a new (better) filename and move the current file if this is the case. This is useful if (during the download) you get more information about the file, e.g. the mimetype and you want to include this information in the file name generation process. If check_only=True is passed to this function, it will never try to rename the file, even if would be a good idea. Use this if you only want to check if a file exists. If "template" is specified, it should be a filename that is to be used as a template for generating the "real" filename. The generated filename is stored in the database for future access. If return_wanted_filename is True, the filename will not be written to the database, but simply returned by this function (for use by the "import external downloads" feature). """ if self.download_filename is None and (check_only or not create): return None ext = self.extension(may_call_local_filename=False).encode( 'utf-8', 'ignore') if not check_only and (force_update or not self.download_filename): # Avoid and catch gPodder bug 1440 and similar situations if template == '': logger.warn('Empty template. Report this podcast URL %s', self.channel.url) template = None # Try to find a new filename for the current file if template is not None: # If template is specified, trust the template's extension episode_filename, ext = os.path.splitext(template) else: episode_filename, _ = util.filename_from_url(self.url) fn_template = util.sanitize_filename(episode_filename, self.MAX_FILENAME_LENGTH) if 'redirect' in fn_template and template is None: # This looks like a redirection URL - force URL resolving! logger.warn('Looks like a redirection to me: %s', self.url) url = util.get_real_url(self.channel.authenticate_url( self.url)) logger.info('Redirection resolved to: %s', url) episode_filename, _ = util.filename_from_url(url) fn_template = util.sanitize_filename(episode_filename, self.MAX_FILENAME_LENGTH) # Use title for YouTube, Vimeo and Soundcloud downloads if (youtube.is_video_link(self.url) or vimeo.is_video_link(self.url) or escapist_videos.is_video_link(self.url) or fn_template == 'stream'): sanitized = util.sanitize_filename(self.title, self.MAX_FILENAME_LENGTH) if sanitized: fn_template = sanitized # If the basename is empty, use the md5 hexdigest of the URL if not fn_template or fn_template.startswith('redirect.'): logger.error('Report this feed: Podcast %s, episode %s', self.channel.url, self.url) fn_template = hashlib.md5(self.url).hexdigest() # Find a unique filename for this episode wanted_filename = self.find_unique_file_name(fn_template, ext) if return_wanted_filename: # return the calculated filename without updating the database return wanted_filename # The old file exists, but we have decided to want a different filename if self.download_filename and wanted_filename != self.download_filename: # there might be an old download folder crawling around - move it! new_file_name = os.path.join(self.channel.save_dir, wanted_filename) old_file_name = os.path.join(self.channel.save_dir, self.download_filename) if os.path.exists( old_file_name) and not os.path.exists(new_file_name): logger.info('Renaming %s => %s', old_file_name, new_file_name) os.rename(old_file_name, new_file_name) elif force_update and not os.path.exists(old_file_name): # When we call force_update, the file might not yet exist when we # call it from the downloading code before saving the file logger.info('Choosing new filename: %s', new_file_name) else: logger.warn('%s exists or %s does not', new_file_name, old_file_name) logger.info('Updating filename of %s to "%s".', self.url, wanted_filename) elif self.download_filename is None: logger.info('Setting download filename: %s', wanted_filename) self.download_filename = wanted_filename self.save() return os.path.join(util.sanitize_encoding(self.channel.save_dir), util.sanitize_encoding(self.download_filename))