pull/12023/merge
gavin 12 hours ago committed by GitHub
commit 38a6cdb1cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,6 +2,7 @@
# Allow direct execution # Allow direct execution
import os import os
import platform
import sys import sys
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
@ -724,6 +725,7 @@ class TestYoutubeDL(unittest.TestCase):
'title3': 'foo/bar\\test', 'title3': 'foo/bar\\test',
'title4': 'foo "bar" test', 'title4': 'foo "bar" test',
'title5': 'áéí 𝐀', 'title5': 'áéí 𝐀',
'title6': '' * 10,
'timestamp': 1618488000, 'timestamp': 1618488000,
'duration': 100000, 'duration': 100000,
'playlist_index': 1, 'playlist_index': 1,
@ -739,12 +741,14 @@ class TestYoutubeDL(unittest.TestCase):
def test_prepare_outtmpl_and_filename(self): def test_prepare_outtmpl_and_filename(self):
def test(tmpl, expected, *, info=None, **params): def test(tmpl, expected, *, info=None, **params):
if 'trim_file_name' not in params:
params['trim_file_name'] = 'none' # disable trimming
params['outtmpl'] = tmpl params['outtmpl'] = tmpl
ydl = FakeYDL(params) ydl = FakeYDL(params)
ydl._num_downloads = 1 ydl._num_downloads = 1
self.assertEqual(ydl.validate_outtmpl(tmpl), None) 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) fname = ydl.prepare_filename(info or self.outtmpl_info)
if not isinstance(expected, (list, tuple)): if not isinstance(expected, (list, tuple)):
@ -951,6 +955,20 @@ class TestYoutubeDL(unittest.TestCase):
test('%(title3)s', ('foo/bar\\test', 'foobartest')) test('%(title3)s', ('foo/bar\\test', 'foobartest'))
test('folder/%(title3)s', ('folder/foo/bar\\test', f'folder{os.path.sep}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): def test_format_note(self):
ydl = YoutubeDL() ydl = YoutubeDL()
self.assertEqual(ydl._format_note({}), '') self.assertEqual(ydl._format_note({}), '')

@ -12,6 +12,8 @@ import json
import locale import locale
import operator import operator
import os import os
from pathlib import Path
import platform
import random import random
import re import re
import shutil import shutil
@ -1457,9 +1459,42 @@ class YoutubeDL:
return EXTERNAL_FORMAT_RE.sub(create_key, outtmpl), TMPL_DICT 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) 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 @_catch_unsafe_extension_error
def _prepare_filename(self, info_dict, *, outtmpl=None, tmpl_type=None): def _prepare_filename(self, info_dict, *, outtmpl=None, tmpl_type=None):
@ -1468,7 +1503,7 @@ class YoutubeDL:
outtmpl = self.params['outtmpl'].get(tmpl_type or 'default', self.params['outtmpl']['default']) outtmpl = self.params['outtmpl'].get(tmpl_type or 'default', self.params['outtmpl']['default'])
try: try:
outtmpl = self._outtmpl_expandpath(outtmpl) 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: if not filename:
return None return None
@ -1480,13 +1515,6 @@ class YoutubeDL:
force_ext = OUTTMPL_TYPES[tmpl_type] force_ext = OUTTMPL_TYPES[tmpl_type]
if force_ext: if force_ext:
filename = replace_extension(filename, force_ext, info_dict.get('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 return filename
except ValueError as err: except ValueError as err:
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')') self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')

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

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

Loading…
Cancel
Save