simple-ffmpeg-batch-io 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_ffmpeg_batch_io/AudioIO.py +824 -0
- simple_ffmpeg_batch_io/FrameContainer.py +81 -0
- simple_ffmpeg_batch_io/FrameCounter.py +182 -0
- simple_ffmpeg_batch_io/PipeMode.py +26 -0
- simple_ffmpeg_batch_io/VideoIO.py +803 -0
- simple_ffmpeg_batch_io/__init__.py +23 -0
- simple_ffmpeg_batch_io/cli.py +27 -0
- simple_ffmpeg_batch_io-1.0.0.dist-info/METADATA +382 -0
- simple_ffmpeg_batch_io-1.0.0.dist-info/RECORD +12 -0
- simple_ffmpeg_batch_io-1.0.0.dist-info/WHEEL +4 -0
- simple_ffmpeg_batch_io-1.0.0.dist-info/entry_points.txt +3 -0
- simple_ffmpeg_batch_io-1.0.0.dist-info/licenses/LICENSE +198 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Create a ``FrameContainer`` with data and associated timestamp(s)
|
|
3
|
+
|
|
4
|
+
Authors
|
|
5
|
+
-------
|
|
6
|
+
Dominique Vaufreydaz
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__authors__ = ("Dominique Vaufreydaz")
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
# class to get audio or video frames with associated timestamp list
|
|
15
|
+
class FrameContainer:
|
|
16
|
+
"""
|
|
17
|
+
Create a Container with audio or image data and associated timestamp(s).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
class FrameContainerException(Exception):
|
|
21
|
+
"""
|
|
22
|
+
Dedicated exception class for FrameContainer class.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, message="Error while setting FrameCounter parameters."):
|
|
25
|
+
self.message = message
|
|
26
|
+
super().__init__(self.message)
|
|
27
|
+
|
|
28
|
+
nb_frames: int
|
|
29
|
+
""" number of frames in the frame container. 1 for audio frame or images, n for a batch. """
|
|
30
|
+
|
|
31
|
+
data: np.array
|
|
32
|
+
""" Audio frame or image, or batch of images or audio frames. """
|
|
33
|
+
|
|
34
|
+
timestamps: np.array
|
|
35
|
+
""" an np.array of timestamp(s) associated to the data frame(s). """
|
|
36
|
+
|
|
37
|
+
def __init__(self, nb_frames : int, frames : np.array, fps : float, start_time : float = 0.0 ):
|
|
38
|
+
"""
|
|
39
|
+
Create a FrameContainer object with data and associated timestamps
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
nb_frames: int.
|
|
44
|
+
Number of frame(s) in the frame container. Either 1, either number of element in the batch.
|
|
45
|
+
|
|
46
|
+
frames: np.array
|
|
47
|
+
Data for image, audio frame or batch of them.
|
|
48
|
+
|
|
49
|
+
fps : float.
|
|
50
|
+
fps of the read stream.
|
|
51
|
+
|
|
52
|
+
start_time: float (default = 0.0).
|
|
53
|
+
Start time of the current FrameContainer, i.e. time of the (first) frame.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# check params
|
|
57
|
+
if nb_frames <= 0:
|
|
58
|
+
raise self.FrameContainerException("nb_frames must be > 0.")
|
|
59
|
+
if nb_frames > 1 and frames.shape[0] != nb_frames:
|
|
60
|
+
raise self.FrameContainerException("nb_frames and batch size (frames.shape[0]) differs.")
|
|
61
|
+
if fps <= 0.0:
|
|
62
|
+
raise self.FrameContainerException("fps must be > 0.0.")
|
|
63
|
+
if start_time < 0.0:
|
|
64
|
+
raise self.FrameContainerException("start_time must be >= 0.0.")
|
|
65
|
+
|
|
66
|
+
self.nb_frames = nb_frames
|
|
67
|
+
self.data = frames
|
|
68
|
+
# first frame is at time start_time
|
|
69
|
+
self.timestamps = np.linspace(start_time, start_time+(self.nb_frames-1)*fps, self.nb_frames).tolist()
|
|
70
|
+
|
|
71
|
+
def totorch(self):
|
|
72
|
+
"""
|
|
73
|
+
Convert the numy array into a pytorch tensor.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
----------
|
|
77
|
+
pytorch tensor.
|
|
78
|
+
"""
|
|
79
|
+
# import torch late as it is not in the standard requirements for simple-ffmpeg-batch-io
|
|
80
|
+
import torch
|
|
81
|
+
return torch.from_numpy(self.data)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Create a ``FrameCounter`` to follow elapsed time in audio or video file in read/write mode. Static utility function allows to get/format elapsed time.
|
|
3
|
+
|
|
4
|
+
Authors
|
|
5
|
+
-------
|
|
6
|
+
Dominique Vaufreydaz
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__authors__ = ("Dominique Vaufreydaz")
|
|
11
|
+
|
|
12
|
+
from typing import Union
|
|
13
|
+
|
|
14
|
+
# class to count frame, thus time, in audio/video files
|
|
15
|
+
class FrameCounter:
|
|
16
|
+
"""
|
|
17
|
+
Create a ``FrameCounter`` to follow elapsed time in audio/video file in read or write mode. Static utility functions allow to format elapsed time.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
class FrameCounterException(Exception):
|
|
21
|
+
"""
|
|
22
|
+
Dedicated exception class for FrameCounter class.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, message="Error while setting FrameCounter parameters."):
|
|
25
|
+
self.message = message
|
|
26
|
+
super().__init__(self.message)
|
|
27
|
+
|
|
28
|
+
_fps: float # (private)
|
|
29
|
+
""" Fps of current stream """
|
|
30
|
+
|
|
31
|
+
_frame_count: int # (private)
|
|
32
|
+
""" Frame count in the current stream """
|
|
33
|
+
|
|
34
|
+
def __init__(self, fps: Union[int, float]):
|
|
35
|
+
"""
|
|
36
|
+
Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
fps: int or float.
|
|
41
|
+
Frames per second of the associated stream.
|
|
42
|
+
"""
|
|
43
|
+
# check init fps value
|
|
44
|
+
self._fps = float(fps)
|
|
45
|
+
if self._fps <= 0.0:
|
|
46
|
+
raise FrameCounterException("fps must be > 0.0.")
|
|
47
|
+
|
|
48
|
+
# 2 modes
|
|
49
|
+
self._frame_count = 0 # at 00:00:00.000
|
|
50
|
+
|
|
51
|
+
# support +=
|
|
52
|
+
def __iadd__(self, other: Union[int, float]):
|
|
53
|
+
"""
|
|
54
|
+
Support += operator for FrameCounter.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
other: int or float.
|
|
59
|
+
If other is a float, add the number of frame to add 'other' seconds (thus other * self._fps samples).
|
|
60
|
+
If other is an int, add the value as a number of samples in the stream.
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(other,float):
|
|
63
|
+
# float means adding time
|
|
64
|
+
self._frame_count += int(other * self._fps) # number of second * Nb of elements per seconds
|
|
65
|
+
else:
|
|
66
|
+
# for int, add number of element
|
|
67
|
+
self._frame_count += other
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def frame_count(self):
|
|
72
|
+
"""
|
|
73
|
+
Property to get underlying self._frame_count. Idea is to control setter to valid setting values.
|
|
74
|
+
"""
|
|
75
|
+
return self._frame_count
|
|
76
|
+
|
|
77
|
+
@frame_count.setter
|
|
78
|
+
def frame_count(self, value: int):
|
|
79
|
+
"""
|
|
80
|
+
Setter for underlying self._frame_count controlling setting value.
|
|
81
|
+
"""
|
|
82
|
+
if value < 0:
|
|
83
|
+
raise FrameCounterException("frame_count must be >= 0")
|
|
84
|
+
self._frame_count = value
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def fps(self):
|
|
88
|
+
"""
|
|
89
|
+
Property to get underlying self._fps.
|
|
90
|
+
"""
|
|
91
|
+
return self._fps
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def format_time(nb_frames: int, fps: float, show_ms : bool = True, show_days : bool = False) -> str:
|
|
95
|
+
"""
|
|
96
|
+
Static function to format time given by a number of frames and an fps. show_ms value defines if we show milliseconds,
|
|
97
|
+
show_days to show days instead of cummulative hour count.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
nb_frames: int.
|
|
102
|
+
Number of samples already present in the stream.
|
|
103
|
+
|
|
104
|
+
fps: float.
|
|
105
|
+
Fps of the associated stream.
|
|
106
|
+
|
|
107
|
+
show_ms: bool.
|
|
108
|
+
Flag to say if we want to show milliseconds in the output str.
|
|
109
|
+
|
|
110
|
+
show_days: bool.
|
|
111
|
+
Flag to say if we want to show says instead of cumulative hours in the output str.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
str representing corresponding time. Either 26:15:00 (show_ms=False, show_days=False), 26:15:00.500 (show_ms=True, show_days=False)
|
|
116
|
+
or 1 day(s) 02:15:00.500 (show_ms=True, show_days=True)
|
|
117
|
+
"""
|
|
118
|
+
# exact time in seconds (float)
|
|
119
|
+
exact_seconds = nb_frames / fps
|
|
120
|
+
|
|
121
|
+
# integer part for days/hours/minutes/seconds
|
|
122
|
+
total_seconds = int(exact_seconds)
|
|
123
|
+
|
|
124
|
+
# milliseconds = decimal part * 1000
|
|
125
|
+
millis = int(round((exact_seconds - total_seconds) * 1000))
|
|
126
|
+
|
|
127
|
+
# handle the case where rounding results in 1000 ms
|
|
128
|
+
if millis == 1000:
|
|
129
|
+
millis = 0
|
|
130
|
+
total_seconds += 1
|
|
131
|
+
|
|
132
|
+
if show_days:
|
|
133
|
+
# compute number of days, hours, minutes, seconds
|
|
134
|
+
days, mod = divmod(total_seconds, 24 * 3600)
|
|
135
|
+
hours, mod = divmod(mod, 3600)
|
|
136
|
+
minutes, seconds = divmod(mod, 60)
|
|
137
|
+
|
|
138
|
+
if days > 0:
|
|
139
|
+
days = f"{days} days(s) "
|
|
140
|
+
else:
|
|
141
|
+
days = ""
|
|
142
|
+
else:
|
|
143
|
+
days = "" # no day as show_days = False
|
|
144
|
+
hours, mod = divmod(total_seconds, 3600)
|
|
145
|
+
minutes, seconds = divmod(mod, 60)
|
|
146
|
+
|
|
147
|
+
if show_ms == True:
|
|
148
|
+
millis = f".{millis:03d}"
|
|
149
|
+
else:
|
|
150
|
+
millis = ""
|
|
151
|
+
|
|
152
|
+
return f"{days}{hours:02d}:{minutes:02d}:{seconds:02d}{millis}"
|
|
153
|
+
|
|
154
|
+
def get_elapsed_time_as_str(self) -> str:
|
|
155
|
+
"""
|
|
156
|
+
Get elapsed time as string representing a float value rounded to 3 decimals.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
str representing corresponding time in float format rounded to 3 decimals.
|
|
161
|
+
"""
|
|
162
|
+
return f"{float(self._frame_count)/self._fps:.3f}"
|
|
163
|
+
|
|
164
|
+
def get_formated_elapsed_time_as_str(self, show_ms : bool = True, show_days : bool = False) -> str:
|
|
165
|
+
"""
|
|
166
|
+
Get elapsed time as string representing time with different mode (see ``FrameCounter.format_time`` for parameter explanation).
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
str representing corresponding time in float format rounded to 3 decimals.
|
|
170
|
+
"""
|
|
171
|
+
# frame count to time correction is done in format_time
|
|
172
|
+
return FrameCounter.format_time(self._frame_count, self._fps, show_ms, show_days)
|
|
173
|
+
|
|
174
|
+
def get_elapsed_time(self) -> float:
|
|
175
|
+
"""
|
|
176
|
+
Get elapsed time as float value rounded to 3 decimals.
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
str representing corresponding time in float format rounded to 3 decimals.
|
|
181
|
+
"""
|
|
182
|
+
return round(float(self._frame_count)/self._fps,3)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Definition of the PipeMode for ``AudioIO`` and ``VideoIO`` classes.
|
|
3
|
+
|
|
4
|
+
Authors
|
|
5
|
+
-------
|
|
6
|
+
Dominique Vaufreydaz (inspired from original C++ code: https://github.com/Vaufreyd/ReadWriteVideosWithOpenCV)
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__authors__ = ("Dominique Vaufreydaz")
|
|
11
|
+
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
class PipeMode(Enum):
|
|
15
|
+
"""
|
|
16
|
+
Enum class for pipe opening type.
|
|
17
|
+
"""
|
|
18
|
+
UNK_MODE = 0
|
|
19
|
+
""" No pipe mode defined """
|
|
20
|
+
|
|
21
|
+
READ_MODE = 1
|
|
22
|
+
""" Read mode (get data from pipe) """
|
|
23
|
+
|
|
24
|
+
WRITE_MODE = 2
|
|
25
|
+
""" Write mode (write data to pipe) """
|
|
26
|
+
|