forked from Breakthrough/PySceneDetect
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplatform.py
More file actions
201 lines (163 loc) · 7.04 KB
/
Copy pathplatform.py
File metadata and controls
201 lines (163 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# -*- coding: utf-8 -*-
#
# PySceneDetect: Python-Based Video Scene Detector
# ---------------------------------------------------------------
# [ Site: http://www.scenedetect.scenedetect.com/ ]
# [ Docs: http://manual.scenedetect.scenedetect.com/ ]
# [ Github: https://github.com/Breakthrough/PySceneDetect/ ]
#
# Copyright (C) 2014-2022 Brandon Castellano <http://www.bcastell.com>.
# PySceneDetect is licensed under the BSD 3-Clause License; see the
# included LICENSE file, or visit one of the above pages for details.
#
""" ``scenedetect.platform`` Module
This file contains all platform/library/OS-specific compatibility fixes,
intended to improve the systems that are able to run PySceneDetect, and allow
for maintaining backwards compatibility with existing libraries going forwards.
Other helper functions related to the detection of the appropriate dependency
DLLs on Windows and getting uniform line-terminating csv reader/writer objects
are also included in this module.
"""
import logging
import os
import os.path
import subprocess
import sys
from typing import AnyStr, Dict, List, Optional, Union
import cv2
##
## tqdm Library (`scenedetect.platform.tqdm`` will be tqdm object type or None)
##
# pylint: disable=unused-import
# pylint: disable=invalid-name
try:
from tqdm import tqdm
except ModuleNotFoundError:
tqdm = None
# pylint: enable=unused-import
# pylint: enable=invalid-name
##
## OpenCV imwrite Supported Image Types & Quality/Compression Parameters
##
# TODO: Move this into scene_manager.
def get_cv2_imwrite_params() -> Dict[str, Union[int, None]]:
""" Get OpenCV imwrite Params: Returns a dict of supported image formats and
their associated quality/compression parameter index, or None if that format
is not supported.
Returns:
Dictionary of supported image formats/extensions ('jpg', 'png', etc...) mapped to the
respective OpenCV quality or compression parameter as {'jpg': cv2.IMWRITE_JPEG_QUALITY,
'png': cv2.IMWRITE_PNG_COMPRESSION, ...}. Parameter will be None if not found on the
current system library (e.g. {'jpg': None}).
"""
def _get_cv2_param(param_name: str) -> Union[int, None]:
if param_name.startswith('CV_'):
param_name = param_name[3:]
try:
return getattr(cv2, param_name)
except AttributeError:
return None
return {
'jpg': _get_cv2_param('IMWRITE_JPEG_QUALITY'),
'png': _get_cv2_param('IMWRITE_PNG_COMPRESSION'),
'webp': _get_cv2_param('IMWRITE_WEBP_QUALITY')
}
##
## File I/O
##
def get_file_name(file_path: AnyStr, include_extension=True) -> str:
"""Return the file name that `file_path` refers to, optionally removing the extension.
E.g. /tmp/foo.bar -> foo"""
file_name = str(os.path.basename(file_path))
if not include_extension:
last_dot_pos = file_name.rfind('.')
if last_dot_pos >= 0:
file_name = file_name[:last_dot_pos]
return file_name
def get_and_create_path(file_path: AnyStr, output_directory: Optional[AnyStr] = None) -> AnyStr:
""" Get & Create Path: Gets and returns the full/absolute path to file_path
in the specified output_directory if set, creating any required directories
along the way.
If file_path is already an absolute path, then output_directory is ignored.
Arguments:
file_path: File name to get path for. If file_path is an absolute
path (e.g. starts at a drive/root), no modification of the path
is performed, only ensuring that all output directories are created.
output_dir: An optional output directory to override the
directory of file_path if it is relative to the working directory.
Returns:
Full path to output file suitable for writing.
"""
# If an output directory is defined and the file path is a relative path, open
# the file handle in the output directory instead of the working directory.
if output_directory is not None and not os.path.isabs(file_path):
file_path = os.path.join(output_directory, file_path)
# Now that file_path is an absolute path, let's make sure all the directories
# exist for us to start writing files there.
os.makedirs(os.path.split(os.path.abspath(file_path))[0], exist_ok=True)
return file_path
##
## Logging
##
def init_logger(log_level: int = logging.INFO,
show_stdout: bool = False,
log_file: Optional[str] = None):
"""Initializes logging for PySceneDetect. The logger instance used is named 'pyscenedetect'.
By default the logger has no handlers to suppress output. All existing log handlers are replaced
every time this function is invoked.
Arguments:
log_level: Verbosity of log messages. Should be one of [logging.INFO, logging.DEBUG,
logging.WARNING, logging.ERROR, logging.CRITICAL].
show_stdout: If True, add handler to show log messages on stdout (default: False).
log_file: If set, add handler to dump log messages to given file path.
"""
# Format of log messages depends on verbosity.
format_str = '[PySceneDetect] %(message)s'
if log_level == logging.DEBUG:
format_str = '%(levelname)s: %(module)s.%(funcName)s(): %(message)s'
# Get the named logger and remove any existing handlers.
logger_instance = logging.getLogger('pyscenedetect')
logger_instance.handlers = []
logger_instance.setLevel(log_level)
# Add stdout handler if required.
if show_stdout:
handler = logging.StreamHandler(stream=sys.stdout)
handler.setLevel(log_level)
handler.setFormatter(logging.Formatter(fmt=format_str))
logger_instance.addHandler(handler)
# Add file handler if required.
if log_file:
log_file = get_and_create_path(log_file)
handler = logging.FileHandler(log_file)
handler.setLevel(log_level)
handler.setFormatter(logging.Formatter(fmt=format_str))
logger_instance.addHandler(handler)
init_logger()
##
## Running External Commands
##
class CommandTooLong(Exception):
"""Raised if the length of a command line argument exceeds the limit allowed on Windows."""
def invoke_command(args: List[str]) -> int:
""" Same as calling Python's subprocess.call() method, but explicitly
raises a different exception when the command length is too long.
See https://github.com/Breakthrough/PySceneDetect/issues/164 for details.
Arguments:
args: List of strings to pass to subprocess.call().
Returns:
Return code of command.
Raises:
CommandTooLong: `args` exceeds built in command line length limit on Windows.
"""
try:
return subprocess.call(args)
except OSError as err:
if os.name != 'nt':
raise
exception_string = str(err)
# Error 206: The filename or extension is too long
# Error 87: The parameter is incorrect
to_match = ('206', '87')
if any([x in exception_string for x in to_match]):
raise CommandTooLong() from err
raise