ytdl-sub/src/ytdl_sub/validators/file_path_validators.py
2023-03-09 23:18:16 -08:00

101 lines
3.8 KiB
Python

from pathlib import Path
from typing import Any
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 StringFormatterValidator
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):
_expected_value_type_name = "ffmpeg dependency"
_ffmpeg_dependency = "ffmpeg"
def __init__(self, name: str, value: Any):
super().__init__(name, value)
if not os.path.isfile(self.value):
raise self._validation_exception(
f"Expects an {self._ffmpeg_dependency} executable at '{self.value}', but "
f"does not exist. See https://github.com/jmbannon/ytdl-sub#installation on how "
f"to install ffmpeg dependencies."
)
@property
def value(self) -> str:
"""Turn into a Path, then a string, to get correct directory separators"""
return str(Path(self._value))
class FFprobeFileValidator(FFmpegFileValidator):
_ffmpeg_dependency = "ffprobe"
class StringFormatterFilePathValidator(StringFormatterValidator):
_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:
"""Turn into a Path, then a string, to get correct directory separators"""
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):
_expected_value_type_name = "static filepath"
def apply_formatter(self, variable_dict: Dict[str, str]) -> str:
"""Turn into a Path, then a string, to get correct directory separators"""
return os.path.realpath(super().apply_formatter(variable_dict))