-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathffprobe.py
More file actions
93 lines (78 loc) · 3.52 KB
/
ffprobe.py
File metadata and controls
93 lines (78 loc) · 3.52 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
import os
import logging
import subprocess
import json
from .exceptions import FFprobeTerminatedException, FFprobeProcessException, FFprobeBinaryNotFound
from .cache import HashCache, CacheMissException
class FFprobeBaseCommand:
DEFAULT_ARGS = ['-hide_banner', '-of', 'json']
def __init__(self, bin_path: str, timeout: int=5):
bin_path = os.path.abspath(bin_path)
if not (os.path.isfile(bin_path) and os.access(bin_path, os.X_OK)):
msg = 'FFprobe binary not found: "{}"'.format(bin_path)
logging.critical(msg)
raise FFprobeBinaryNotFound(msg)
self._bin_path = bin_path
self._timeout = timeout
self._cache = HashCache(10, logging.debug)
def _exec(self, args: list) -> dict:
cache_id = ''.join(args)
logging.debug('Trying to get ffprobe result from cache...')
try:
cached_value = self._cache.from_cache(cache_id)
except CacheMissException:
pass
else:
return cached_value
logging.debug('Starting {}'.format(' '.join(args)))
try:
proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=self._timeout)
except subprocess.TimeoutExpired as e:
logging.error('FFprobe timeout - terminating')
raise FFprobeProcessException from e
if proc.returncode == 0:
logging.debug('FFprobe done')
stdout = proc.stdout.decode('utf-8')
try:
result = json.loads(stdout)
self._cache.to_cache(cache_id, result)
return result
except ValueError as e:
logging.error('FFprobe\'s stdout decoding error: {}'.format(str(e)))
logging.debug('Dumping stdout: {}'.format(stdout))
raise FFprobeProcessException from e
elif proc.returncode < 0:
msg = 'FFprobe terminated with signal {}'.format(abs(proc.returncode))
raise FFprobeTerminatedException(msg)
else:
log_err = 'Ffprobe exited with code {}'.format(proc.returncode)
log_debug = 'Dumping stderr: {}'.format(proc.stderr.decode('utf-8'))
logging.error(log_err)
logging.debug(log_debug)
raise FFprobeProcessException('{}. {}'.format(log_err, log_debug))
class FFprobeFrameCommand(FFprobeBaseCommand):
DEFAULT_ARGS = FFprobeBaseCommand.DEFAULT_ARGS + ['-show_frames']
def exec(self, in_url: str, select_streams: str=None, read_intervals: str=None) -> dict:
logging.debug('Building FFprobe command...')
args = [self._bin_path] + self.__class__.DEFAULT_ARGS
if select_streams is not None:
args.append('-select_streams')
args.append(select_streams)
if read_intervals is not None:
args.append('-read_intervals')
args.append(read_intervals)
args.append(in_url)
return self._exec(args)
class FFprobeInfoCommand(FFprobeBaseCommand):
def exec(self, in_url: str, show_format: bool=True, show_streams: bool=True, show_programs: bool=True) -> dict:
logging.debug('Building FFprobe command...')
args = [self._bin_path] + self.__class__.DEFAULT_ARGS
logging.debug('Appending -show* arguments...')
if show_format:
args.append('-show_format')
if show_streams:
args.append('-show_streams')
if show_programs:
args.append('-show_programs')
args.append(in_url)
return self._exec(args)