Files
ytdl-sub/src/ytdl_sub/validators/file_path_validators.py
Jesse Bannon 7385aac1ee [FEATURE][EXPERIMENTAL] --update-with-info-json (#557)
* [FEATURE
] Reformat existing downloads

* working?!?!

* reformat in output dir

* close, need info json enabled by default

* asdfadsfsaf

* lint

* closer

* multi created variables do not exist for update command!

* info json downloader

* [REFACTOR] Shared plugin and download options class

* more refactoring

* lint

* put in initialize plugins

* changes

* [REFACTOR] simplify enhanced download archive

* working!

* in main

* simplified, many url tests working

* ready?
2023-03-21 08:05:08 -07:00

129 lines
4.8 KiB
Python

import os
from pathlib import Path
from typing import Any
from typing import Dict
from typing import Tuple
from ytdl_sub.utils.file_handler import get_file_extension
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
if IS_WINDOWS:
_MAX_FILE_NAME_BYTES = 255
else:
_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 FilePathValidatorMixin:
@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]:
ext = get_file_extension(file_name)
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}"
@classmethod
def _maybe_truncate_file_path(cls, file_path: Path) -> str:
"""Turn into a Path, then a string, to get correct directory separators"""
file_directory, file_name = os.path.split(Path(file_path))
if cls._is_file_name_too_long(file_name):
return str(Path(file_directory) / cls._truncate_file_name(file_name))
return str(file_path)
# pylint: disable=line-too-long
class StringFormatterFileNameValidator(StringFormatterValidator, FilePathValidatorMixin):
"""
Same as a
:class:`StringFormatterValidator <ytdl_sub.validators.string_formatter_validators.StringFormatterValidator>`
but ensures the file name does not exceed the OS limit (typically 255 bytes). If it does exceed,
it will preserve the extension and truncate the end of the file name.
"""
# pylint: enable=line-too-long
_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:
"""Turn into a Path, then a string, to get correct directory separators"""
file_path = Path(super().apply_formatter(variable_dict))
return self._maybe_truncate_file_path(file_path)
class OverridesStringFormatterFilePathValidator(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))