ffmpeg + ffprobe paths
This commit is contained in:
parent
c2fab59b3e
commit
8a3d92b04b
7 changed files with 73 additions and 12 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -144,4 +144,5 @@ docker/root/defaults/examples
|
||||||
|
|
||||||
.local/
|
.local/
|
||||||
|
|
||||||
ffmpeg.exe
|
ffmpeg.exe
|
||||||
|
ffprobe.exe
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import tempfile
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -5,14 +6,24 @@ from typing import Optional
|
||||||
from mergedeep import mergedeep
|
from mergedeep import mergedeep
|
||||||
|
|
||||||
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
|
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
|
||||||
|
from ytdl_sub.utils.system import IS_WINDOWS
|
||||||
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
||||||
from ytdl_sub.validators.validators import LiteralDictValidator
|
from ytdl_sub.validators.validators import LiteralDictValidator
|
||||||
from ytdl_sub.validators.validators import StringValidator
|
from ytdl_sub.validators.validators import StringValidator
|
||||||
|
|
||||||
|
if IS_WINDOWS:
|
||||||
|
_DEFAULT_LOCK_DIRECTORY = tempfile.TemporaryDirectory().name
|
||||||
|
_DEFAULT_FFMPEG_PATH = ".\\ffmpeg.exe"
|
||||||
|
_DEFAULT_FFPROBE_PATH = ".\\ffprobe.exe"
|
||||||
|
else:
|
||||||
|
_DEFAULT_LOCK_DIRECTORY = "/tmp"
|
||||||
|
_DEFAULT_FFMPEG_PATH = "/usr/bin/ffmpeg"
|
||||||
|
_DEFAULT_FFPROBE_PATH = "/usr/bin/ffprobe"
|
||||||
|
|
||||||
|
|
||||||
class ConfigOptions(StrictDictValidator):
|
class ConfigOptions(StrictDictValidator):
|
||||||
_required_keys = {"working_directory"}
|
_required_keys = {"working_directory"}
|
||||||
_optional_keys = {"umask", "dl_aliases", "lock_directory"}
|
_optional_keys = {"umask", "dl_aliases", "lock_directory", "ffmpeg_path", "ffprobe_path"}
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
def __init__(self, name: str, value: Any):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
@ -27,7 +38,14 @@ class ConfigOptions(StrictDictValidator):
|
||||||
key="dl_aliases", validator=LiteralDictValidator
|
key="dl_aliases", validator=LiteralDictValidator
|
||||||
)
|
)
|
||||||
self._lock_directory = self._validate_key(
|
self._lock_directory = self._validate_key(
|
||||||
key="lock_directory", validator=StringValidator, default="/tmp"
|
key="lock_directory", validator=StringValidator, default=_DEFAULT_LOCK_DIRECTORY
|
||||||
|
)
|
||||||
|
# TODO: Validate these exist
|
||||||
|
self._ffmpeg_path = self._validate_key(
|
||||||
|
key="ffmpeg_path", validator=StringValidator, default=_DEFAULT_FFMPEG_PATH
|
||||||
|
)
|
||||||
|
self._ffprobe_path = self._validate_key(
|
||||||
|
key="ffprobe_path", validator=StringValidator, default=_DEFAULT_FFPROBE_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -82,6 +100,20 @@ class ConfigOptions(StrictDictValidator):
|
||||||
"""
|
"""
|
||||||
return self._lock_directory.value
|
return self._lock_directory.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ffmpeg_path(self) -> str:
|
||||||
|
"""
|
||||||
|
TODO: Fill out!
|
||||||
|
"""
|
||||||
|
return self._ffmpeg_path.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ffprobe_path(self) -> str:
|
||||||
|
"""
|
||||||
|
TODO: Fill out!
|
||||||
|
"""
|
||||||
|
return self._ffprobe_path.value
|
||||||
|
|
||||||
|
|
||||||
class ConfigValidator(StrictDictValidator):
|
class ConfigValidator(StrictDictValidator):
|
||||||
_required_keys = {"configuration", "presets"}
|
_required_keys = {"configuration", "presets"}
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,10 @@ class SplitByChaptersPlugin(Plugin[SplitByChaptersOptions]):
|
||||||
if self.is_dry_run:
|
if self.is_dry_run:
|
||||||
chapters = Chapters.from_entry_chapters(entry=entry)
|
chapters = Chapters.from_entry_chapters(entry=entry)
|
||||||
else:
|
else:
|
||||||
chapters = Chapters.from_embedded_chapters(file_path=entry.get_download_file_path())
|
chapters = Chapters.from_embedded_chapters(
|
||||||
|
ffprobe_path=FFMPEG.ffprobe_path(),
|
||||||
|
file_path=entry.get_download_file_path(),
|
||||||
|
)
|
||||||
|
|
||||||
# If no chapters, do not split anything
|
# If no chapters, do not split anything
|
||||||
if not chapters.contains_any_chapters():
|
if not chapters.contains_any_chapters():
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from ytdl_sub.subscriptions.base_subscription import BaseSubscription
|
||||||
from ytdl_sub.subscriptions.subscription_ytdl_options import SubscriptionYTDLOptions
|
from ytdl_sub.subscriptions.subscription_ytdl_options import SubscriptionYTDLOptions
|
||||||
from ytdl_sub.utils.datetime import to_date_range
|
from ytdl_sub.utils.datetime import to_date_range
|
||||||
from ytdl_sub.utils.exceptions import ValidationException
|
from ytdl_sub.utils.exceptions import ValidationException
|
||||||
|
from ytdl_sub.utils.ffmpeg import FFMPEG
|
||||||
from ytdl_sub.utils.file_handler import FileHandler
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
||||||
from ytdl_sub.utils.file_handler import FileMetadata
|
from ytdl_sub.utils.file_handler import FileMetadata
|
||||||
|
|
@ -265,6 +266,12 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
||||||
If true, do not download any video/audio files or move anything to the output
|
If true, do not download any video/audio files or move anything to the output
|
||||||
directory.
|
directory.
|
||||||
"""
|
"""
|
||||||
|
# Set ffmpeg paths
|
||||||
|
FFMPEG.set_paths(
|
||||||
|
ffmpeg_path=self._config_options.ffmpeg_path,
|
||||||
|
ffprobe_path=self._config_options.ffprobe_path,
|
||||||
|
)
|
||||||
|
|
||||||
self._enhanced_download_archive.reinitialize(dry_run=dry_run)
|
self._enhanced_download_archive.reinitialize(dry_run=dry_run)
|
||||||
plugins = self._initialize_plugins()
|
plugins = self._initialize_plugins()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from ytdl_sub.plugins.file_convert import FileConvertPlugin
|
||||||
from ytdl_sub.plugins.match_filters import MatchFiltersPlugin
|
from ytdl_sub.plugins.match_filters import MatchFiltersPlugin
|
||||||
from ytdl_sub.plugins.plugin import Plugin
|
from ytdl_sub.plugins.plugin import Plugin
|
||||||
from ytdl_sub.plugins.subtitles import SubtitlesPlugin
|
from ytdl_sub.plugins.subtitles import SubtitlesPlugin
|
||||||
|
from ytdl_sub.utils.ffmpeg import FFMPEG
|
||||||
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
||||||
|
|
||||||
PluginT = TypeVar("PluginT", bound=Plugin)
|
PluginT = TypeVar("PluginT", bound=Plugin)
|
||||||
|
|
@ -57,6 +58,7 @@ class SubscriptionYTDLOptions:
|
||||||
"outtmpl": str(Path(self._working_directory) / "%(id)s.%(ext)s"),
|
"outtmpl": str(Path(self._working_directory) / "%(id)s.%(ext)s"),
|
||||||
# Always write thumbnails
|
# Always write thumbnails
|
||||||
"writethumbnail": True,
|
"writethumbnail": True,
|
||||||
|
"ffmpeg_location": FFMPEG.ffmpeg_path(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return ytdl_options
|
return ytdl_options
|
||||||
|
|
|
||||||
|
|
@ -221,10 +221,12 @@ class Chapters:
|
||||||
return Chapters(timestamps=timestamps, titles=titles)
|
return Chapters(timestamps=timestamps, titles=titles)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_embedded_chapters(cls, file_path: str) -> "Chapters":
|
def from_embedded_chapters(cls, ffprobe_path: str, file_path: str) -> "Chapters":
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
ffprobe_path
|
||||||
|
Path to ffprobe executable
|
||||||
file_path
|
file_path
|
||||||
File to read ffmpeg chapter metadata from
|
File to read ffmpeg chapter metadata from
|
||||||
|
|
||||||
|
|
@ -234,7 +236,7 @@ class Chapters:
|
||||||
"""
|
"""
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
[
|
[
|
||||||
"ffprobe",
|
ffprobe_path,
|
||||||
"-loglevel",
|
"-loglevel",
|
||||||
"quiet",
|
"quiet",
|
||||||
"-print_format",
|
"-print_format",
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ from ytdl_sub.utils.chapters import Chapters
|
||||||
from ytdl_sub.utils.exceptions import ValidationException
|
from ytdl_sub.utils.exceptions import ValidationException
|
||||||
from ytdl_sub.utils.file_handler import FileHandler
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
from ytdl_sub.utils.logger import Logger
|
from ytdl_sub.utils.logger import Logger
|
||||||
from ytdl_sub.utils.system import IS_WINDOWS
|
|
||||||
|
|
||||||
logger = Logger.get(name="ffmpeg")
|
logger = Logger.get(name="ffmpeg")
|
||||||
|
|
||||||
|
|
@ -26,13 +25,28 @@ def _ffmpeg_metadata_escape(str_to_escape: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
class FFMPEG:
|
class FFMPEG:
|
||||||
|
_FFMPEG_PATH: str = ""
|
||||||
|
_FFPROBE_PATH: str = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_paths(cls, ffmpeg_path: str, ffprobe_path: str) -> None:
|
||||||
|
cls._FFMPEG_PATH = ffmpeg_path
|
||||||
|
cls._FFPROBE_PATH = ffprobe_path
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ffmpeg_path(cls) -> str:
|
||||||
|
assert cls._FFMPEG_PATH, "ffmpeg has not been set"
|
||||||
|
return cls._FFMPEG_PATH
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ffprobe_path(cls) -> str:
|
||||||
|
assert cls._FFPROBE_PATH, "ffprobe has not been set"
|
||||||
|
return cls._FFPROBE_PATH
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _ensure_installed(cls):
|
def _ensure_installed(cls):
|
||||||
try:
|
try:
|
||||||
if IS_WINDOWS:
|
subprocess.check_output([cls.ffmpeg_path(), "-version"])
|
||||||
subprocess.check_output([".\\ffmpeg", "-version"])
|
|
||||||
else:
|
|
||||||
subprocess.check_output(["which", "ffmpeg"])
|
|
||||||
except subprocess.CalledProcessError as subprocess_error:
|
except subprocess.CalledProcessError as subprocess_error:
|
||||||
raise ValidationException(
|
raise ValidationException(
|
||||||
"Trying to use a feature which requires ffmpeg, but it cannot be found"
|
"Trying to use a feature which requires ffmpeg, but it cannot be found"
|
||||||
|
|
@ -69,7 +83,7 @@ class FFMPEG:
|
||||||
"""
|
"""
|
||||||
cls._ensure_installed()
|
cls._ensure_installed()
|
||||||
|
|
||||||
cmd = [".\\ffmpeg.exe" if IS_WINDOWS else "ffmpeg"]
|
cmd = [cls.ffmpeg_path()]
|
||||||
cmd.extend(ffmpeg_args)
|
cmd.extend(ffmpeg_args)
|
||||||
logger.debug("Running %s", " ".join(cmd))
|
logger.debug("Running %s", " ".join(cmd))
|
||||||
with Logger.handle_external_logs(name="ffmpeg"):
|
with Logger.handle_external_logs(name="ffmpeg"):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue