fix #494 - ensure short filenames

- fix rename_download that was causing #494
- max_length is now mandatory in util.sanitize_filename
- add max_length param where it's missing
- factor name computation in deviceplaylist and sync
This commit is contained in:
Eric Le Lay 2018-07-19 18:44:58 +02:00
parent e7e5b2209e
commit 66bc2a6f72
6 changed files with 64 additions and 42 deletions

View File

@ -7,6 +7,7 @@ import os
import gpodder
from gpodder import util
from gpodder.model import PodcastEpisode
import logging
logger = logging.getLogger(__name__)
@ -46,18 +47,21 @@ class gPodderExtension:
dirname = os.path.dirname(current_filename)
filename = os.path.basename(current_filename)
basename, ext = os.path.splitext(filename)
ext = utils.sanitize_filename(ext, PodcastEpisode.MAX_FILENAME_LENGTH)
new_basename = []
new_basename.append(title + ext)
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)
# On Windows, force ASCII encoding for filenames (bug 1724)
new_basename = util.sanitize_filename(new_basename)
new_filename = os.path.join(dirname, 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

View File

@ -162,7 +162,7 @@ defaults = {
'skip_played_episodes': True,
'delete_played_episodes': False,
'max_filename_length': 999,
'max_filename_length': 120,
'custom_sync_name': '{episode.sortdate}_{episode.title}',
'custom_sync_name_enabled': False,

View File

@ -23,6 +23,7 @@ import gpodder
_ = gpodder.gettext
from gpodder import util
from gpodder.sync import episode_filename_on_device, episode_foldername_on_device
import logging
logger = logging.getLogger(__name__)
@ -32,7 +33,7 @@ class gPodderDevicePlaylist(object):
def __init__(self, config, playlist_name):
self._config = config
self.linebreak = '\r\n'
self.playlist_file = util.sanitize_filename(playlist_name + '.m3u')
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 == '/':
@ -72,20 +73,16 @@ class gPodderDevicePlaylist(object):
"""
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
return episode_filename_on_device(self._config, episode)
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)
foldername = episode_foldername_on_device(self._config, episode)
if foldername:
filename = os.path.join(foldername, 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

View File

@ -60,6 +60,7 @@ from gpodder import my
from gpodder import youtube
from gpodder import player
from gpodder import common
from gpodder.model import PodcastEpisode
import logging
logger = logging.getLogger(__name__)
@ -1772,7 +1773,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
@staticmethod
def build_filename(filename, extension):
filename = util.sanitize_filename(filename)
filename = util.sanitize_filename(filename, PodcastEpisode.MAX_FILENAME_LENGTH)
if not filename.endswith(extension):
filename += extension
return filename

View File

@ -145,6 +145,46 @@ def get_track_length(filename):
# Default is three hours (to be on the safe side)
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_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
class SyncTrack(object):
"""
This represents a track that is on a device. You need
@ -527,12 +567,8 @@ class MP3PlayerDevice(Device):
return False
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 = episode_foldername_on_device(self._config, episode)
if folder:
folder = os.path.join(self.destination, folder)
else:
folder = self.destination
@ -540,23 +576,7 @@ class MP3PlayerDevice(Device):
return folder
def get_episode_file_on_device(self, episode):
# get the local file
from_file = 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
return episode_filename_on_device(self._config, episode)
def add_track(self, episode,reporthook=None):
self.notify('status', _('Adding %s') % episode.title)

View File

@ -1468,16 +1468,16 @@ def convert_bytes(d):
return d
def sanitize_filename(filename, max_length=0):
def sanitize_filename(filename, max_length):
"""
Generate a sanitized version of a filename; trim filename
if greater than max_length (0 = no limit).
>>> sanitize_filename('https://www.host.name/feed')
>>> sanitize_filename('https://www.host.name/feed', 0)
'https___www.host.name_feed'
>>> sanitize_filename('Binärgewitter')
>>> sanitize_filename('Binärgewitter', 0)
'Binärgewitter'
>>> sanitize_filename('Cool feed (ogg)')
>>> sanitize_filename('Cool feed (ogg)', 0)
'Cool feed (ogg)'
>>> sanitize_filename('Cool feed (ogg)', 1)
'C'