Пример #1
0
    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
Пример #2
0
    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
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
    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
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
    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
Пример #10
0
    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
Пример #11
0
    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)
Пример #12
0
    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
Пример #13
0
 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()
Пример #14
0
 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)
Пример #15
0
 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)
Пример #16
0
    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
Пример #17
0
 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)
Пример #18
0
 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)
Пример #19
0
 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
Пример #20
0
 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
Пример #21
0
    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
Пример #22
0
    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)
Пример #23
0
    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
Пример #24
0
    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
Пример #25
0
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
Пример #26
0
    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
Пример #27
0
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
Пример #28
0
    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
Пример #29
0
 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()
Пример #30
0
    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
Пример #31
0
    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
Пример #32
0
    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
Пример #33
0
    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
Пример #34
0
 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()
Пример #35
0
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
Пример #36
0
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
Пример #37
0
    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)
        )
Пример #38
0
    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
Пример #39
0
    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)
Пример #40
0
    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))