pull/12023/merge
gavin 6 days ago committed by GitHub
commit 695e49d4b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,6 +2,7 @@
# Allow direct execution
import os
import platform
import sys
import unittest
from unittest.mock import patch
@ -724,6 +725,7 @@ class TestYoutubeDL(unittest.TestCase):
'title3': 'foo/bar\\test',
'title4': 'foo "bar" test',
'title5': 'áéí 𝐀',
'title6': '' * 10,
'timestamp': 1618488000,
'duration': 100000,
'playlist_index': 1,
@ -739,12 +741,14 @@ class TestYoutubeDL(unittest.TestCase):
def test_prepare_outtmpl_and_filename(self):
def test(tmpl, expected, *, info=None, **params):
if 'trim_file_name' not in params:
params['trim_file_name'] = 'none' # disable trimming
params['outtmpl'] = tmpl
ydl = FakeYDL(params)
ydl._num_downloads = 1
self.assertEqual(ydl.validate_outtmpl(tmpl), None)
out = ydl.evaluate_outtmpl(tmpl, info or self.outtmpl_info)
out = ydl.evaluate_outtmpl(tmpl, info or self.outtmpl_info, trim_filename=True)
fname = ydl.prepare_filename(info or self.outtmpl_info)
if not isinstance(expected, (list, tuple)):
@ -951,6 +955,20 @@ class TestYoutubeDL(unittest.TestCase):
test('%(title3)s', ('foo/bar\\test', 'foobartest'))
test('folder/%(title3)s', ('folder/foo/bar\\test', f'folder{os.path.sep}foobartest'))
# --trim-filenames
test('%(title6)s.%(ext)s', '' * 10 + '.mp4')
test('%(title6)s.%(ext)s', '' * 3 + '.mp4', trim_file_name='3c')
if sys.getfilesystemencoding() == 'utf-8' and platform.system() != 'Windows':
test('%(title6)s.%(ext)s', '' * 3 + '.mp4', trim_file_name='9b')
test('%(title6)s.%(ext)s', '' * 3 + '.mp4', trim_file_name='10b')
test('%(title6)s.%(ext)s', '' * 3 + '.mp4', trim_file_name='11b')
test('%(title6)s.%(ext)s', '' * 4 + '.mp4', trim_file_name='12b')
elif platform.system() == 'Windows':
test('%(title6)s.%(ext)s', '' * 4 + '.mp4', trim_file_name='8b')
test('%(title6)s.%(ext)s', '' * 4 + '.mp4', trim_file_name='9b')
test('%(title6)s.%(ext)s', '' * 5 + '.mp4', trim_file_name='10b')
test('folder/%(title6)s.%(ext)s', f'fol{os.path.sep}あああ.mp4', trim_file_name='3c')
def test_format_note(self):
ydl = YoutubeDL()
self.assertEqual(ydl._format_note({}), '')

@ -12,6 +12,8 @@ import json
import locale
import operator
import os
from pathlib import Path
import platform
import random
import re
import shutil
@ -1442,9 +1444,42 @@ class YoutubeDL:
return EXTERNAL_FORMAT_RE.sub(create_key, outtmpl), TMPL_DICT
def evaluate_outtmpl(self, outtmpl, info_dict, *args, **kwargs):
def evaluate_outtmpl(self, outtmpl, info_dict, *args, trim_filename=False, **kwargs):
outtmpl, info_dict = self.prepare_outtmpl(outtmpl, info_dict, *args, **kwargs)
return self.escape_outtmpl(outtmpl) % info_dict
if not trim_filename:
return self.escape_outtmpl(outtmpl) % info_dict
ext_suffix = '.%(ext\0s)s'
suffix = ''
if outtmpl.endswith(ext_suffix):
outtmpl = outtmpl[:-len(ext_suffix)]
suffix = ext_suffix % info_dict
outtmpl = self.escape_outtmpl(outtmpl)
filename = outtmpl % info_dict
def parse_trim_file_name(trim_file_name):
if trim_file_name is None or trim_file_name == 'none':
return 0, None
mobj = re.match(r'(?:(?P<length>\d+)(?P<mode>b|c)?|none)', trim_file_name)
return int(mobj.group('length')), mobj.group('mode') or 'c'
max_file_name, mode = parse_trim_file_name(self.params.get('trim_file_name'))
if max_file_name == 0:
# no maximum
return filename + suffix
encoding = sys.getfilesystemencoding() if platform.system() != 'Windows' else 'utf-16-le'
def trim_filename(name: str):
if mode == 'b':
name = name.encode(encoding)
name = name[:max_file_name]
return name.decode(encoding, 'ignore')
else:
return name[:max_file_name]
filename = os.path.join(*map(trim_filename, Path(filename).parts or '.'))
return filename + suffix
@_catch_unsafe_extension_error
def _prepare_filename(self, info_dict, *, outtmpl=None, tmpl_type=None):
@ -1453,7 +1488,7 @@ class YoutubeDL:
outtmpl = self.params['outtmpl'].get(tmpl_type or 'default', self.params['outtmpl']['default'])
try:
outtmpl = self._outtmpl_expandpath(outtmpl)
filename = self.evaluate_outtmpl(outtmpl, info_dict, True)
filename = self.evaluate_outtmpl(outtmpl, info_dict, True, trim_filename=True)
if not filename:
return None
@ -1465,13 +1500,6 @@ class YoutubeDL:
force_ext = OUTTMPL_TYPES[tmpl_type]
if force_ext:
filename = replace_extension(filename, force_ext, info_dict.get('ext'))
# https://github.com/blackjack4494/youtube-dlc/issues/85
trim_file_name = self.params.get('trim_file_name', False)
if trim_file_name:
no_ext, *ext = filename.rsplit('.', 2)
filename = join_nonempty(no_ext[:trim_file_name], *ext, delim='.')
return filename
except ValueError as err:
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')

@ -436,6 +436,8 @@ def validate_options(opts):
if opts.plugin_dirs is None:
opts.plugin_dirs = ['default']
validate_regex('trim filenames', opts.trim_file_name, r'(?:\d+[bc]?|none)')
if opts.playlist_items is not None:
try:
tuple(PlaylistEntries.parse_playlist_items(opts.playlist_items))

@ -1388,8 +1388,8 @@ def create_parser():
help='Sanitize filenames only minimally')
filesystem.add_option(
'--trim-filenames', '--trim-file-names', metavar='LENGTH',
dest='trim_file_name', default=0, type=int,
help='Limit the filename length (excluding extension) to the specified number of characters')
dest='trim_file_name', default='none',
help='Limit the filename length (excluding extension) to the specified number of characters or bytes')
filesystem.add_option(
'-w', '--no-overwrites',
action='store_false', dest='overwrites', default=None,

Loading…
Cancel
Save