[BUGFIX] Handle truncating file names if too long
This commit is contained in:
parent
d46829b4ab
commit
12f4d36223
4 changed files with 94 additions and 6 deletions
|
|
@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
3
src/ytdl_sub/utils/subtitles.py
Normal file
3
src/ytdl_sub/utils/subtitles.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
SUBTITLE_EXTENSIONS: Set[str] = {"srt", "vtt", "ass", "lrc"}
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
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
|
||||||
|
|
||||||
|
_MAX_FILE_NAME_LEN: int = 0
|
||||||
|
_MAX_FILE_NAME_BYTES: int = 0
|
||||||
|
if IS_WINDOWS:
|
||||||
|
from ctypes.wintypes import MAX_PATH
|
||||||
|
|
||||||
|
_MAX_FILE_NAME_LEN = MAX_PATH
|
||||||
|
else:
|
||||||
|
import os
|
||||||
|
|
||||||
|
_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 +47,50 @@ 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 (_MAX_FILE_NAME_LEN and len(file_name) > _MAX_FILE_NAME_LEN) or (
|
||||||
|
_MAX_FILE_NAME_BYTES and 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)
|
||||||
|
if _MAX_FILE_NAME_LEN:
|
||||||
|
to_trim = len(file_name) - _MAX_FILE_NAME_LEN + 1
|
||||||
|
file_sub_name = file_sub_name[:-to_trim]
|
||||||
|
elif _MAX_FILE_NAME_BYTES:
|
||||||
|
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 = str(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 file_path
|
||||||
|
|
||||||
|
|
||||||
class OverridesStringFormatterValidatorFilePathValidator(OverridesStringFormatterValidator):
|
class OverridesStringFormatterValidatorFilePathValidator(OverridesStringFormatterValidator):
|
||||||
|
|
|
||||||
34
tests/unit/validators/test_file_path_validators.py
Normal file
34
tests/unit/validators/test_file_path_validators.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
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.{ext}" for ext in SUBTITLE_EXTENSIONS],
|
||||||
|
)
|
||||||
|
def test_truncates_file_name_successfully(self, ext: str):
|
||||||
|
ext = ".info.json"
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
file_name = ("a" * 10000) + 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"):
|
||||||
|
pass
|
||||||
Loading…
Reference in a new issue