[BUGFIX] Handle truncating file names if too long (#524)

* [BUGFIX] Handle truncating file names if too long

* always import os

* test fix

* fix for windows

* lint
This commit is contained in:
Jesse Bannon 2023-03-10 07:20:43 -08:00 committed by GitHub
parent a9212bdf84
commit f9318b9d07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 5 deletions

View file

@ -2,7 +2,6 @@ from pathlib import Path
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Set
from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
@ -11,15 +10,13 @@ from ytdl_sub.plugins.plugin import PluginOptions
from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.logger import Logger from ytdl_sub.utils.logger import Logger
from ytdl_sub.utils.subtitles import SUBTITLE_EXTENSIONS
from ytdl_sub.validators.file_path_validators import StringFormatterFilePathValidator from ytdl_sub.validators.file_path_validators import StringFormatterFilePathValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.string_select_validator import StringSelectValidator from ytdl_sub.validators.string_select_validator import StringSelectValidator
from ytdl_sub.validators.validators import BoolValidator from ytdl_sub.validators.validators import BoolValidator
from ytdl_sub.validators.validators import StringListValidator from ytdl_sub.validators.validators import StringListValidator
SUBTITLE_EXTENSIONS: Set[str] = {"srt", "vtt", "ass", "lrc"}
logger = Logger.get(name="subtitles") logger = Logger.get(name="subtitles")

View file

@ -0,0 +1,3 @@
from typing import Set
SUBTITLE_EXTENSIONS: Set[str] = {"srt", "vtt", "ass", "lrc"}

View file

@ -2,11 +2,19 @@ import os
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Tuple
from ytdl_sub.utils.subtitles import SUBTITLE_EXTENSIONS
from ytdl_sub.utils.system import IS_WINDOWS
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.validators import StringValidator from ytdl_sub.validators.validators import StringValidator
if IS_WINDOWS:
_MAX_FILE_NAME_BYTES = 255
else:
_MAX_FILE_NAME_BYTES = os.pathconf("/", "PC_NAME_MAX")
class FFmpegFileValidator(StringValidator): class FFmpegFileValidator(StringValidator):
_expected_value_type_name = "ffmpeg dependency" _expected_value_type_name = "ffmpeg dependency"
@ -34,9 +42,45 @@ class FFprobeFileValidator(FFmpegFileValidator):
class StringFormatterFilePathValidator(StringFormatterValidator): class StringFormatterFilePathValidator(StringFormatterValidator):
_expected_value_type_name = "filepath" _expected_value_type_name = "filepath"
@classmethod
def _is_file_name_too_long(cls, file_name: str) -> bool:
return len(file_name.encode("utf-8")) > _MAX_FILE_NAME_BYTES
@classmethod
def _get_extension_split(cls, file_name: str) -> Tuple[str, str]:
if file_name.endswith(".info.json"):
ext = "info.json"
elif any(file_name.endswith(f".{subtitle_ext}") for subtitle_ext in SUBTITLE_EXTENSIONS):
file_name_split = file_name.split(".")
ext = file_name_split[-1]
# Try to capture .lang.ext
if len(file_name_split) > 2 and len(file_name_split[-2]) < 6:
ext = f"{file_name_split[-2]}.{file_name_split[-1]}"
else:
ext = file_name.rsplit(".", maxsplit=1)[-1]
return file_name[: -len(ext)], ext
@classmethod
def _truncate_file_name(cls, file_name: str) -> str:
file_sub_name, file_ext = cls._get_extension_split(file_name)
desired_size = _MAX_FILE_NAME_BYTES - len(file_ext.encode("utf-8")) - 1
while len(file_sub_name.encode("utf-8")) > desired_size:
file_sub_name = file_sub_name[:-1]
return f"{file_sub_name}.{file_ext}"
def apply_formatter(self, variable_dict: Dict[str, str]) -> str: def apply_formatter(self, variable_dict: Dict[str, str]) -> str:
"""Turn into a Path, then a string, to get correct directory separators""" """Turn into a Path, then a string, to get correct directory separators"""
return str(Path(super().apply_formatter(variable_dict))) file_path = Path(super().apply_formatter(variable_dict))
file_directory, file_name = os.path.split(Path(file_path))
if self._is_file_name_too_long(file_name):
return str(Path(file_directory) / self._truncate_file_name(file_name))
return str(file_path)
class OverridesStringFormatterValidatorFilePathValidator(OverridesStringFormatterValidator): class OverridesStringFormatterValidatorFilePathValidator(OverridesStringFormatterValidator):

View file

@ -0,0 +1,42 @@
import tempfile
from pathlib import Path
import pytest
from ytdl_sub.utils.subtitles import SUBTITLE_EXTENSIONS
from ytdl_sub.validators.file_path_validators import StringFormatterFilePathValidator
class TestStringFormatterFilePathValidator:
@pytest.mark.parametrize(
"ext",
[
"mp4",
"info.json",
]
+ [f"en-US.{ext}" for ext in SUBTITLE_EXTENSIONS],
)
@pytest.mark.parametrize("file_name_char", ["a", "𒃀"])
@pytest.mark.parametrize("file_name_len", [10, 10000])
def test_truncates_file_name_successfully(
self, ext: str, file_name_char: str, file_name_len: int
):
ext = f".{ext}" # pytest args with . in the beginning act weird
with tempfile.TemporaryDirectory() as temp_dir:
file_name = (file_name_char * file_name_len) + ext
file_path = str(Path(temp_dir) / file_name)
formatter = StringFormatterFilePathValidator(name="test", value=str(file_path))
truncated_file_path = formatter.apply_formatter({})
assert truncated_file_path.count(".") == ext.count(".")
assert str(Path(temp_dir)) in truncated_file_path
assert ext in truncated_file_path
# Ensure it can actually open the file
with open(truncated_file_path, "w", encoding="utf-8"):
# Make sure the file is actually in the directory
dir_paths = list(Path(temp_dir).rglob("*"))
assert len(dir_paths) == 1
assert Path(truncated_file_path) == dir_paths[0]