simple_ffmpeg_batch_io

Reading and writing image and audio batches from/to video and audio files using an FFmpeg backend, with a simple Python API built on top of numpy.

Features

  • Read batches of audio and video frames from video and audio files into numpy arrays.
  • Write batches of frames from numpy arrays to video or audio files, even compressed
  • Uses static_ffmpeg to provide FFmpeg binaries in a portable way. Simple-ffmpeg-batch-io provide ffmpeg and ffprobe as commands within the virtual environment
  • Designed for machine learning and audio/video generation pipelines.

Installation of last version

pip install simple-ffmpeg-batch-io

Documentation

Automatically generated documentation is available online.

Examples

Handling video files (i.e. images from video files)

Read video file at its own frame rate and frame shape

# Open it with VideoIO old way (C++ style)
inputVideo = VideoIO()
inputVideo.open(video_filename) # video file is a str or a path

# or open it a more pythonish way
inputVideo = VideoIO.reader(video_filename) # video file is a str or a path

# Here, one can read inputVideo.width, inputVideo.height, inputVideo.fps
print(inputVideo.width, inputVideo.height, inputVideo.fps)

# read one frame
frame = inputVideo.read_frame() # Here frame is a numpy arrays of shape (Width,height,channels). Channel is 3 as we support only 3 channels for the moment.

# Process video frame by frame
for frame in inputVideo.iter_frames():
    # Process frame. Here frame is a numpy arrays of shape (Width,height,channels). Channel is 3 as we support only 3 channels for the moment.
    process( frame )

# or process video using batches
n = 10
for batch in inputVideo.iter_batches(n):
    # Process batch. Here batch is a numpy arrays of shape (n,Width,height,channels). Channel is 3 as we support only 3 channels for the moment.
    process( batch )

inputVideo.close()

# Read video frame by frame with associated timestamps using with context, no need to close after end of context, close is aotomùatically called.
with VideoIO.reader(video_filename) as inputVideo:
    for frame in inputVideo.iter_frames(with_timestamps = True):
        # Here frame is encapsulated within simple-ffmpeg-batch-io.FrameContainer object
        process( frame.data )    # numpy array of shape (Width,height,channels)
        print( frame.timestamps ) # python list of the timestamp associated to the frame (video here, but same for audio), here only one element as one frame is used

# OR
# Read video batch by batch with associated timestamps using with context, no need to close after end of context, close is aotomùatically called.
n = 10
with VideoIO.reader(video_filename) as inputVideo:
    for batch in inputVideo.iter_batches(n, with_timestamps = True):
        # Here batch is encapsulated within simple-ffmpeg-batch-io.FrameContainer object
        process( batch.data )    # numpy array of shape (n,Width,height,channels)
        print( batch.timestamps ) # python list of timestamps associated to each frame (video here, but same for audio), here only one element as one frame is used

Read video file with more parameters

from simple_ffmpeg_batch_io import VideoIO

# Read video changing width, height, and fps
inputVideo = VideoIO.reader(video_file, width=100, height=100, fps=1.0)

# Read modifying only some of them
with VideoIO.reader(video_file, width=100, fps=2.0) as inputVideo:
    ...

Read video and write video file with dedicated ffmpeg parameters

from simple_ffmpeg_batch_io import VideoIO

# open file using filter:
# resizing to width=320, adapting height to keep aspect ratio while keep height pair (for some codec like H264)
# pixelising it to 5x5 pixels
# important note: one must not use decodingParams for scalling. Indeed, VideoIO class use filter to scale video, use width/height parameters
with VideoIO.reader(video_in_filename, width=320, height=320, decodingParams="-vf pixelize=w=5:h=5") as inputVideo,
     VideoIO.writer(video_out_filename, width=inputVideo.width, height=inputVideo.height, fps=inputVideo.fps ) as outputVideo:  # possible to add encodingParams to add filters, ...
    # iter over batch of 10s and write it to the output file
    batch_size = int( 10*inputVideo.fps )
    for batch in inputVideo.iter_batches(batch_size):
        outputVideo.write_batch(batch)

Handling audio from video or audio files

Read audio from audio file

from simple_ffmpeg_batch_io import AudioIO

# Read audio converting it to one channel, 16000 Hz, frame size of 1s as parameter is a float, start reading file at 2.0s
# default mode is plannar, i.e. samples are not interleaved, they are separated by channel after reading
# frame_size for subsequent call to read_frame, iter_frames, read_batch or iter_batches is 1.0s (16000 samples) as the value is a float, thus times in seconds.
inputAudio = AudioIO.reader(audio_filename, sample_rate=16000, channels=1, frame_size = 1.0, start_time = 2.0 )
# Read batches of 10 audio frames, each frame has 16000 sanples (1.0 second)
for audio_batch in inputAudio.iter_batches(10):
    ... # audio_batch is a np.array

# OR

# If the frame_size value is an int, frame_size is considered as a number of samples, for instance to have 0.5 seconds at 16 Khz (8000 samples for each frame)
with AudioIO.reader(audio_filename, sample_rate=16000, channels=1, frame_size = 8000, start_time = 2.0 ) as inputAudio:
    # Read batches of 10 audio frames, each frame has 8000 sanples
    for audio_batch in inputAudio.iter_batches(10, with_timestamps = True):
        .... # audio_batch is encapsulated within a FrameContainer object with_timestamps = True

Copy audio stream(s) from an audio file or a video file with an audio stream to a wav file

from simple_ffmpeg_batch_io import AudioIO

# Read audio data in interleaved mode (plannard = False) to avoid useless conversion to plannar using default frame_size (1 second). Sample rate remains the same.
with AudioIO.reader(audio_or_video_filename, plannar = False) as inputAudio:
    # Copy sample_rate and channels to the created file, overwrite existing output filename if any.
    with AudioIO.writer(audio_filename, sample_rate=inputAudio.sample_rate, channels=inputAudio.channels, plannar = False, writeOverExistingFile = True) as outputAudio:
        # Read batches of 10 audio frames (10 seconds as frame_size was by default 1 second when opening video)
        for audio_batch in inputAudio.iter_batches(10):
            outputAudio.write_batch(audio_batch)

# no need to close AudioIO objects as 'with' context do it automatically.

Static utility functions

VideoIO

from simple_ffmpeg_batch_io import VideoIO

# get (width, height, fps) of a video using a static function
print( VideoIO.get_params(video_filename) )

# get length of an audio stream as float (seconds.milliseconds)
print( VideoIO.get_time_in_sec(video_filename) )

AudioIO

from simple_ffmpeg_batch_io import AudioIO

# get (channels,sample_rate) of a stream using astatic function
print( AudioIO.get_params(audio_or_video_filename) )

# get length of video stream as float (seconds.milliseconds)
print( AudioIO.get_time_in_sec(audio_or_video_filename) )

Submodules

 1"""
 2.. include:: ../../README.md
 3 :start-after: # simple-ffmpeg-batch-io
 4
 5# Submodules
 6"""
 7
 8__authors__ = ("Dominique Vaufreydaz")
 9
10# __init__.py
11from .VideoIO import VideoIO
12from .AudioIO import AudioIO
13from .PipeMode import PipeMode
14from .FrameCounter import FrameCounter
15from .FrameContainer import FrameContainer
16
17__all__ = [
18    "VideoIO",
19    "AudioIO",
20    "FrameCounter",
21    "FrameContainer",
22    "PipeMode",
23]
class VideoIO:
 32class VideoIO:
 33    # "static" variables to ffmpeg, ffprobe executables
 34    videoProgram, paramProgram = static_ffmpeg.run.get_or_fetch_platform_executables_else_raise()
 35
 36    class VideoIOException(Exception):
 37        """
 38        Dedicated exception class for VideoIO class.
 39        """
 40        def __init__(self, message="Error while reading/writing video occurs"):
 41            self.message = message
 42            super().__init__(self.message)
 43
 44    class PixelFormat(Enum):
 45        """
 46        Enum class for supported input video type: GBR 24 bits or RGB 24 bis.
 47        """
 48        GBR24 = 'bgr24' # default format
 49        RGB24 = 'rgb24'
 50
 51    @classmethod
 52    def reader(cls, filename, **kwargs):
 53        """
 54        Create and open a VideoIO object in reader mode (read a video file)
 55
 56        See `VideoIO.open` for the full list
 57        of accepted parameters.
 58        """
 59        reader = cls()
 60        reader.open(filename,**kwargs)
 61        return reader
 62
 63    @classmethod
 64    def writer(cls, filename, width, height, fps, **kwargs):
 65        """
 66        Create and open a VideoIO object in writer mode (write a video file)
 67
 68        See `VideoIO.create` for the full list
 69        of accepted parameters.
 70        """
 71        writer = cls()
 72        writer.create(filename, width, height, fps, **kwargs)
 73        return writer
 74
 75    # To use with context manager "with VideoIO.reader(...) as f:' for instance
 76    def __enter__(self):
 77        """
 78        Method call at initialisation of a context manager like "with VideoIO.reader(...) as f:' for instance
 79        """
 80        # simply return myself
 81        return self
 82
 83    def __exit__(self, exc_type, exc_val, exc_tb):
 84        """
 85        Method call when existing of a context manager like "with VideoIO.reader(...) as f:' for instance
 86        """
 87        # close VideoIO
 88        self.close()
 89        return False
 90
 91    @staticmethod
 92    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 93        """
 94        Static method to get length of a video file in seconds including milliseconds as decimal part.
 95
 96        Parameters
 97        ----------
 98        filename : str or path
 99            Video file name.
100
101        debug : bool (default False)
102            Show debug info.
103
104        log_level: int (default 16)
105            Log level to pass to the underlying ffmpeg/ffprobe command.
106        
107        Returns
108        ----------
109        float
110            Length in seconds of video file (including milliseconds as decimal part)
111        """
112        
113        cmd = [VideoIO.paramProgram, # ffprobe
114                    '-hide_banner',
115                    '-loglevel', str(logLevel),
116                    '-show_entries', 'format=duration',
117                    '-of', 'default=noprint_wrappers=1:nokey=1',
118                    filename
119                    ]
120
121        if debug == True:
122            print(' '.join(cmd))
123
124        # call ffprobe and get params in one single line
125        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
126        output = lpipe.stdout.readlines()
127        lpipe.terminate()
128        # transform Bytes output to one single string
129        output = ''.join( [element.decode('utf-8') for element in output])
130
131        try:
132            return float(output)
133        except (ValueError, TypeError):
134            return None
135
136    @staticmethod
137    def get_params(filename, *, debug=False, logLevel=16):
138        """
139        Static method to get params (width, height, fps) from a video file.
140
141        Parameters
142        ----------
143        filename: str or path
144            Video filename.
145
146        debug: bool (default (False)
147            Show debug info.
148
149        log_level: int (default 16)
150            Log level to pass to the underlying ffmpeg/ffprobe command.
151
152        Returns
153        ----------
154        tuple
155            Tuple containing (width, height, fps) of the video
156        """
157        cmd = [VideoIO.paramProgram, # ffprobe
158                    '-hide_banner',
159                    '-loglevel', str(logLevel),
160                    '-show_entries', 'stream=width,height,r_frame_rate',
161                    filename
162                    ]
163
164        if debug == True:
165            print(' '.join(cmd))
166
167        # call ffprobe and get params in one single line
168        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
169        output = lpipe.stdout.readlines()
170        lpipe.terminate()
171        # transform Bytes output to one single string
172        output = ''.join( [element.decode('utf-8') for element in output])
173
174        pattern_width = r'width=(\d+)'
175        pattern_height = r'height=(\d+)'
176        pattern_fps = r'r_frame_rate=(\d+)/(\d+)'
177
178        # Search for values in the ffprobe output
179        match_width = re.search(pattern_width, output, flags=re.MULTILINE)
180        match_height = re.search(pattern_height, output, flags=re.MULTILINE)
181        match_fps = re.search(pattern_fps, output, flags=re.MULTILINE)
182
183        # Extraction des valeurs
184        if match_width:
185            width = int(match_width.group(1))
186        else:
187            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
188
189        if match_height:
190            height = int(match_height.group(1))
191        else:
192            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
193
194        if match_fps:
195            numerator = float(match_fps.group(1))
196            denominator = float(match_fps.group(2))
197            fps = numerator / denominator
198        else:
199            raise VideoIO.VideoIOException("Unable to get frame rate (fps) of '" + filename + "'")
200
201        return (width, height, fps)
202
203    # Attributes
204    mode: PipeMode
205    """ Pipemode of the current object (default PipeMode.UNK_MODE)"""
206
207    loglevel: int
208    """ loglevel of the underlying ffmpeg backend for this object (default 16)"""
209
210    debugModel: bool
211    """ debutMode flag for this object (print debut info, default False)"""
212
213    width: int
214    """ width of images (default -1) """
215
216    height: int
217    """ height of images (default -1) """
218
219    fps: float
220    """ fps of video (default -1.0) """
221
222    pipe: sp.Popen
223    """ pipe object to ffmpeg/ffprobe (default None)"""
224
225    shape: tuple
226    """ Shape of images (default (None, None, None))"""
227
228    imageSize: int
229    """ Weight in bytes of one image (default -1)"""
230
231    filename: str
232    """ Filename of the video file (default None)"""
233
234    frame_counter: FrameCounter
235    """ `Framecounter` object to count ellapsed time (default None)"""
236
237    def __init__(self, *, logLevel = 16, debugMode = False):
238        """
239        Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode
240
241        Parameters
242        ----------
243        log_level: int (default 16)
244            Log level to pass to the underlying ffmpeg/ffprobe command.
245
246        debugMode: bool (default (False)
247            Show debug info. while processing video
248        """
249
250        self.mode = PipeMode.UNK_MODE
251        self.logLevel = logLevel
252        self.debugMode = debugMode
253
254        # Call init() method
255        self.init()
256
257    def init(self):
258        """
259        Init or reinit a VideoIO object.
260        """
261        self.width  = -1
262        self.height = -1
263        self.fps = -1.0
264        self.pipe = None
265        self.shape = (None, None, None)
266        self.imageSize = -1
267        self.filename = None
268        self.frame_counter = None
269
270    _repr_exclude = {"pipe"}
271    """ List of excluded attribute for string conversion. """
272
273    # converting the object to a string representation
274    def __repr__(self):
275        """
276        Convert object (excluding attributes in _repr_exclude) to string representation.
277        """
278        attrs = ", ".join(
279            f"{k}={v!r}"
280            for k, v in self.__dict__.items()
281            if k not in self._repr_exclude
282        )
283        return f"{self.__class__.__name__}({attrs})"
284
285    __str__ = __repr__
286    """ String representation """
287
288    def get_elapsed_time_as_str(self) -> str:
289        """
290        Method to get elapsed time (float value) as str from `frame_counter` attribute.
291
292        Returns
293        ----------
294        str or None
295            Elapsed time (float value) as str, "15.500" for instance for 15 secondes and 500 milliseconds
296            None if no frame counter are available.
297        """
298        if self.frame_counter is None:
299            return None
300        return self.frame_counter.get_elapsed_time_as_str()
301
302    def get_formated_elapsed_time_as_str(self,show_ms=True) -> str:
303        """
304        Method to get elapsed time (hour format) as str from `frame_counter` attribute.
305
306        Returns
307        ----------
308        str or None
309            Elapsed time (float value) as str, "00:00:15.500" for instance for 15 secondes and 500 milliseconds
310            None if no frame counter are available.
311        """
312        if self.frame_counter is None:
313            return None
314        return self.frame_counter.get_formated_elapsed_time_as_str()
315
316    def get_elapsed_time(self) -> float:
317        """
318        Method to get elapsed time as float value rounded to 3 decimals (millisecond) from `frame_counter` attribute.
319
320        Returns
321        ----------
322        float or None
323            Elapsed time (float value) as str, 15.500 for instance for 15 secondes and 500 milliseconds
324            None if no frame counter are available.
325        """
326        if self.frame_counter is None:
327            return None
328        return self.frame_counter.get_elapsed_time()
329
330    def is_opened(self) -> bool:
331        """
332        Method to get status of the underlying pipe to ffmpeg.
333
334        Returns
335        ----------
336        bool
337            True if pipe is opened (reading or writing mode), False if not.
338        """
339        # is the pipe opened?
340        if self.pipe is not None and self.pipe.poll() is None:
341            return True
342
343        return False
344
345    def close(self) -> None:
346        """
347        Method to close current pipe to ffmpeg (if any). Ffmpeg/ffprobe will be terminated. Object can be reused using open or create methods.
348        """
349        if self.pipe is not None:
350            if self.mode == PipeMode.WRITE_MODE:
351                # killing will make ffmpeg not finish properly the job, close the pipe
352                # to let it know that no more data are comming
353                self.pipe.stdin.close()
354            else: # self.mode == PipeMode.READ_MODE
355                # in read mode, no need to be nice, send SIGTERM on Linux,/Kill it on windows
356                self.pipe.kill()
357
358            # wait for subprocess to end
359            self.pipe.wait()
360
361        # reinit object for later use
362        self.init()
363
364    def create(self, filename, width, height, fps, *, writeOverExistingFile = False,
365                     inputEncoding = PixelFormat.GBR24, encodingParams = None ) -> bool:
366        """
367        Method to create a video using parametrized access through ffmpeg. Importante note: calling create
368        on a VideoIO will close any former open video.
369
370        Parameters
371        ----------
372        filename: str or path
373            filename of path to the file (mp4, avi, ...)
374
375        width: int
376            If defined as a positive value, width of output images will be set to this value.
377
378        height: int
379            If defined as a positive value, height of output images will be set to this value.
380
381        fps:
382            If defined as a positive value, fps of output video will be set to this value.
383
384        inputEncoding: PixelFormat optional (default PixelFormat.BGR24)
385            Define order of channels for channels. Possible values are PixelFormat.BGR24 or PixelFormat.RGB24.
386
387        encodingParams: str optional (default None)
388            Parameter to pass to ffmpeg to encode video like video filters.
389
390        Returns
391        ----------
392        bool
393            Was the creation successfull
394        """
395
396        # Close if already opened
397        self.close()
398
399        # Set geometry/fps of the video stream from params
400        self.width = int(width)
401        self.height = int(height)
402        self.fps = float(fps)
403
404        # Check params
405        if self.width <= 0 or self.height <= 0 or self.fps <= 0.0:
406            raise self.VideoIOException("Bad parameters: width={}, height={}, fps={:3f}".format(self.width,self.height,self.fps))
407
408        # Params are ok, set shape and image size
409        self.shape     = (self.height,self.width,3)
410        self.imageSize = self.height * self.width * 3
411
412        # Video params are set, open the video
413        cmd = [self.videoProgram] # ffmpeg
414
415        if writeOverExistingFile == True:
416            cmd.extend(['-y'])
417
418        cmd.extend(['-hide_banner',
419            '-nostats',
420            '-loglevel', str(self.logLevel),
421            '-f', 'rawvideo', '-vcodec', 'rawvideo', '-pix_fmt', inputEncoding.value,
422            '-video_size', f"{self.width}x{self.height}",
423            '-r', "{:.3f}".format(self.fps),
424            '-i', '-'])
425
426        if encodingParams is not None:
427            cmd.extend(encodingParams.split())
428
429        cmd.extend( ['-an', filename ] )
430
431        if self.debugMode == True:
432            print( ' '.join(cmd), file=sys.stderr )
433
434        # store filename and set mode
435        self.filename = filename
436        self.mode = PipeMode.WRITE_MODE
437
438        # Call ffmpeg in write mode
439        try:
440            self.pipe = sp.Popen(cmd, stdin=sp.PIPE)
441            self.frame_counter = FrameCounter(self.fps)
442        except Exception as e:
443            # if pipe failed, reinit object and raise exception
444            self.init()
445            raise
446
447        return True
448
449    def open( self, filename, *, width = -1, height = -1, fps = -1.0, outputEncoding = PixelFormat.GBR24,
450                    decodingParams = None, start_time = 0.0 ) -> bool:
451        """
452        Method to read video using parametrized access through ffmpeg. Importante note: calling open
453        on a VideoIO will close any former open video.
454
455        Parameters
456        ----------
457        filename: str or path
458            filename of path to the file (mp4, avi, ...)
459
460        width: int optional (default -1)
461            If defined as a positive value, width of input images will be converted to this value.
462
463        height: int optional (default -1)
464            If defined as a positive value, height of input images will be converted to this value.
465
466        fps: float optional (default -1.0)
467            If defined as a positive value, fps of input video will be converted to this value.
468
469        outputEncoding: PixelFormat optional (default PixelFormat.BGR24)
470            Define order of channels for channels. Possible values are PixelFormat.BGR24 or PixelFormat.RGB24.
471
472        decodingParams: str optional (default None)
473            Parameter to pass to ffmpeg to decode video like filters.
474
475        start_time: float optional (default 0.0)
476            Define the reading start time. If not set, reading at beginning of the video.
477
478        Returns
479        ----------
480        bool
481            Was the opening successfull
482        """
483
484        # Close if already opened
485        self.close()
486
487        # Force conversion of parameters
488        width = int(width)
489        height = int(height)
490        fps = float(fps)
491
492        # get parameters from video
493        self.width, self.height, self.fps = self.getVideoParams(filename)
494
495        # check if parameters ask to overide video parameters
496        # TODO: add support for negative value (automatic preservation of aspect ratio)
497        if width > 0:
498            self.width = width
499        if height > 0:
500            self.height = height
501        if fps > 0.0:
502            self.fps = fps
503
504        # Params are ok, set shape and image size
505        self.shape = (self.height,self.width,3)
506        self.imageSize = self.height * self.width * 3
507
508        # Video params are set, open the video
509        cmd = [self.videoProgram, # ffmpeg
510                    '-hide_banner',
511                    '-nostats',
512                    '-loglevel', str(self.logLevel)]
513
514        if start_time < 0.0:
515            pass
516        elif start_time > 0.0:
517            cmd.extend(["-ss", f"{start_time}"])    # set start time if any
518
519        cmd.extend( ['-i', filename] )
520
521        video_filters = '' # empty
522        if decodingParams is not None:
523            decodingParams = decodingParams.split()
524            # walk over decodingParams for specific params
525            i = 0
526            while i < len(decodingParams):
527                if decodingParams[i] == '-vf':
528                    decodingParams.pop(i)  # remove '-vf'
529                    if i < len(decodingParams):
530                        video_filters += ','+decodingParams.pop(i)  # remove parameters from list too
531                    # to do : add support to other option like -y
532                else:
533                    i += 1
534        else:
535            decodingParams = []
536
537        cmd.extend( ['-vf', f'scale={self.width}:{self.height}{video_filters}', # rescale (or not if shape is original one), add specific video filters
538                    *(decodingParams),
539                    '-f', 'rawvideo', '-vcodec', 'rawvideo', '-pix_fmt', outputEncoding.value, # input expected coding
540                    '-an', # no audio
541                     '-r', f"{self.fps}",
542                     '-' # output to stdout
543                    ] )
544
545        if self.debugMode == True:
546            print( ' '.join(cmd) )
547
548        # store filename and set mode to READ_MODE
549        self.filename = filename
550        self.mode = PipeMode.READ_MODE
551
552        # call ffmpeg in read mode
553        try:
554            self.pipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
555            self.frame_counter = FrameCounter(self.fps)
556            if start_time > 0.0:
557                self.frame_counter += start_time # adding with float means adding time
558        except Exception as e:
559            # if pipe failed, reinit object and raise exception
560            self.init()
561            raise
562
563        return True
564
565    def read_frame(self, with_timestamps = False):
566        """
567        Read next frame from the video
568
569        Parameters
570        ----------
571        with_timestamps: bool optional (default False)
572            If set to True, the method returns a FrameContainer with the image and an array containing the associated timestamp(s)
573
574        Returns
575        ----------
576        nparray or FrameContainer
577            An image of shape (3,width,height). if with_timestamps is True, the return object is a FrameContainer with the image in ``FrameContainer.data`` and
578            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element for one frame).
579        """
580
581        if self.pipe is None:
582            raise self.VideoIOException("No pipe opened to {}. Call open(...) before reading a frame.".format(self.videoProgram))
583        # - pipe is in write mode
584        if self.mode != PipeMode.READ_MODE:
585            raise self.VideoIOException("Pipe to {} for '{}' not opened in read mode.".format(self.videoProgram, self.filename))
586
587        if with_timestamps:
588            # get elapsed time in video, it is time of next frame(s)
589            current_elapsed_time = self.get_elapsed_time()
590
591        # read rgb image from pipe
592        buffer = self.pipe.stdout.read(self.imageSize)
593        if len(buffer) != self.imageSize:
594            # not considered as an error, no more frame, no exception
595            return None
596
597        # get numpy UINT8 array from buffer
598        rgbImage = np.frombuffer(buffer, dtype = np.uint8).reshape(self.shape)
599
600        # increase frame_counter
601        self.frame_counter.frame_count += 1
602
603        # say to gc that this buffer is no longer needed
604        del buffer
605
606        if with_timestamps:
607            return FrameContainer(1,rgbImage,self.fps,current_elapsed_time)
608
609        return rgbImage
610
611    def read_batch(self, number_of_frames, with_timestamps = False) ->  Union[np.array, FrameContainer]:
612        """
613        Read next batch of images from the video
614
615        Parameters
616        ----------
617        number_of_frames: int
618            Number of desired images within the batch. The last batch of the video may have less images.
619            
620        with_timestamps: bool optional (default False)
621            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
622
623        Returns
624        ----------
625        nparray or FrameContainer
626            A batch of images of shape (n,3,width,height). if with_timestamps is True, the return object is a FrameContainer with the batch in ``FrameContainer.data`` and
627            the associated timestamps in ``FrameContainer.timestamps`` as an array (one element for each frame).
628        """
629
630        if self.pipe is None:
631            raise self.VideoIOException("No pipe opened to {}. Call open(...) before reading frames.".format(self.videoProgram))
632        # - pipe is in write mode
633        if self.mode != PipeMode.READ_MODE:
634            raise self.VideoIOException("Pipe to {} for '{}' not opened in read mode.".format(self.videoProgram, self.filename))
635
636        if with_timestamps:
637            # get elapsed time in video, it is time of next frame(s)
638            current_elapsed_time = self.get_elapsed_time()
639
640        # try to read complete batch
641        buffer = self.pipe.stdout.read(self.imageSize*number_of_frames)
642
643        # check if we have at least 1 Frame
644        if len(buffer) < self.imageSize:
645            # not considered as an error, no more frame, no exception
646            return None
647
648        # compute actual number of Frames
649        actualNbFrames = len(buffer)//self.imageSize
650
651        # get and reshape batch from buffer
652        batch = np.frombuffer(buffer, dtype = np.uint8).reshape((actualNbFrames, self.height, self.width, 3))
653
654        # increase frame_counter
655        self.frame_counter.frame_count += actualNbFrames
656        
657        # say to gc that this buffer is no longer needed
658        del buffer
659
660        if with_timestamps:
661            return FrameContainer(actualNbFrames, batch, self.fps, current_elapsed_time)
662
663        return batch
664
665    def write_frame(self, image) -> bool:
666        """
667        Write an image to the video
668
669        Parameters
670        ----------
671        image: nparray
672            The image of shape (3, width, height) to write to the video file in the PixelFormat provided when create was called.
673
674        Returns
675        ----------
676        bool
677            Writing was successful or not.
678        """
679        
680        # Check params
681        # - pipe exists
682        if self.pipe is None:
683            raise self.VideoIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.videoProgram))
684        # - pipe is in write mode
685        if self.mode != PipeMode.WRITE_MODE:
686            raise self.VideoIOException("Pipe to {} for '{}' not opened in write mode.".format(self.videoProgram, self.filename))
687        # - shape of image is fine, thus we have pixels for a full compatible frame
688        if image.shape != self.shape:
689            raise self.VideoIOException("Wong image shape: {} expected {}.".format(image.shape,self.shape))
690        # - type of data is UINT8
691        if image.dtype != np.uint8:
692            raise self.VideoIOException("Wong pixel type: {} expected np.uint8.".format(image.dtype))
693
694        # write frame
695        buffer = image.tobytes()
696        if self.pipe.stdin.write( buffer ) < self.imageSize:
697            print( "Error writing frame to" )
698            return False
699
700        # increase frame_counter
701        self.frame_counter.frame_count += 1
702
703        # say to gc that this buffer is no longer needed 
704        del buffer
705
706        return True
707
708    def write_batch(self, batch) -> bool:
709        """
710        Write a batch of images to the video
711
712        Parameters
713        ----------
714        batch: nparray
715            A batch of images to write to the video file in the PixelFormat provided when create was called.
716
717        Returns
718        ----------
719        bool
720            Writing was successful or not.
721        """
722
723        # Check params
724        # - pipe exists
725        if self.pipe is None:
726            raise self.VideoIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.videoProgram))
727        # - pipe is in write mode
728        if self.mode != PipeMode.WRITE_MODE:
729            raise self.VideoIOException("Pipe to {} for '{}' not opened in write mode.".format(self.videoProgram, self.filename))
730        # - shape of images in batch is fine
731        if batch.shape[-3:] != self.shape:
732            raise self.VideoIOException("Wrong image shape in batch: {} expected {}.".format(batch.shape[-3:], self.shape))
733        # - we have the right amount of pixels for the full batch
734        if batch.size != (batch.shape[0]*self.imageSize):
735            raise self.VideoIOException("Wrong number of pixels in batch: {} expected {}.".format(batch.shape[-3:], self.imageSize))
736
737        # write frame
738        buffer = batch.tobytes()
739        if self.pipe.stdin.write( buffer ) < batch.size:
740            # say to gc that this buffer is no longer needed
741            del buffer
742            raise self.VideoIOException("Error writing batch to '{}'.".format(self.filename))
743
744        # increase frame_counter
745        self.frame_counter.frame_count += batch.shape[0]       
746            
747        # say to gc that this buffer is no longer needed
748        del buffer
749
750        return True
751
752    def iter_frames(self, with_timestamps = False):
753        """
754        Method to iterate on video frames using VideoIO obj.
755        for frame in obj.iter_frames():
756            ....
757
758        Parameters
759        ----------
760        with_timestamps: bool optional (default False)
761            If set to True, the method returns a FrameContainer with the batch and an array containing the associated timestamps to frames
762        """
763
764        try:
765            if self.mode == PipeMode.READ_MODE:
766                while self.isOpened():
767                    frame = self.readFrame(with_timestamps)
768                    if frame is not None:
769                        yield frame
770        finally:
771            self.close()
772
773    def iter_batches(self, batch_size : int, with_timestamps = False):
774        """
775        Method to iterate on batch of frames using VideoIO obj.
776        for image_batch in obj.iter_batches():
777            ....
778
779        Parameters
780        ----------
781        with_timestamps: bool optional (default False)
782            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
783        """
784        try:
785            if self.mode == PipeMode.READ_MODE:
786                while self.isOpened():
787                    batch = self.readBatch(batch_size, with_timestamps)
788                    if batch is not None:
789                        yield batch
790        finally:
791            self.close()
792
793    # function aliases to be compliant with original C++ version
794    getVideoTimeInSec = get_time_in_sec
795    getVideoParams = get_params
796    get_video_time_in_sec = get_time_in_sec
797    get_video_params = get_params
798    isOpened = is_opened
799    readFrame = read_frame
800    readBatch = read_batch
801    writeFrame = write_frame
802    writeBatch = write_batch
VideoIO(*, logLevel=16, debugMode=False)
237    def __init__(self, *, logLevel = 16, debugMode = False):
238        """
239        Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode
240
241        Parameters
242        ----------
243        log_level: int (default 16)
244            Log level to pass to the underlying ffmpeg/ffprobe command.
245
246        debugMode: bool (default (False)
247            Show debug info. while processing video
248        """
249
250        self.mode = PipeMode.UNK_MODE
251        self.logLevel = logLevel
252        self.debugMode = debugMode
253
254        # Call init() method
255        self.init()

Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode

Parameters

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

debugMode: bool (default (False) Show debug info. while processing video

@classmethod
def reader(cls, filename, **kwargs):
51    @classmethod
52    def reader(cls, filename, **kwargs):
53        """
54        Create and open a VideoIO object in reader mode (read a video file)
55
56        See `VideoIO.open` for the full list
57        of accepted parameters.
58        """
59        reader = cls()
60        reader.open(filename,**kwargs)
61        return reader

Create and open a VideoIO object in reader mode (read a video file)

See VideoIO.open for the full list of accepted parameters.

@classmethod
def writer(cls, filename, width, height, fps, **kwargs):
63    @classmethod
64    def writer(cls, filename, width, height, fps, **kwargs):
65        """
66        Create and open a VideoIO object in writer mode (write a video file)
67
68        See `VideoIO.create` for the full list
69        of accepted parameters.
70        """
71        writer = cls()
72        writer.create(filename, width, height, fps, **kwargs)
73        return writer

Create and open a VideoIO object in writer mode (write a video file)

See VideoIO.create for the full list of accepted parameters.

@staticmethod
def get_time_in_sec(filename, *, debug=False, logLevel=16):
 91    @staticmethod
 92    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 93        """
 94        Static method to get length of a video file in seconds including milliseconds as decimal part.
 95
 96        Parameters
 97        ----------
 98        filename : str or path
 99            Video file name.
100
101        debug : bool (default False)
102            Show debug info.
103
104        log_level: int (default 16)
105            Log level to pass to the underlying ffmpeg/ffprobe command.
106        
107        Returns
108        ----------
109        float
110            Length in seconds of video file (including milliseconds as decimal part)
111        """
112        
113        cmd = [VideoIO.paramProgram, # ffprobe
114                    '-hide_banner',
115                    '-loglevel', str(logLevel),
116                    '-show_entries', 'format=duration',
117                    '-of', 'default=noprint_wrappers=1:nokey=1',
118                    filename
119                    ]
120
121        if debug == True:
122            print(' '.join(cmd))
123
124        # call ffprobe and get params in one single line
125        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
126        output = lpipe.stdout.readlines()
127        lpipe.terminate()
128        # transform Bytes output to one single string
129        output = ''.join( [element.decode('utf-8') for element in output])
130
131        try:
132            return float(output)
133        except (ValueError, TypeError):
134            return None

Static method to get length of a video file in seconds including milliseconds as decimal part.

Parameters

filename : str or path Video file name.

debug : bool (default False) Show debug info.

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

float Length in seconds of video file (including milliseconds as decimal part)

@staticmethod
def get_params(filename, *, debug=False, logLevel=16):
136    @staticmethod
137    def get_params(filename, *, debug=False, logLevel=16):
138        """
139        Static method to get params (width, height, fps) from a video file.
140
141        Parameters
142        ----------
143        filename: str or path
144            Video filename.
145
146        debug: bool (default (False)
147            Show debug info.
148
149        log_level: int (default 16)
150            Log level to pass to the underlying ffmpeg/ffprobe command.
151
152        Returns
153        ----------
154        tuple
155            Tuple containing (width, height, fps) of the video
156        """
157        cmd = [VideoIO.paramProgram, # ffprobe
158                    '-hide_banner',
159                    '-loglevel', str(logLevel),
160                    '-show_entries', 'stream=width,height,r_frame_rate',
161                    filename
162                    ]
163
164        if debug == True:
165            print(' '.join(cmd))
166
167        # call ffprobe and get params in one single line
168        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
169        output = lpipe.stdout.readlines()
170        lpipe.terminate()
171        # transform Bytes output to one single string
172        output = ''.join( [element.decode('utf-8') for element in output])
173
174        pattern_width = r'width=(\d+)'
175        pattern_height = r'height=(\d+)'
176        pattern_fps = r'r_frame_rate=(\d+)/(\d+)'
177
178        # Search for values in the ffprobe output
179        match_width = re.search(pattern_width, output, flags=re.MULTILINE)
180        match_height = re.search(pattern_height, output, flags=re.MULTILINE)
181        match_fps = re.search(pattern_fps, output, flags=re.MULTILINE)
182
183        # Extraction des valeurs
184        if match_width:
185            width = int(match_width.group(1))
186        else:
187            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
188
189        if match_height:
190            height = int(match_height.group(1))
191        else:
192            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
193
194        if match_fps:
195            numerator = float(match_fps.group(1))
196            denominator = float(match_fps.group(2))
197            fps = numerator / denominator
198        else:
199            raise VideoIO.VideoIOException("Unable to get frame rate (fps) of '" + filename + "'")
200
201        return (width, height, fps)

Static method to get params (width, height, fps) from a video file.

Parameters

filename: str or path Video filename.

debug: bool (default (False) Show debug info.

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

tuple Tuple containing (width, height, fps) of the video

mode: PipeMode

Pipemode of the current object (default PipeMode.UNK_MODE)

loglevel: int

loglevel of the underlying ffmpeg backend for this object (default 16)

debugModel: bool

debutMode flag for this object (print debut info, default False)

width: int

width of images (default -1)

height: int

height of images (default -1)

fps: float

fps of video (default -1.0)

pipe: pdoc.extract._PdocDefusedPopen

pipe object to ffmpeg/ffprobe (default None)

shape: tuple

Shape of images (default (None, None, None))

imageSize: int

Weight in bytes of one image (default -1)

filename: str

Filename of the video file (default None)

frame_counter: FrameCounter

Framecounter object to count ellapsed time (default None)

logLevel
debugMode
def init(self):
257    def init(self):
258        """
259        Init or reinit a VideoIO object.
260        """
261        self.width  = -1
262        self.height = -1
263        self.fps = -1.0
264        self.pipe = None
265        self.shape = (None, None, None)
266        self.imageSize = -1
267        self.filename = None
268        self.frame_counter = None

Init or reinit a VideoIO object.

def get_elapsed_time_as_str(self) -> str:
288    def get_elapsed_time_as_str(self) -> str:
289        """
290        Method to get elapsed time (float value) as str from `frame_counter` attribute.
291
292        Returns
293        ----------
294        str or None
295            Elapsed time (float value) as str, "15.500" for instance for 15 secondes and 500 milliseconds
296            None if no frame counter are available.
297        """
298        if self.frame_counter is None:
299            return None
300        return self.frame_counter.get_elapsed_time_as_str()

Method to get elapsed time (float value) as str from frame_counter attribute.

Returns

str or None Elapsed time (float value) as str, "15.500" for instance for 15 secondes and 500 milliseconds None if no frame counter are available.

def get_formated_elapsed_time_as_str(self, show_ms=True) -> str:
302    def get_formated_elapsed_time_as_str(self,show_ms=True) -> str:
303        """
304        Method to get elapsed time (hour format) as str from `frame_counter` attribute.
305
306        Returns
307        ----------
308        str or None
309            Elapsed time (float value) as str, "00:00:15.500" for instance for 15 secondes and 500 milliseconds
310            None if no frame counter are available.
311        """
312        if self.frame_counter is None:
313            return None
314        return self.frame_counter.get_formated_elapsed_time_as_str()

Method to get elapsed time (hour format) as str from frame_counter attribute.

Returns

str or None Elapsed time (float value) as str, "00:00:15.500" for instance for 15 secondes and 500 milliseconds None if no frame counter are available.

def get_elapsed_time(self) -> float:
316    def get_elapsed_time(self) -> float:
317        """
318        Method to get elapsed time as float value rounded to 3 decimals (millisecond) from `frame_counter` attribute.
319
320        Returns
321        ----------
322        float or None
323            Elapsed time (float value) as str, 15.500 for instance for 15 secondes and 500 milliseconds
324            None if no frame counter are available.
325        """
326        if self.frame_counter is None:
327            return None
328        return self.frame_counter.get_elapsed_time()

Method to get elapsed time as float value rounded to 3 decimals (millisecond) from frame_counter attribute.

Returns

float or None Elapsed time (float value) as str, 15.500 for instance for 15 secondes and 500 milliseconds None if no frame counter are available.

def is_opened(self) -> bool:
330    def is_opened(self) -> bool:
331        """
332        Method to get status of the underlying pipe to ffmpeg.
333
334        Returns
335        ----------
336        bool
337            True if pipe is opened (reading or writing mode), False if not.
338        """
339        # is the pipe opened?
340        if self.pipe is not None and self.pipe.poll() is None:
341            return True
342
343        return False

Method to get status of the underlying pipe to ffmpeg.

Returns

bool True if pipe is opened (reading or writing mode), False if not.

def close(self) -> None:
345    def close(self) -> None:
346        """
347        Method to close current pipe to ffmpeg (if any). Ffmpeg/ffprobe will be terminated. Object can be reused using open or create methods.
348        """
349        if self.pipe is not None:
350            if self.mode == PipeMode.WRITE_MODE:
351                # killing will make ffmpeg not finish properly the job, close the pipe
352                # to let it know that no more data are comming
353                self.pipe.stdin.close()
354            else: # self.mode == PipeMode.READ_MODE
355                # in read mode, no need to be nice, send SIGTERM on Linux,/Kill it on windows
356                self.pipe.kill()
357
358            # wait for subprocess to end
359            self.pipe.wait()
360
361        # reinit object for later use
362        self.init()

Method to close current pipe to ffmpeg (if any). Ffmpeg/ffprobe will be terminated. Object can be reused using open or create methods.

def create( self, filename, width, height, fps, *, writeOverExistingFile=False, inputEncoding=<PixelFormat.GBR24: 'bgr24'>, encodingParams=None) -> bool:
364    def create(self, filename, width, height, fps, *, writeOverExistingFile = False,
365                     inputEncoding = PixelFormat.GBR24, encodingParams = None ) -> bool:
366        """
367        Method to create a video using parametrized access through ffmpeg. Importante note: calling create
368        on a VideoIO will close any former open video.
369
370        Parameters
371        ----------
372        filename: str or path
373            filename of path to the file (mp4, avi, ...)
374
375        width: int
376            If defined as a positive value, width of output images will be set to this value.
377
378        height: int
379            If defined as a positive value, height of output images will be set to this value.
380
381        fps:
382            If defined as a positive value, fps of output video will be set to this value.
383
384        inputEncoding: PixelFormat optional (default PixelFormat.BGR24)
385            Define order of channels for channels. Possible values are PixelFormat.BGR24 or PixelFormat.RGB24.
386
387        encodingParams: str optional (default None)
388            Parameter to pass to ffmpeg to encode video like video filters.
389
390        Returns
391        ----------
392        bool
393            Was the creation successfull
394        """
395
396        # Close if already opened
397        self.close()
398
399        # Set geometry/fps of the video stream from params
400        self.width = int(width)
401        self.height = int(height)
402        self.fps = float(fps)
403
404        # Check params
405        if self.width <= 0 or self.height <= 0 or self.fps <= 0.0:
406            raise self.VideoIOException("Bad parameters: width={}, height={}, fps={:3f}".format(self.width,self.height,self.fps))
407
408        # Params are ok, set shape and image size
409        self.shape     = (self.height,self.width,3)
410        self.imageSize = self.height * self.width * 3
411
412        # Video params are set, open the video
413        cmd = [self.videoProgram] # ffmpeg
414
415        if writeOverExistingFile == True:
416            cmd.extend(['-y'])
417
418        cmd.extend(['-hide_banner',
419            '-nostats',
420            '-loglevel', str(self.logLevel),
421            '-f', 'rawvideo', '-vcodec', 'rawvideo', '-pix_fmt', inputEncoding.value,
422            '-video_size', f"{self.width}x{self.height}",
423            '-r', "{:.3f}".format(self.fps),
424            '-i', '-'])
425
426        if encodingParams is not None:
427            cmd.extend(encodingParams.split())
428
429        cmd.extend( ['-an', filename ] )
430
431        if self.debugMode == True:
432            print( ' '.join(cmd), file=sys.stderr )
433
434        # store filename and set mode
435        self.filename = filename
436        self.mode = PipeMode.WRITE_MODE
437
438        # Call ffmpeg in write mode
439        try:
440            self.pipe = sp.Popen(cmd, stdin=sp.PIPE)
441            self.frame_counter = FrameCounter(self.fps)
442        except Exception as e:
443            # if pipe failed, reinit object and raise exception
444            self.init()
445            raise
446
447        return True

Method to create a video using parametrized access through ffmpeg. Importante note: calling create on a VideoIO will close any former open video.

Parameters

filename: str or path filename of path to the file (mp4, avi, ...)

width: int If defined as a positive value, width of output images will be set to this value.

height: int If defined as a positive value, height of output images will be set to this value.

fps: If defined as a positive value, fps of output video will be set to this value.

inputEncoding: PixelFormat optional (default PixelFormat.BGR24) Define order of channels for channels. Possible values are PixelFormat.BGR24 or PixelFormat.RGB24.

encodingParams: str optional (default None) Parameter to pass to ffmpeg to encode video like video filters.

Returns

bool Was the creation successfull

def open( self, filename, *, width=-1, height=-1, fps=-1.0, outputEncoding=<PixelFormat.GBR24: 'bgr24'>, decodingParams=None, start_time=0.0) -> bool:
449    def open( self, filename, *, width = -1, height = -1, fps = -1.0, outputEncoding = PixelFormat.GBR24,
450                    decodingParams = None, start_time = 0.0 ) -> bool:
451        """
452        Method to read video using parametrized access through ffmpeg. Importante note: calling open
453        on a VideoIO will close any former open video.
454
455        Parameters
456        ----------
457        filename: str or path
458            filename of path to the file (mp4, avi, ...)
459
460        width: int optional (default -1)
461            If defined as a positive value, width of input images will be converted to this value.
462
463        height: int optional (default -1)
464            If defined as a positive value, height of input images will be converted to this value.
465
466        fps: float optional (default -1.0)
467            If defined as a positive value, fps of input video will be converted to this value.
468
469        outputEncoding: PixelFormat optional (default PixelFormat.BGR24)
470            Define order of channels for channels. Possible values are PixelFormat.BGR24 or PixelFormat.RGB24.
471
472        decodingParams: str optional (default None)
473            Parameter to pass to ffmpeg to decode video like filters.
474
475        start_time: float optional (default 0.0)
476            Define the reading start time. If not set, reading at beginning of the video.
477
478        Returns
479        ----------
480        bool
481            Was the opening successfull
482        """
483
484        # Close if already opened
485        self.close()
486
487        # Force conversion of parameters
488        width = int(width)
489        height = int(height)
490        fps = float(fps)
491
492        # get parameters from video
493        self.width, self.height, self.fps = self.getVideoParams(filename)
494
495        # check if parameters ask to overide video parameters
496        # TODO: add support for negative value (automatic preservation of aspect ratio)
497        if width > 0:
498            self.width = width
499        if height > 0:
500            self.height = height
501        if fps > 0.0:
502            self.fps = fps
503
504        # Params are ok, set shape and image size
505        self.shape = (self.height,self.width,3)
506        self.imageSize = self.height * self.width * 3
507
508        # Video params are set, open the video
509        cmd = [self.videoProgram, # ffmpeg
510                    '-hide_banner',
511                    '-nostats',
512                    '-loglevel', str(self.logLevel)]
513
514        if start_time < 0.0:
515            pass
516        elif start_time > 0.0:
517            cmd.extend(["-ss", f"{start_time}"])    # set start time if any
518
519        cmd.extend( ['-i', filename] )
520
521        video_filters = '' # empty
522        if decodingParams is not None:
523            decodingParams = decodingParams.split()
524            # walk over decodingParams for specific params
525            i = 0
526            while i < len(decodingParams):
527                if decodingParams[i] == '-vf':
528                    decodingParams.pop(i)  # remove '-vf'
529                    if i < len(decodingParams):
530                        video_filters += ','+decodingParams.pop(i)  # remove parameters from list too
531                    # to do : add support to other option like -y
532                else:
533                    i += 1
534        else:
535            decodingParams = []
536
537        cmd.extend( ['-vf', f'scale={self.width}:{self.height}{video_filters}', # rescale (or not if shape is original one), add specific video filters
538                    *(decodingParams),
539                    '-f', 'rawvideo', '-vcodec', 'rawvideo', '-pix_fmt', outputEncoding.value, # input expected coding
540                    '-an', # no audio
541                     '-r', f"{self.fps}",
542                     '-' # output to stdout
543                    ] )
544
545        if self.debugMode == True:
546            print( ' '.join(cmd) )
547
548        # store filename and set mode to READ_MODE
549        self.filename = filename
550        self.mode = PipeMode.READ_MODE
551
552        # call ffmpeg in read mode
553        try:
554            self.pipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
555            self.frame_counter = FrameCounter(self.fps)
556            if start_time > 0.0:
557                self.frame_counter += start_time # adding with float means adding time
558        except Exception as e:
559            # if pipe failed, reinit object and raise exception
560            self.init()
561            raise
562
563        return True

Method to read video using parametrized access through ffmpeg. Importante note: calling open on a VideoIO will close any former open video.

Parameters

filename: str or path filename of path to the file (mp4, avi, ...)

width: int optional (default -1) If defined as a positive value, width of input images will be converted to this value.

height: int optional (default -1) If defined as a positive value, height of input images will be converted to this value.

fps: float optional (default -1.0) If defined as a positive value, fps of input video will be converted to this value.

outputEncoding: PixelFormat optional (default PixelFormat.BGR24) Define order of channels for channels. Possible values are PixelFormat.BGR24 or PixelFormat.RGB24.

decodingParams: str optional (default None) Parameter to pass to ffmpeg to decode video like filters.

start_time: float optional (default 0.0) Define the reading start time. If not set, reading at beginning of the video.

Returns

bool Was the opening successfull

def read_frame(self, with_timestamps=False):
565    def read_frame(self, with_timestamps = False):
566        """
567        Read next frame from the video
568
569        Parameters
570        ----------
571        with_timestamps: bool optional (default False)
572            If set to True, the method returns a FrameContainer with the image and an array containing the associated timestamp(s)
573
574        Returns
575        ----------
576        nparray or FrameContainer
577            An image of shape (3,width,height). if with_timestamps is True, the return object is a FrameContainer with the image in ``FrameContainer.data`` and
578            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element for one frame).
579        """
580
581        if self.pipe is None:
582            raise self.VideoIOException("No pipe opened to {}. Call open(...) before reading a frame.".format(self.videoProgram))
583        # - pipe is in write mode
584        if self.mode != PipeMode.READ_MODE:
585            raise self.VideoIOException("Pipe to {} for '{}' not opened in read mode.".format(self.videoProgram, self.filename))
586
587        if with_timestamps:
588            # get elapsed time in video, it is time of next frame(s)
589            current_elapsed_time = self.get_elapsed_time()
590
591        # read rgb image from pipe
592        buffer = self.pipe.stdout.read(self.imageSize)
593        if len(buffer) != self.imageSize:
594            # not considered as an error, no more frame, no exception
595            return None
596
597        # get numpy UINT8 array from buffer
598        rgbImage = np.frombuffer(buffer, dtype = np.uint8).reshape(self.shape)
599
600        # increase frame_counter
601        self.frame_counter.frame_count += 1
602
603        # say to gc that this buffer is no longer needed
604        del buffer
605
606        if with_timestamps:
607            return FrameContainer(1,rgbImage,self.fps,current_elapsed_time)
608
609        return rgbImage

Read next frame from the video

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the image and an array containing the associated timestamp(s)

Returns

nparray or FrameContainer An image of shape (3,width,height). if with_timestamps is True, the return object is a FrameContainer with the image in FrameContainer.data and the associated timestamp in FrameContainer.timestamps as an array (one element for one frame).

def read_batch( self, number_of_frames, with_timestamps=False) -> Union[<built-in function array>, FrameContainer]:
611    def read_batch(self, number_of_frames, with_timestamps = False) ->  Union[np.array, FrameContainer]:
612        """
613        Read next batch of images from the video
614
615        Parameters
616        ----------
617        number_of_frames: int
618            Number of desired images within the batch. The last batch of the video may have less images.
619            
620        with_timestamps: bool optional (default False)
621            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
622
623        Returns
624        ----------
625        nparray or FrameContainer
626            A batch of images of shape (n,3,width,height). if with_timestamps is True, the return object is a FrameContainer with the batch in ``FrameContainer.data`` and
627            the associated timestamps in ``FrameContainer.timestamps`` as an array (one element for each frame).
628        """
629
630        if self.pipe is None:
631            raise self.VideoIOException("No pipe opened to {}. Call open(...) before reading frames.".format(self.videoProgram))
632        # - pipe is in write mode
633        if self.mode != PipeMode.READ_MODE:
634            raise self.VideoIOException("Pipe to {} for '{}' not opened in read mode.".format(self.videoProgram, self.filename))
635
636        if with_timestamps:
637            # get elapsed time in video, it is time of next frame(s)
638            current_elapsed_time = self.get_elapsed_time()
639
640        # try to read complete batch
641        buffer = self.pipe.stdout.read(self.imageSize*number_of_frames)
642
643        # check if we have at least 1 Frame
644        if len(buffer) < self.imageSize:
645            # not considered as an error, no more frame, no exception
646            return None
647
648        # compute actual number of Frames
649        actualNbFrames = len(buffer)//self.imageSize
650
651        # get and reshape batch from buffer
652        batch = np.frombuffer(buffer, dtype = np.uint8).reshape((actualNbFrames, self.height, self.width, 3))
653
654        # increase frame_counter
655        self.frame_counter.frame_count += actualNbFrames
656        
657        # say to gc that this buffer is no longer needed
658        del buffer
659
660        if with_timestamps:
661            return FrameContainer(actualNbFrames, batch, self.fps, current_elapsed_time)
662
663        return batch

Read next batch of images from the video

Parameters

number_of_frames: int Number of desired images within the batch. The last batch of the video may have less images.

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames

Returns

nparray or FrameContainer A batch of images of shape (n,3,width,height). if with_timestamps is True, the return object is a FrameContainer with the batch in FrameContainer.data and the associated timestamps in FrameContainer.timestamps as an array (one element for each frame).

def write_frame(self, image) -> bool:
665    def write_frame(self, image) -> bool:
666        """
667        Write an image to the video
668
669        Parameters
670        ----------
671        image: nparray
672            The image of shape (3, width, height) to write to the video file in the PixelFormat provided when create was called.
673
674        Returns
675        ----------
676        bool
677            Writing was successful or not.
678        """
679        
680        # Check params
681        # - pipe exists
682        if self.pipe is None:
683            raise self.VideoIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.videoProgram))
684        # - pipe is in write mode
685        if self.mode != PipeMode.WRITE_MODE:
686            raise self.VideoIOException("Pipe to {} for '{}' not opened in write mode.".format(self.videoProgram, self.filename))
687        # - shape of image is fine, thus we have pixels for a full compatible frame
688        if image.shape != self.shape:
689            raise self.VideoIOException("Wong image shape: {} expected {}.".format(image.shape,self.shape))
690        # - type of data is UINT8
691        if image.dtype != np.uint8:
692            raise self.VideoIOException("Wong pixel type: {} expected np.uint8.".format(image.dtype))
693
694        # write frame
695        buffer = image.tobytes()
696        if self.pipe.stdin.write( buffer ) < self.imageSize:
697            print( "Error writing frame to" )
698            return False
699
700        # increase frame_counter
701        self.frame_counter.frame_count += 1
702
703        # say to gc that this buffer is no longer needed 
704        del buffer
705
706        return True

Write an image to the video

Parameters

image: nparray The image of shape (3, width, height) to write to the video file in the PixelFormat provided when create was called.

Returns

bool Writing was successful or not.

def write_batch(self, batch) -> bool:
708    def write_batch(self, batch) -> bool:
709        """
710        Write a batch of images to the video
711
712        Parameters
713        ----------
714        batch: nparray
715            A batch of images to write to the video file in the PixelFormat provided when create was called.
716
717        Returns
718        ----------
719        bool
720            Writing was successful or not.
721        """
722
723        # Check params
724        # - pipe exists
725        if self.pipe is None:
726            raise self.VideoIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.videoProgram))
727        # - pipe is in write mode
728        if self.mode != PipeMode.WRITE_MODE:
729            raise self.VideoIOException("Pipe to {} for '{}' not opened in write mode.".format(self.videoProgram, self.filename))
730        # - shape of images in batch is fine
731        if batch.shape[-3:] != self.shape:
732            raise self.VideoIOException("Wrong image shape in batch: {} expected {}.".format(batch.shape[-3:], self.shape))
733        # - we have the right amount of pixels for the full batch
734        if batch.size != (batch.shape[0]*self.imageSize):
735            raise self.VideoIOException("Wrong number of pixels in batch: {} expected {}.".format(batch.shape[-3:], self.imageSize))
736
737        # write frame
738        buffer = batch.tobytes()
739        if self.pipe.stdin.write( buffer ) < batch.size:
740            # say to gc that this buffer is no longer needed
741            del buffer
742            raise self.VideoIOException("Error writing batch to '{}'.".format(self.filename))
743
744        # increase frame_counter
745        self.frame_counter.frame_count += batch.shape[0]       
746            
747        # say to gc that this buffer is no longer needed
748        del buffer
749
750        return True

Write a batch of images to the video

Parameters

batch: nparray A batch of images to write to the video file in the PixelFormat provided when create was called.

Returns

bool Writing was successful or not.

def iter_frames(self, with_timestamps=False):
752    def iter_frames(self, with_timestamps = False):
753        """
754        Method to iterate on video frames using VideoIO obj.
755        for frame in obj.iter_frames():
756            ....
757
758        Parameters
759        ----------
760        with_timestamps: bool optional (default False)
761            If set to True, the method returns a FrameContainer with the batch and an array containing the associated timestamps to frames
762        """
763
764        try:
765            if self.mode == PipeMode.READ_MODE:
766                while self.isOpened():
767                    frame = self.readFrame(with_timestamps)
768                    if frame is not None:
769                        yield frame
770        finally:
771            self.close()

Method to iterate on video frames using VideoIO obj. for frame in obj.iter_frames(): ....

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the batch and an array containing the associated timestamps to frames

def iter_batches(self, batch_size: int, with_timestamps=False):
773    def iter_batches(self, batch_size : int, with_timestamps = False):
774        """
775        Method to iterate on batch of frames using VideoIO obj.
776        for image_batch in obj.iter_batches():
777            ....
778
779        Parameters
780        ----------
781        with_timestamps: bool optional (default False)
782            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
783        """
784        try:
785            if self.mode == PipeMode.READ_MODE:
786                while self.isOpened():
787                    batch = self.readBatch(batch_size, with_timestamps)
788                    if batch is not None:
789                        yield batch
790        finally:
791            self.close()

Method to iterate on batch of frames using VideoIO obj. for image_batch in obj.iter_batches(): ....

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames

@staticmethod
def getVideoTimeInSec(filename, *, debug=False, logLevel=16):
 91    @staticmethod
 92    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 93        """
 94        Static method to get length of a video file in seconds including milliseconds as decimal part.
 95
 96        Parameters
 97        ----------
 98        filename : str or path
 99            Video file name.
100
101        debug : bool (default False)
102            Show debug info.
103
104        log_level: int (default 16)
105            Log level to pass to the underlying ffmpeg/ffprobe command.
106        
107        Returns
108        ----------
109        float
110            Length in seconds of video file (including milliseconds as decimal part)
111        """
112        
113        cmd = [VideoIO.paramProgram, # ffprobe
114                    '-hide_banner',
115                    '-loglevel', str(logLevel),
116                    '-show_entries', 'format=duration',
117                    '-of', 'default=noprint_wrappers=1:nokey=1',
118                    filename
119                    ]
120
121        if debug == True:
122            print(' '.join(cmd))
123
124        # call ffprobe and get params in one single line
125        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
126        output = lpipe.stdout.readlines()
127        lpipe.terminate()
128        # transform Bytes output to one single string
129        output = ''.join( [element.decode('utf-8') for element in output])
130
131        try:
132            return float(output)
133        except (ValueError, TypeError):
134            return None

Static method to get length of a video file in seconds including milliseconds as decimal part.

Parameters

filename : str or path Video file name.

debug : bool (default False) Show debug info.

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

float Length in seconds of video file (including milliseconds as decimal part)

@staticmethod
def getVideoParams(filename, *, debug=False, logLevel=16):
136    @staticmethod
137    def get_params(filename, *, debug=False, logLevel=16):
138        """
139        Static method to get params (width, height, fps) from a video file.
140
141        Parameters
142        ----------
143        filename: str or path
144            Video filename.
145
146        debug: bool (default (False)
147            Show debug info.
148
149        log_level: int (default 16)
150            Log level to pass to the underlying ffmpeg/ffprobe command.
151
152        Returns
153        ----------
154        tuple
155            Tuple containing (width, height, fps) of the video
156        """
157        cmd = [VideoIO.paramProgram, # ffprobe
158                    '-hide_banner',
159                    '-loglevel', str(logLevel),
160                    '-show_entries', 'stream=width,height,r_frame_rate',
161                    filename
162                    ]
163
164        if debug == True:
165            print(' '.join(cmd))
166
167        # call ffprobe and get params in one single line
168        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
169        output = lpipe.stdout.readlines()
170        lpipe.terminate()
171        # transform Bytes output to one single string
172        output = ''.join( [element.decode('utf-8') for element in output])
173
174        pattern_width = r'width=(\d+)'
175        pattern_height = r'height=(\d+)'
176        pattern_fps = r'r_frame_rate=(\d+)/(\d+)'
177
178        # Search for values in the ffprobe output
179        match_width = re.search(pattern_width, output, flags=re.MULTILINE)
180        match_height = re.search(pattern_height, output, flags=re.MULTILINE)
181        match_fps = re.search(pattern_fps, output, flags=re.MULTILINE)
182
183        # Extraction des valeurs
184        if match_width:
185            width = int(match_width.group(1))
186        else:
187            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
188
189        if match_height:
190            height = int(match_height.group(1))
191        else:
192            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
193
194        if match_fps:
195            numerator = float(match_fps.group(1))
196            denominator = float(match_fps.group(2))
197            fps = numerator / denominator
198        else:
199            raise VideoIO.VideoIOException("Unable to get frame rate (fps) of '" + filename + "'")
200
201        return (width, height, fps)

Static method to get params (width, height, fps) from a video file.

Parameters

filename: str or path Video filename.

debug: bool (default (False) Show debug info.

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

tuple Tuple containing (width, height, fps) of the video

@staticmethod
def get_video_time_in_sec(filename, *, debug=False, logLevel=16):
 91    @staticmethod
 92    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 93        """
 94        Static method to get length of a video file in seconds including milliseconds as decimal part.
 95
 96        Parameters
 97        ----------
 98        filename : str or path
 99            Video file name.
100
101        debug : bool (default False)
102            Show debug info.
103
104        log_level: int (default 16)
105            Log level to pass to the underlying ffmpeg/ffprobe command.
106        
107        Returns
108        ----------
109        float
110            Length in seconds of video file (including milliseconds as decimal part)
111        """
112        
113        cmd = [VideoIO.paramProgram, # ffprobe
114                    '-hide_banner',
115                    '-loglevel', str(logLevel),
116                    '-show_entries', 'format=duration',
117                    '-of', 'default=noprint_wrappers=1:nokey=1',
118                    filename
119                    ]
120
121        if debug == True:
122            print(' '.join(cmd))
123
124        # call ffprobe and get params in one single line
125        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
126        output = lpipe.stdout.readlines()
127        lpipe.terminate()
128        # transform Bytes output to one single string
129        output = ''.join( [element.decode('utf-8') for element in output])
130
131        try:
132            return float(output)
133        except (ValueError, TypeError):
134            return None

Static method to get length of a video file in seconds including milliseconds as decimal part.

Parameters

filename : str or path Video file name.

debug : bool (default False) Show debug info.

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

float Length in seconds of video file (including milliseconds as decimal part)

@staticmethod
def get_video_params(filename, *, debug=False, logLevel=16):
136    @staticmethod
137    def get_params(filename, *, debug=False, logLevel=16):
138        """
139        Static method to get params (width, height, fps) from a video file.
140
141        Parameters
142        ----------
143        filename: str or path
144            Video filename.
145
146        debug: bool (default (False)
147            Show debug info.
148
149        log_level: int (default 16)
150            Log level to pass to the underlying ffmpeg/ffprobe command.
151
152        Returns
153        ----------
154        tuple
155            Tuple containing (width, height, fps) of the video
156        """
157        cmd = [VideoIO.paramProgram, # ffprobe
158                    '-hide_banner',
159                    '-loglevel', str(logLevel),
160                    '-show_entries', 'stream=width,height,r_frame_rate',
161                    filename
162                    ]
163
164        if debug == True:
165            print(' '.join(cmd))
166
167        # call ffprobe and get params in one single line
168        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
169        output = lpipe.stdout.readlines()
170        lpipe.terminate()
171        # transform Bytes output to one single string
172        output = ''.join( [element.decode('utf-8') for element in output])
173
174        pattern_width = r'width=(\d+)'
175        pattern_height = r'height=(\d+)'
176        pattern_fps = r'r_frame_rate=(\d+)/(\d+)'
177
178        # Search for values in the ffprobe output
179        match_width = re.search(pattern_width, output, flags=re.MULTILINE)
180        match_height = re.search(pattern_height, output, flags=re.MULTILINE)
181        match_fps = re.search(pattern_fps, output, flags=re.MULTILINE)
182
183        # Extraction des valeurs
184        if match_width:
185            width = int(match_width.group(1))
186        else:
187            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
188
189        if match_height:
190            height = int(match_height.group(1))
191        else:
192            raise VideoIO.VideoIOException("Unable to get geometry of '" + filename + "'")
193
194        if match_fps:
195            numerator = float(match_fps.group(1))
196            denominator = float(match_fps.group(2))
197            fps = numerator / denominator
198        else:
199            raise VideoIO.VideoIOException("Unable to get frame rate (fps) of '" + filename + "'")
200
201        return (width, height, fps)

Static method to get params (width, height, fps) from a video file.

Parameters

filename: str or path Video filename.

debug: bool (default (False) Show debug info.

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

tuple Tuple containing (width, height, fps) of the video

def isOpened(self) -> bool:
330    def is_opened(self) -> bool:
331        """
332        Method to get status of the underlying pipe to ffmpeg.
333
334        Returns
335        ----------
336        bool
337            True if pipe is opened (reading or writing mode), False if not.
338        """
339        # is the pipe opened?
340        if self.pipe is not None and self.pipe.poll() is None:
341            return True
342
343        return False

Method to get status of the underlying pipe to ffmpeg.

Returns

bool True if pipe is opened (reading or writing mode), False if not.

def readFrame(self, with_timestamps=False):
565    def read_frame(self, with_timestamps = False):
566        """
567        Read next frame from the video
568
569        Parameters
570        ----------
571        with_timestamps: bool optional (default False)
572            If set to True, the method returns a FrameContainer with the image and an array containing the associated timestamp(s)
573
574        Returns
575        ----------
576        nparray or FrameContainer
577            An image of shape (3,width,height). if with_timestamps is True, the return object is a FrameContainer with the image in ``FrameContainer.data`` and
578            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element for one frame).
579        """
580
581        if self.pipe is None:
582            raise self.VideoIOException("No pipe opened to {}. Call open(...) before reading a frame.".format(self.videoProgram))
583        # - pipe is in write mode
584        if self.mode != PipeMode.READ_MODE:
585            raise self.VideoIOException("Pipe to {} for '{}' not opened in read mode.".format(self.videoProgram, self.filename))
586
587        if with_timestamps:
588            # get elapsed time in video, it is time of next frame(s)
589            current_elapsed_time = self.get_elapsed_time()
590
591        # read rgb image from pipe
592        buffer = self.pipe.stdout.read(self.imageSize)
593        if len(buffer) != self.imageSize:
594            # not considered as an error, no more frame, no exception
595            return None
596
597        # get numpy UINT8 array from buffer
598        rgbImage = np.frombuffer(buffer, dtype = np.uint8).reshape(self.shape)
599
600        # increase frame_counter
601        self.frame_counter.frame_count += 1
602
603        # say to gc that this buffer is no longer needed
604        del buffer
605
606        if with_timestamps:
607            return FrameContainer(1,rgbImage,self.fps,current_elapsed_time)
608
609        return rgbImage

Read next frame from the video

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the image and an array containing the associated timestamp(s)

Returns

nparray or FrameContainer An image of shape (3,width,height). if with_timestamps is True, the return object is a FrameContainer with the image in FrameContainer.data and the associated timestamp in FrameContainer.timestamps as an array (one element for one frame).

def readBatch( self, number_of_frames, with_timestamps=False) -> Union[<built-in function array>, FrameContainer]:
611    def read_batch(self, number_of_frames, with_timestamps = False) ->  Union[np.array, FrameContainer]:
612        """
613        Read next batch of images from the video
614
615        Parameters
616        ----------
617        number_of_frames: int
618            Number of desired images within the batch. The last batch of the video may have less images.
619            
620        with_timestamps: bool optional (default False)
621            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
622
623        Returns
624        ----------
625        nparray or FrameContainer
626            A batch of images of shape (n,3,width,height). if with_timestamps is True, the return object is a FrameContainer with the batch in ``FrameContainer.data`` and
627            the associated timestamps in ``FrameContainer.timestamps`` as an array (one element for each frame).
628        """
629
630        if self.pipe is None:
631            raise self.VideoIOException("No pipe opened to {}. Call open(...) before reading frames.".format(self.videoProgram))
632        # - pipe is in write mode
633        if self.mode != PipeMode.READ_MODE:
634            raise self.VideoIOException("Pipe to {} for '{}' not opened in read mode.".format(self.videoProgram, self.filename))
635
636        if with_timestamps:
637            # get elapsed time in video, it is time of next frame(s)
638            current_elapsed_time = self.get_elapsed_time()
639
640        # try to read complete batch
641        buffer = self.pipe.stdout.read(self.imageSize*number_of_frames)
642
643        # check if we have at least 1 Frame
644        if len(buffer) < self.imageSize:
645            # not considered as an error, no more frame, no exception
646            return None
647
648        # compute actual number of Frames
649        actualNbFrames = len(buffer)//self.imageSize
650
651        # get and reshape batch from buffer
652        batch = np.frombuffer(buffer, dtype = np.uint8).reshape((actualNbFrames, self.height, self.width, 3))
653
654        # increase frame_counter
655        self.frame_counter.frame_count += actualNbFrames
656        
657        # say to gc that this buffer is no longer needed
658        del buffer
659
660        if with_timestamps:
661            return FrameContainer(actualNbFrames, batch, self.fps, current_elapsed_time)
662
663        return batch

Read next batch of images from the video

Parameters

number_of_frames: int Number of desired images within the batch. The last batch of the video may have less images.

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames

Returns

nparray or FrameContainer A batch of images of shape (n,3,width,height). if with_timestamps is True, the return object is a FrameContainer with the batch in FrameContainer.data and the associated timestamps in FrameContainer.timestamps as an array (one element for each frame).

def writeFrame(self, image) -> bool:
665    def write_frame(self, image) -> bool:
666        """
667        Write an image to the video
668
669        Parameters
670        ----------
671        image: nparray
672            The image of shape (3, width, height) to write to the video file in the PixelFormat provided when create was called.
673
674        Returns
675        ----------
676        bool
677            Writing was successful or not.
678        """
679        
680        # Check params
681        # - pipe exists
682        if self.pipe is None:
683            raise self.VideoIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.videoProgram))
684        # - pipe is in write mode
685        if self.mode != PipeMode.WRITE_MODE:
686            raise self.VideoIOException("Pipe to {} for '{}' not opened in write mode.".format(self.videoProgram, self.filename))
687        # - shape of image is fine, thus we have pixels for a full compatible frame
688        if image.shape != self.shape:
689            raise self.VideoIOException("Wong image shape: {} expected {}.".format(image.shape,self.shape))
690        # - type of data is UINT8
691        if image.dtype != np.uint8:
692            raise self.VideoIOException("Wong pixel type: {} expected np.uint8.".format(image.dtype))
693
694        # write frame
695        buffer = image.tobytes()
696        if self.pipe.stdin.write( buffer ) < self.imageSize:
697            print( "Error writing frame to" )
698            return False
699
700        # increase frame_counter
701        self.frame_counter.frame_count += 1
702
703        # say to gc that this buffer is no longer needed 
704        del buffer
705
706        return True

Write an image to the video

Parameters

image: nparray The image of shape (3, width, height) to write to the video file in the PixelFormat provided when create was called.

Returns

bool Writing was successful or not.

def writeBatch(self, batch) -> bool:
708    def write_batch(self, batch) -> bool:
709        """
710        Write a batch of images to the video
711
712        Parameters
713        ----------
714        batch: nparray
715            A batch of images to write to the video file in the PixelFormat provided when create was called.
716
717        Returns
718        ----------
719        bool
720            Writing was successful or not.
721        """
722
723        # Check params
724        # - pipe exists
725        if self.pipe is None:
726            raise self.VideoIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.videoProgram))
727        # - pipe is in write mode
728        if self.mode != PipeMode.WRITE_MODE:
729            raise self.VideoIOException("Pipe to {} for '{}' not opened in write mode.".format(self.videoProgram, self.filename))
730        # - shape of images in batch is fine
731        if batch.shape[-3:] != self.shape:
732            raise self.VideoIOException("Wrong image shape in batch: {} expected {}.".format(batch.shape[-3:], self.shape))
733        # - we have the right amount of pixels for the full batch
734        if batch.size != (batch.shape[0]*self.imageSize):
735            raise self.VideoIOException("Wrong number of pixels in batch: {} expected {}.".format(batch.shape[-3:], self.imageSize))
736
737        # write frame
738        buffer = batch.tobytes()
739        if self.pipe.stdin.write( buffer ) < batch.size:
740            # say to gc that this buffer is no longer needed
741            del buffer
742            raise self.VideoIOException("Error writing batch to '{}'.".format(self.filename))
743
744        # increase frame_counter
745        self.frame_counter.frame_count += batch.shape[0]       
746            
747        # say to gc that this buffer is no longer needed
748        del buffer
749
750        return True

Write a batch of images to the video

Parameters

batch: nparray A batch of images to write to the video file in the PixelFormat provided when create was called.

Returns

bool Writing was successful or not.

videoProgram = '/usr/local/lib/python3.12/site-packages/static_ffmpeg/bin/linux/ffmpeg'
paramProgram = '/usr/local/lib/python3.12/site-packages/static_ffmpeg/bin/linux/ffprobe'
class VideoIO.VideoIOException(builtins.Exception):
36    class VideoIOException(Exception):
37        """
38        Dedicated exception class for VideoIO class.
39        """
40        def __init__(self, message="Error while reading/writing video occurs"):
41            self.message = message
42            super().__init__(self.message)

Dedicated exception class for VideoIO class.

VideoIO.VideoIOException(message='Error while reading/writing video occurs')
40        def __init__(self, message="Error while reading/writing video occurs"):
41            self.message = message
42            super().__init__(self.message)
message
class VideoIO.PixelFormat(enum.Enum):
44    class PixelFormat(Enum):
45        """
46        Enum class for supported input video type: GBR 24 bits or RGB 24 bis.
47        """
48        GBR24 = 'bgr24' # default format
49        RGB24 = 'rgb24'

Enum class for supported input video type: GBR 24 bits or RGB 24 bis.

GBR24 = <PixelFormat.GBR24: 'bgr24'>
RGB24 = <PixelFormat.RGB24: 'rgb24'>
class AudioIO:
 32class AudioIO:
 33    # "static" variables  to ffmpeg, ffprobe executables
 34    audioProgram, paramProgram = static_ffmpeg.run.get_or_fetch_platform_executables_else_raise()
 35
 36    class AudioIOException(Exception):
 37        """
 38        Dedicated exception class for AudioIO class.
 39        """
 40        def __init__(self, message="Error while reading/writing video occurs"):
 41            self.message = message
 42            super().__init__(self.message)
 43
 44    class AudioFormat(Enum):
 45        """
 46        Enum class for supported input video type: 32-bit float is the only supported type for the moment.
 47        """
 48        PCM32LE = 'pcm_f32le' # default format (unique mode for the moment)
 49
 50    @classmethod
 51    def reader(cls, filename, **kwargs):
 52        """
 53        Create and open an AudioIO object in reader mode
 54
 55        See ``AudioIO.open`` for the full list of accepted parameters.
 56        """
 57        reader = cls()
 58        reader.open(filename, **kwargs)
 59        return reader
 60
 61    @classmethod
 62    def writer(cls, filename, sample_rate, channels, **kwargs):
 63        """
 64        Create and open an AudioIO object in writer mode
 65
 66        See ``AudioIO.create`` for the full list of accepted parameters.
 67        """
 68        writer = cls()
 69        writer.create(filename, sample_rate, channels, **kwargs)
 70        return writer
 71
 72    # To use with context manager "with AudioIO.reader(...) as f:' for instance
 73    def __enter__(self):
 74        """
 75        Method call at initialisation of a context manager like "with AudioIO.reader/writer(...) as f:' for instance
 76        """
 77        # simply return myself
 78        return self
 79
 80    def __exit__(self, exc_type, exc_val, exc_tb):
 81        """
 82        Method call when existing of a context manager like "with AudioIO.reader/writer(...) as f:' for instance
 83        """
 84        # close AudioIO
 85        self.close()
 86        return False
 87
 88    @staticmethod
 89    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 90        """
 91        Static method to get length of an audio file (or video file containing audio) in seconds including milliseconds as decimal part (3 decimals).
 92
 93        Parameters
 94        ----------
 95        filename : str or path. 
 96            Raw audio waveform as a 1D array.
 97
 98        debug : bool (default False).
 99            Show debug info.
100
101        log_level: int (default 16).
102            Log level to pass to the underlying ffmpeg/ffprobe command.
103        
104        Returns
105        ----------
106        float
107            Length in seconds of video file (including milliseconds as decimal part with 3 decimals)
108        """
109        
110        cmd = [AudioIO.paramProgram, # ffprobe
111                    '-hide_banner',
112                    '-loglevel', str(logLevel),
113                    '-show_entries', 'format=duration',
114                    '-of', 'default=noprint_wrappers=1:nokey=1',
115                    filename
116                    ]
117
118        if debug == True:
119            print(' '.join(cmd))
120
121        # call ffprobe and get params in one single line
122        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
123        output = lpipe.stdout.readlines()
124        lpipe.terminate()
125        # transform Bytes output to one single string
126        output = ''.join( [element.decode('utf-8') for element in output])
127
128        try:
129            return float(output)
130        except (ValueError, TypeError):
131            return None
132
133    @staticmethod
134    def get_params(filename, *, debug=False, logLevel=16):
135        """
136        Static method to get params (channels,sample_rate) of a (video containing) audio file in seconds.
137
138        Parameters
139        ----------
140        filename : str or path.
141            Raw audio waveform as a 1D array.
142
143        debug : bool (default (False).
144            Show debug info.
145
146        log_level: int (default 16).
147            Log level to pass to the underlying ffmpeg/ffprobe command.
148
149        Returns
150        ----------
151        tuple
152            Tuple containing (channels,sample_rate) of the file
153        """
154        cmd = [AudioIO.paramProgram, # ffprobe
155                    '-hide_banner',
156                    '-loglevel', str(logLevel),
157                    '-show_entries', 'stream=channels,sample_rate',
158                    filename
159                    ]
160
161        if debug == True:
162            print(' '.join(cmd))
163
164        # call ffprobe and get params in one single line
165        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
166        output = lpipe.stdout.readlines()
167        lpipe.terminate()
168        # transform Bytes output to one single string
169        output = ''.join( [element.decode('utf-8') for element in output])
170
171        pattern_sample_rate = r'sample_rate=(\d+)'
172        pattern_channels = r'channels=(\d+)'
173
174        # Search for values in the ffprobe output
175        match_sample_rate = re.search(pattern_sample_rate, output, flags=re.MULTILINE)
176        match_channels = re.search(pattern_channels, output, flags=re.MULTILINE)
177
178        # Extraction des valeurs
179        if match_sample_rate:
180            sample_rate = int(match_sample_rate.group(1))
181        else:
182            raise AudioIO.AudioIOException("Unable to get audio sample_rate of '" + str(filename) + "'")
183
184        if match_channels:
185            channels = int(match_channels.group(1))
186        else:
187            raise AudioIO.AudioIOException("Unable to get audio channels of '" + str(filename) + "'")
188
189        return (channels,sample_rate)
190
191        # Attributes
192        mode: PipeMode
193        """ Pipemode of the current object (default PipeMode.UNK_MODE)"""
194
195        loglevel: int
196        """ loglevel of the underlying ffmpeg backend for this object (default 16)"""
197
198        debugModel: bool
199        """ debutMode flag for this object (print debut info, default False)"""
200
201        channels: int
202        """ Number of channels of images (default -1) """
203
204        sample_rate: int
205        """ sample_rate of images (default -1) """
206
207        plannar: bool
208        """ Read/write data as plannar, i.e. not interleaved (default True) """
209
210        pipe: sp.Popen
211        """ pipe object to ffmpeg/ffprobe (default None)"""
212
213        frame_size: int
214        """ Weight in bytes of one image (default -1)"""
215
216        filename: str
217        """ Filename of the file (default None)"""
218
219        frame_counter: FrameCounter
220        """ `Framecounter` object to count ellapsed time (default None)"""
221
222    def __init__(self, *, logLevel = 16, debugMode = False):
223        """
224        Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode
225
226        Parameters
227        ----------
228        log_level: int (default 16)
229            Log level to pass to the underlying ffmpeg/ffprobe command.
230
231        debugMode: bool (default (False)
232            Show debug info. while processing video
233        """
234
235        self.mode = PipeMode.UNK_MODE
236        self.logLevel = logLevel
237        self.debugMode = debugMode
238
239        # Call init() method
240        self.init()
241
242    def init(self):
243        """
244        Init or reinit a VideoIO object.
245        """
246        self.channels  = -1
247        self.sample_rate = -1
248        self.plannar = True
249        self.pipe = None
250        self.frame_size = -1
251        self.filename = None
252        self.frame_counter = None
253
254    _repr_exclude = {"pipe"}
255    """ List of excluded attribute for string conversion. """
256
257    # converting the object to a string representation
258    def __repr__(self):
259        """
260        Convert object (excluding attributes in _repr_exclude) to string representation.
261        """
262        attrs = ", ".join(
263            f"{k}={v!r}"
264            for k, v in self.__dict__.items()
265            if k not in self._repr_exclude
266        )
267        return f"{self.__class__.__name__}({attrs})"
268
269    __str__ = __repr__
270    """ String representation """
271
272    def get_elapsed_time_as_str(self) -> str:
273        """
274        Method to get elapsed time (float value represented) as str.
275
276        Returns
277        ----------
278        str or None
279            Elapsed time (float value) as str, "15.500" for instance for 15 secondes and 500 milliseconds
280            None if no frame counter are available.
281        """
282        if self.frame_counter is None:
283            return None
284        return self.frame_counter.get_elapsed_time_as_str()
285
286    def get_formated_elapsed_time_as_str(self,show_ms=True) -> str:
287        """
288        Method to get elapsed time (hour format) as str.
289
290        Returns
291        ----------
292        str or None
293            Elapsed time (float value) as str, "00:00:15.500" for instance for 15 secondes and 500 milliseconds
294            None if no frame counter are available.
295        """
296        if self.frame_counter is None:
297            return None
298        return self.frame_counter.get_formated_elapsed_time_as_str()
299
300    def get_elapsed_time(self) -> float:
301        """
302        Method to get elapsed time as float value rounded to 3 decimals.
303
304        Returns
305        ----------
306        float or None
307            Elapsed time (float value) as str, 15.500 for instance for 15 secondes and 500 milliseconds
308            None if no frame counter are available.
309        """
310        if self.frame_counter is None:
311            return None
312        return self.frame_counter.get_elapsed_time()
313
314    def is_opened(self) -> bool:
315        """
316        Method to get status of the underlying pipe to ffmpeg.
317
318        Returns
319        ----------
320        bool
321            True if pipe is opened (reading or writing mode), False if not.
322        """
323        # is the pip opened?
324        if self.pipe is not None and self.pipe.poll() is None:
325            return True
326
327        return False
328
329    def close(self):
330        """
331        Method to close current pipe to ffmpeg (if any). Ffmpeg/ffprobe  will be terminated. Object can be reused using open or create methods.
332        """
333        if self.pipe is not None:
334            if self.mode == PipeMode.WRITE_MODE:
335                # killing will make ffmpeg not finish properly the job, close the pipe
336                # to let it know that no more data are comming
337                self.pipe.stdin.close()
338            else: # self.mode == PipeMode.READ_MODE
339                # in read mode, no need to be nice, send SIGTERM on Linux,/Kill it on windows
340                self.pipe.kill()
341
342            # wait for subprocess to end
343            self.pipe.wait()
344
345        # reinit object for later use
346        self.init()
347
348    def create( self, filename, sample_rate, channels, *, writeOverExistingFile = False,
349                outputEncoding = AudioFormat.PCM32LE, encodingParams = None, plannar = True ):
350        """
351        Method to create a audio file using parametrized access through ffmpeg. Importante note: calling create
352        on a AudioIO will close any former open video.
353
354        Parameters
355        ----------
356        filename: str or path
357            filename of path to the file (mp4, avi, ...)
358
359        sample_rate: int
360            If defined as a positive value, sample_rates of the output file will be set to this value.
361
362        channels: int
363            If defined as a positive value, number of channels of output file will be set to this value.
364
365        fps:
366            If defined as a positive value, fps of input video will be set to this value.
367
368        outputEncoding: AudioFormat optional (default AudioFormat.PCM32LE)
369            Define audio format for samples. Possible value is AudioFormat.PCM32LE.
370
371        encodingParams: str optional (default None)
372            Parameter to pass to ffmpeg to encode video like audio filters.
373
374        plannar : bool optionnal (default True)
375            Input data to write are grouped by channel if True, interleaved instead.
376
377        Returns
378        ----------
379        bool
380            Was the creation successfull
381        """
382
383        # Close if already opened
384        self.close()
385
386        # Set geometry/fps of the video stream from params
387        self.sample_rate = int(sample_rate)
388        self.channels = int(channels)
389        self.plannar = plannar
390
391        # Check params
392        if self.sample_rate <= 0 or self.channels <= 0:
393            raise self.AudioIOException("Bad parameters: sample_rate={}, channels={}".format(self.sample_rate,self.channels))
394
395        # To write audio, we do not need to know in advance frame size, we will write x values of n bytes
396        self.frame_size = None
397
398        # Video params are set, open the video
399        cmd = [self.audioProgram] # ffmpeg
400
401        if writeOverExistingFile == True:
402            cmd.extend(['-y'])
403
404        cmd.extend(['-hide_banner',
405            '-nostats',
406            '-loglevel', str(self.logLevel),
407            '-f', 'f32le', '-acodec', outputEncoding.value, # input expected coding
408            '-ar', f"{self.sample_rate}",
409            '-ac', f"{self.channels}",
410            '-i', '-'])
411
412        if encodingParams is not None:
413            cmd.extend(encodingParams.split())
414
415        # remove video
416        cmd.extend( ['-vn', filename ] )
417
418        if self.debugMode == True:
419            print( ' '.join(cmd), file=sys.stderr )
420
421        # store filename and set mode
422        self.filename = filename
423        self.mode = PipeMode.WRITE_MODE
424
425        # call ffmpeg in write mode
426        try:
427            self.pipe = sp.Popen(cmd, stdin=sp.PIPE)
428            self.frame_counter = FrameCounter(self.sample_rate)
429        except Exception as e:
430            # if pipe failed, reinit object and raise exception
431            self.init()
432            raise
433
434        return True
435
436    def open( self, filename, *, sample_rate = -1, channels = -1, inputEncoding = AudioFormat.PCM32LE,
437                    decodingParams = None, frame_size = 1.0, plannar = True, start_time = 0.0 ):
438        """
439        Method to read (video file containing) audio using parametrized access through ffmpeg. Importante note: calling open
440        on a AudioIO will close any former open file.
441
442        Parameters
443        ----------
444        filename: str or path
445            filename of path to the file (mp4, avi, ...)
446
447        sample_rate: int optional (default -1)
448            If defined as a positive value, sample rate of the input audio will be converted to this value.
449
450        channels: int optional (default -1)
451            If defined as a positive value, number of channels of the input audio will converted to this value.
452
453        inputEncoding: AudioFormat optional (default AudioFormat.PCM32LE)
454            Define audio format for samples. Possible value is AudioFormat.PCM32LE.
455
456        decodingParams: str optional (default None)
457            Parameter to pass to ffmpeg to decode video like audio filters.
458
459        plannar: bool optionnal (default True)
460            Group audio samples per channel if True. Else, samples are interleaved.
461
462        frame_size: int or float (default 1.0)
463            If frame_size is an int, it is the number of expected samples in each frame, for instance 8000 for 8000 samples.
464            if frame_size is a float, it is considered as a time in seconds for each audio frame, for instance 1.0 for 1 second, 0.010 for 10 ms.
465            Number of samples in this case is computed using frame_size and sample_rate as int(frame_size * sample_rate)
466
467        start_time: float optional (default 0.0)
468            Define the reading start time. If not set, reading at beginning of the file.
469
470        Returns
471        ----------
472        bool
473            Was the opening successfull
474        """
475
476        # Close if already opened
477        self.close()
478
479        # Force conversion of parameters
480        channels = int(channels)
481        sample_rate = float(sample_rate)
482
483        self.plannar = plannar
484
485        # get parameters from file if needed:
486        if sample_rate <= 0 or channels <= 0:
487            self.channels, self.sample_rate = self.getAudioParams(filename)
488
489        # check if parameters ask to overide video parameters
490        if channels > 0:
491            self.channels = channels
492        if sample_rate > 0:
493            self.sample_rate = sample_rate
494
495        # check parameters
496
497        if isinstance(frame_size,float):
498            # time in seconds
499            self.frame_size = int(frame_size*self.sample_rate)
500        elif isinstance(frame_size,int):
501            # number of samples
502            self.frame_size = frame_size
503        else:
504            # to do
505            pass
506
507        # Video params are set, open the video
508        cmd = [self.audioProgram, # ffmpeg
509                    '-hide_banner',
510                    '-nostats',
511                    '-loglevel', str(self.logLevel)]
512
513        if decodingParams is not None:
514            cmd.extend([decodingParams.split()])
515
516        if start_time < 0.0:
517            pass
518        elif start_time > 0.0:
519            cmd.extend(["-ss", f"{start_time}"])            
520
521        cmd.extend( ['-i', filename,
522                     '-f', 'f32le', '-acodec', inputEncoding.value, # input expected coding
523                     '-ar', f"{self.sample_rate}",
524                     '-ac', f"{self.channels}",
525                     '-' # output to stdout
526                    ]
527                )
528
529        if self.debugMode == True:
530            print( ' '.join(cmd) )
531
532        # store filename and set mode to READ_MODE
533        self.filename = filename
534        self.mode = PipeMode.READ_MODE
535
536        # call ffmpeg in read mode
537        try:
538            self.pipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
539            self.frame_counter = FrameCounter(self.sample_rate)
540            if start_time > 0.0:
541                self.frame_counter += start_time # adding with float means adding time
542        except Exception as e:
543            # if pipe failed, reinit object and raise exception
544            self.init()
545            raise
546
547        return True
548
549    def read_frame(self, with_timestamps = False):
550        """
551        Read next frame from the audio file
552
553        Parameters
554        ----------
555        with_timestamps: bool optional (default False)
556            If set to True, the method returns a ``FrameContainer`` with the audio and an array containing the associated timestamp(s)
557
558        Returns
559        ----------
560        nparray or FrameContainer
561            A frame of shape (self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A frame
562            of shape (self.channels*self.frame_size) with interleaved data if self.plannar is False.
563            if with_timestamps is True, the return object is a FrameContainer with the audio data in ``FrameContainer.data`` and
564            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element).
565        """
566
567        if self.pipe is None:
568            raise self.AudioIOException("No pipe opened to {}. Call open(...) before reading a frame.".format(self.audioProgram))
569        # - pipe is in write mode
570        if self.mode != PipeMode.READ_MODE:
571            raise self.AudioIOException("Pipe to {} for '{}' not opened in read mode.".format(self.audioProgram, self.filename))
572
573        if with_timestamps:
574            # get elapsed time in video, it is time of next frame(s)
575            current_elapsed_time = self.get_elapsed_time()
576
577        # read rgb image from pipe
578        toread = self.frame_size*4
579        buffer = self.pipe.stdout.read(toread)
580        if len(buffer) != toread:
581            # not considered as an error, no more frame, no exception
582            return None
583
584        # get numpy UINT8 array from buffer
585        audio = np.frombuffer(buffer, dtype = np.float32).reshape(self.frame_size, self.channels)
586
587        # make it plannar (or not)
588        if self.plannar:
589            #transpose it
590            audio = audio.T
591
592        # increase frame_counter
593        self.frame_counter.frame_count += (self.frame_size * self.channels)
594
595        # say to gc that this buffer is no longer needed
596        del buffer
597
598        if with_timestamps:
599            return FrameContainer(1, audio, self.frame_size/self.sample_rate, current_elapsed_time)
600        
601        return audio
602
603    def read_batch(self, numberOfFrames, with_timestamps = False):
604        """
605        Read next batch of audio from the file
606
607        Parameters
608        ----------
609        number_of_frames: int
610            Number of desired images within the batch. The last batch from the file may have less images.
611            
612        with_timestamps: bool optional (default False)
613            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
614
615        Returns
616        ----------
617        nparray or FrameContainer
618            A batch of shape (n, self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A batch
619            of shape (n, self.channels*self.frame_size) with interleaved data if self.plannar is False.
620            if with_timestamps is True, the return object is a FrameContainer with the audio batch in ``FrameContainer.data`` and
621            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element for each audio frame).
622        """
623
624        if self.pipe is None:
625            raise self.AudioIOException("No pipe opened to {}. Call open(...) before reading frames.".format(self.audioProgram))
626        # - pipe is in write mode
627        if self.mode != PipeMode.READ_MODE:
628            raise self.AudioIOException("Pipe to {} for '{}' not opened in read mode.".format(self.audioProgram, self.filename))
629
630        if with_timestamps:
631            # get elapsed time in video, it is time of next frame(s)
632            current_elapsed_time = self.get_elapsed_time()
633
634        # try to read complete batch
635        toread = self.frame_size*4*self.channels*numberOfFrames
636        buffer = self.pipe.stdout.read(toread)
637
638        # check if we have at least 1 Frame
639        if len(buffer) < toread:
640            # not considered as an error, no more frame, no exception
641            return None
642
643        # compute actual number of Frames
644        actualNbFrames = len(buffer)//(self.frame_size*4*self.channels)
645
646        # get and reshape batch from buffer
647        batch = np.frombuffer(buffer, dtype = np.float32).reshape((actualNbFrames, self.frame_size, self.channels,))
648
649        if self.plannar:
650            batch = batch.transpose(0, 2, 1)
651
652        # increase frame_counter
653        self.frame_counter.frame_count += (actualNbFrames * self.frame_size * self.channels)
654        
655        # say to gc that this buffer is no longer needed
656        del buffer
657
658        if with_timestamps:
659            return FrameContainer( actualNbFrames, batch, self.frame_size/self.sample_rate, current_elapsed_time)
660        
661        return batch
662
663    def write_frame(self, audio) -> bool:
664        """
665        Write an audio frame to the file
666
667        Parameters
668        ----------
669        audio: nparray
670            The audio frame to write to the video file of shape (self.channels,nb_samples_per_channel) if plannar is True else (self.channels*nb_samples_per_channel).
671
672        Returns
673        ----------
674        bool
675            Writing was successful or not.
676        """
677        # Check params
678        # - pipe exists
679        if self.pipe is None:
680            raise self.AudioIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.audioProgram))
681        # - pipe is in write mode
682        if self.mode != PipeMode.WRITE_MODE:
683            raise self.AudioIOException("Pipe to {} for '{}' not opened in write mode.".format(self.audioProgram, self.filename))
684        # - shape of image is fine, thus we have pixels for a full compatible frame
685        if audio.shape[0] != self.channels:
686            raise self.AudioIOException("Wong audio shape: {} expected ({},{}).".format(audio.shape,self.channels,self.frame_size))
687        # - type of data is Float32
688        if audio.dtype != np.float32:
689            raise self.AudioIOException("Wong audio type: {} expected np.float32.".format(audio.dtype))
690
691        # array must have a shape (channels, samples), reshape it it to (samples, channels) if plannar
692        if not self.plannar:
693            audio = audio.reshape(-1)
694
695        # print( audio.shape )
696
697        # garantee to have a C continuous array
698        if not audio.flags['C_CONTIGUOUS']:
699            a = np.ascontiguousarray(a) 
700
701        # write frame
702        buffer = audio.tobytes()
703        if self.pipe.stdin.write( buffer ) < len(buffer):
704            print( f"Error writing frame to {self.filename}" )
705            return False
706
707        # increase frame_counter
708        self.frame_counter.frame_count += (self.frame_size * self.channels)
709
710        # say to gc that this buffer is no longer needed 
711        del buffer
712
713        return True
714
715    def write_batch(self, batch):
716        """
717        Write a batch of audio frame to the file
718
719        Parameters
720        ----------
721        batch: nparray
722            The batch of audio frames to write to the video file of shape (n,self.channels,nb_samples_per_channel) if plannar is True else (n,self.channels*nb_samples_per_channel) of interleaved audio data.
723
724        Returns
725        ----------
726        bool
727            Writing was successful or not.
728        """
729        # Check params
730        # - pipe exists
731        if self.pipe is None:
732            raise self.AudioIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.audioProgram))
733        # - pipe is in write mode
734        if self.mode != PipeMode.WRITE_MODE:
735            raise self.AudioIOException("Pipe to {} for '{}' not opened in write mode.".format(self.audioProgram, self.filename))
736        # batch is 3D (n, channels, nb samples)
737        if batch.ndim !=3:
738            raise self.AudioIOException("Wrong batch shape: {} expected 3 dimensions (n, n_channels, n_samples_per_channel).".format(batch.shape))
739        # - shape of images in batch is fine
740        if batch.shape[2] != self.channels:
741            raise self.AudioIOException("Wrong audio channels in batch: {} expected {} {}.".format(batch.shape[2], self.channels, batch.shape))
742
743        # array must have a shape (n * n_channels * n_samples_per_channel) before writing them to pipe
744        # reshape it it to (n * n_channels * n_samples_per_channel) if plannar is False
745        if not self.plannar:
746            # goes from (n, n_channels, n_samples_per_channel) to (n * n_channels * n_samples_per_channel)
747            batch = batch.transpose(0, 2, 1) # first go to (n, n_samples_per_channel, n_channels)
748            batch = batch.reshape(-1) # then to 1D array (n * n_channels * n_samples_per_channel)
749
750        # garantee to have a C continuous array
751        if not batch.flags['C_CONTIGUOUS']:
752            batch = np.ascontiguousarray(batch)
753
754        # write frame
755        buffer = batch.tobytes()
756        if self.pipe.stdin.write( buffer ) < len(buffer):
757            # say to gc that this buffer is no longer needed
758            del buffer
759            raise self.AudioIOException("Error writing batch to '{}'.".format(self.filename))
760
761        # increase frame_counter
762        self.frame_counter.frame_count += int(batch.shape[0]/self.channels) # int conversion is mandatory to avoid confusion with time as float
763              
764        # say to gc that this buffer is no longer needed
765        del buffer
766
767        return True
768
769    def iter_frames(self, with_timestamps = False):
770        """
771        Method to iterate on audio frames using AudioIO obj.
772        for audio_frame in obj.iter_frames():
773            ....
774
775        Parameters
776        ----------
777        with_timestamps: bool optional (default False)
778            If set to True, the method returns a FrameContainer object with the batch and an array containing the associated timestamps to frames
779
780        Returns
781        ----------
782        nparray or FrameContainer
783            A batch of images of shape ()
784        """
785
786        try:
787            if self.mode == PipeMode.READ_MODE:
788                while self.isOpened():
789                    frame = self.readFrame(with_timestamps)
790                    if frame is not None:
791                        yield frame
792        finally:
793            self.close()
794
795    def iter_batches(self, batch_size : int, with_timestamps = False ):
796        """
797        Method to iterate on batch ofaudio  frames using VideoIO obj.
798        for audio_batch in obj.iter_batches():
799            ....
800
801        Parameters
802        ----------
803        with_timestamps: bool optional (default False)
804            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
805        """
806        try:
807            if self.mode == PipeMode.READ_MODE:
808                while self.isOpened():
809                    batch = self.readBatch(batch_size, with_timestamps)
810                    if batch is not None:
811                        yield batch
812        finally:
813            self.close()
814
815    # function aliases to be compliant with original C++ version
816    getAudioTimeInSec = get_time_in_sec
817    getAudioParams = get_params
818    get_audio_time_in_sec = get_time_in_sec
819    get_audio_params = get_params
820    isOpened = is_opened
821    readFrame = read_frame
822    readBatch = read_batch
823    writeFrame = write_frame
824    writeBatch = write_batch
AudioIO(*, logLevel=16, debugMode=False)
222    def __init__(self, *, logLevel = 16, debugMode = False):
223        """
224        Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode
225
226        Parameters
227        ----------
228        log_level: int (default 16)
229            Log level to pass to the underlying ffmpeg/ffprobe command.
230
231        debugMode: bool (default (False)
232            Show debug info. while processing video
233        """
234
235        self.mode = PipeMode.UNK_MODE
236        self.logLevel = logLevel
237        self.debugMode = debugMode
238
239        # Call init() method
240        self.init()

Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode

Parameters

log_level: int (default 16) Log level to pass to the underlying ffmpeg/ffprobe command.

debugMode: bool (default (False) Show debug info. while processing video

@classmethod
def reader(cls, filename, **kwargs):
50    @classmethod
51    def reader(cls, filename, **kwargs):
52        """
53        Create and open an AudioIO object in reader mode
54
55        See ``AudioIO.open`` for the full list of accepted parameters.
56        """
57        reader = cls()
58        reader.open(filename, **kwargs)
59        return reader

Create and open an AudioIO object in reader mode

See AudioIO.open for the full list of accepted parameters.

@classmethod
def writer(cls, filename, sample_rate, channels, **kwargs):
61    @classmethod
62    def writer(cls, filename, sample_rate, channels, **kwargs):
63        """
64        Create and open an AudioIO object in writer mode
65
66        See ``AudioIO.create`` for the full list of accepted parameters.
67        """
68        writer = cls()
69        writer.create(filename, sample_rate, channels, **kwargs)
70        return writer

Create and open an AudioIO object in writer mode

See AudioIO.create for the full list of accepted parameters.

@staticmethod
def get_time_in_sec(filename, *, debug=False, logLevel=16):
 88    @staticmethod
 89    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 90        """
 91        Static method to get length of an audio file (or video file containing audio) in seconds including milliseconds as decimal part (3 decimals).
 92
 93        Parameters
 94        ----------
 95        filename : str or path. 
 96            Raw audio waveform as a 1D array.
 97
 98        debug : bool (default False).
 99            Show debug info.
100
101        log_level: int (default 16).
102            Log level to pass to the underlying ffmpeg/ffprobe command.
103        
104        Returns
105        ----------
106        float
107            Length in seconds of video file (including milliseconds as decimal part with 3 decimals)
108        """
109        
110        cmd = [AudioIO.paramProgram, # ffprobe
111                    '-hide_banner',
112                    '-loglevel', str(logLevel),
113                    '-show_entries', 'format=duration',
114                    '-of', 'default=noprint_wrappers=1:nokey=1',
115                    filename
116                    ]
117
118        if debug == True:
119            print(' '.join(cmd))
120
121        # call ffprobe and get params in one single line
122        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
123        output = lpipe.stdout.readlines()
124        lpipe.terminate()
125        # transform Bytes output to one single string
126        output = ''.join( [element.decode('utf-8') for element in output])
127
128        try:
129            return float(output)
130        except (ValueError, TypeError):
131            return None

Static method to get length of an audio file (or video file containing audio) in seconds including milliseconds as decimal part (3 decimals).

Parameters

filename : str or path. Raw audio waveform as a 1D array.

debug : bool (default False). Show debug info.

log_level: int (default 16). Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

float Length in seconds of video file (including milliseconds as decimal part with 3 decimals)

@staticmethod
def get_params(filename, *, debug=False, logLevel=16):
133    @staticmethod
134    def get_params(filename, *, debug=False, logLevel=16):
135        """
136        Static method to get params (channels,sample_rate) of a (video containing) audio file in seconds.
137
138        Parameters
139        ----------
140        filename : str or path.
141            Raw audio waveform as a 1D array.
142
143        debug : bool (default (False).
144            Show debug info.
145
146        log_level: int (default 16).
147            Log level to pass to the underlying ffmpeg/ffprobe command.
148
149        Returns
150        ----------
151        tuple
152            Tuple containing (channels,sample_rate) of the file
153        """
154        cmd = [AudioIO.paramProgram, # ffprobe
155                    '-hide_banner',
156                    '-loglevel', str(logLevel),
157                    '-show_entries', 'stream=channels,sample_rate',
158                    filename
159                    ]
160
161        if debug == True:
162            print(' '.join(cmd))
163
164        # call ffprobe and get params in one single line
165        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
166        output = lpipe.stdout.readlines()
167        lpipe.terminate()
168        # transform Bytes output to one single string
169        output = ''.join( [element.decode('utf-8') for element in output])
170
171        pattern_sample_rate = r'sample_rate=(\d+)'
172        pattern_channels = r'channels=(\d+)'
173
174        # Search for values in the ffprobe output
175        match_sample_rate = re.search(pattern_sample_rate, output, flags=re.MULTILINE)
176        match_channels = re.search(pattern_channels, output, flags=re.MULTILINE)
177
178        # Extraction des valeurs
179        if match_sample_rate:
180            sample_rate = int(match_sample_rate.group(1))
181        else:
182            raise AudioIO.AudioIOException("Unable to get audio sample_rate of '" + str(filename) + "'")
183
184        if match_channels:
185            channels = int(match_channels.group(1))
186        else:
187            raise AudioIO.AudioIOException("Unable to get audio channels of '" + str(filename) + "'")
188
189        return (channels,sample_rate)
190
191        # Attributes
192        mode: PipeMode
193        """ Pipemode of the current object (default PipeMode.UNK_MODE)"""
194
195        loglevel: int
196        """ loglevel of the underlying ffmpeg backend for this object (default 16)"""
197
198        debugModel: bool
199        """ debutMode flag for this object (print debut info, default False)"""
200
201        channels: int
202        """ Number of channels of images (default -1) """
203
204        sample_rate: int
205        """ sample_rate of images (default -1) """
206
207        plannar: bool
208        """ Read/write data as plannar, i.e. not interleaved (default True) """
209
210        pipe: sp.Popen
211        """ pipe object to ffmpeg/ffprobe (default None)"""
212
213        frame_size: int
214        """ Weight in bytes of one image (default -1)"""
215
216        filename: str
217        """ Filename of the file (default None)"""
218
219        frame_counter: FrameCounter
220        """ `Framecounter` object to count ellapsed time (default None)"""

Static method to get params (channels,sample_rate) of a (video containing) audio file in seconds.

Parameters

filename : str or path. Raw audio waveform as a 1D array.

debug : bool (default (False). Show debug info.

log_level: int (default 16). Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

tuple Tuple containing (channels,sample_rate) of the file

mode
logLevel
debugMode
def init(self):
242    def init(self):
243        """
244        Init or reinit a VideoIO object.
245        """
246        self.channels  = -1
247        self.sample_rate = -1
248        self.plannar = True
249        self.pipe = None
250        self.frame_size = -1
251        self.filename = None
252        self.frame_counter = None

Init or reinit a VideoIO object.

def get_elapsed_time_as_str(self) -> str:
272    def get_elapsed_time_as_str(self) -> str:
273        """
274        Method to get elapsed time (float value represented) as str.
275
276        Returns
277        ----------
278        str or None
279            Elapsed time (float value) as str, "15.500" for instance for 15 secondes and 500 milliseconds
280            None if no frame counter are available.
281        """
282        if self.frame_counter is None:
283            return None
284        return self.frame_counter.get_elapsed_time_as_str()

Method to get elapsed time (float value represented) as str.

Returns

str or None Elapsed time (float value) as str, "15.500" for instance for 15 secondes and 500 milliseconds None if no frame counter are available.

def get_formated_elapsed_time_as_str(self, show_ms=True) -> str:
286    def get_formated_elapsed_time_as_str(self,show_ms=True) -> str:
287        """
288        Method to get elapsed time (hour format) as str.
289
290        Returns
291        ----------
292        str or None
293            Elapsed time (float value) as str, "00:00:15.500" for instance for 15 secondes and 500 milliseconds
294            None if no frame counter are available.
295        """
296        if self.frame_counter is None:
297            return None
298        return self.frame_counter.get_formated_elapsed_time_as_str()

Method to get elapsed time (hour format) as str.

Returns

str or None Elapsed time (float value) as str, "00:00:15.500" for instance for 15 secondes and 500 milliseconds None if no frame counter are available.

def get_elapsed_time(self) -> float:
300    def get_elapsed_time(self) -> float:
301        """
302        Method to get elapsed time as float value rounded to 3 decimals.
303
304        Returns
305        ----------
306        float or None
307            Elapsed time (float value) as str, 15.500 for instance for 15 secondes and 500 milliseconds
308            None if no frame counter are available.
309        """
310        if self.frame_counter is None:
311            return None
312        return self.frame_counter.get_elapsed_time()

Method to get elapsed time as float value rounded to 3 decimals.

Returns

float or None Elapsed time (float value) as str, 15.500 for instance for 15 secondes and 500 milliseconds None if no frame counter are available.

def is_opened(self) -> bool:
314    def is_opened(self) -> bool:
315        """
316        Method to get status of the underlying pipe to ffmpeg.
317
318        Returns
319        ----------
320        bool
321            True if pipe is opened (reading or writing mode), False if not.
322        """
323        # is the pip opened?
324        if self.pipe is not None and self.pipe.poll() is None:
325            return True
326
327        return False

Method to get status of the underlying pipe to ffmpeg.

Returns

bool True if pipe is opened (reading or writing mode), False if not.

def close(self):
329    def close(self):
330        """
331        Method to close current pipe to ffmpeg (if any). Ffmpeg/ffprobe  will be terminated. Object can be reused using open or create methods.
332        """
333        if self.pipe is not None:
334            if self.mode == PipeMode.WRITE_MODE:
335                # killing will make ffmpeg not finish properly the job, close the pipe
336                # to let it know that no more data are comming
337                self.pipe.stdin.close()
338            else: # self.mode == PipeMode.READ_MODE
339                # in read mode, no need to be nice, send SIGTERM on Linux,/Kill it on windows
340                self.pipe.kill()
341
342            # wait for subprocess to end
343            self.pipe.wait()
344
345        # reinit object for later use
346        self.init()

Method to close current pipe to ffmpeg (if any). Ffmpeg/ffprobe will be terminated. Object can be reused using open or create methods.

def create( self, filename, sample_rate, channels, *, writeOverExistingFile=False, outputEncoding=<AudioFormat.PCM32LE: 'pcm_f32le'>, encodingParams=None, plannar=True):
348    def create( self, filename, sample_rate, channels, *, writeOverExistingFile = False,
349                outputEncoding = AudioFormat.PCM32LE, encodingParams = None, plannar = True ):
350        """
351        Method to create a audio file using parametrized access through ffmpeg. Importante note: calling create
352        on a AudioIO will close any former open video.
353
354        Parameters
355        ----------
356        filename: str or path
357            filename of path to the file (mp4, avi, ...)
358
359        sample_rate: int
360            If defined as a positive value, sample_rates of the output file will be set to this value.
361
362        channels: int
363            If defined as a positive value, number of channels of output file will be set to this value.
364
365        fps:
366            If defined as a positive value, fps of input video will be set to this value.
367
368        outputEncoding: AudioFormat optional (default AudioFormat.PCM32LE)
369            Define audio format for samples. Possible value is AudioFormat.PCM32LE.
370
371        encodingParams: str optional (default None)
372            Parameter to pass to ffmpeg to encode video like audio filters.
373
374        plannar : bool optionnal (default True)
375            Input data to write are grouped by channel if True, interleaved instead.
376
377        Returns
378        ----------
379        bool
380            Was the creation successfull
381        """
382
383        # Close if already opened
384        self.close()
385
386        # Set geometry/fps of the video stream from params
387        self.sample_rate = int(sample_rate)
388        self.channels = int(channels)
389        self.plannar = plannar
390
391        # Check params
392        if self.sample_rate <= 0 or self.channels <= 0:
393            raise self.AudioIOException("Bad parameters: sample_rate={}, channels={}".format(self.sample_rate,self.channels))
394
395        # To write audio, we do not need to know in advance frame size, we will write x values of n bytes
396        self.frame_size = None
397
398        # Video params are set, open the video
399        cmd = [self.audioProgram] # ffmpeg
400
401        if writeOverExistingFile == True:
402            cmd.extend(['-y'])
403
404        cmd.extend(['-hide_banner',
405            '-nostats',
406            '-loglevel', str(self.logLevel),
407            '-f', 'f32le', '-acodec', outputEncoding.value, # input expected coding
408            '-ar', f"{self.sample_rate}",
409            '-ac', f"{self.channels}",
410            '-i', '-'])
411
412        if encodingParams is not None:
413            cmd.extend(encodingParams.split())
414
415        # remove video
416        cmd.extend( ['-vn', filename ] )
417
418        if self.debugMode == True:
419            print( ' '.join(cmd), file=sys.stderr )
420
421        # store filename and set mode
422        self.filename = filename
423        self.mode = PipeMode.WRITE_MODE
424
425        # call ffmpeg in write mode
426        try:
427            self.pipe = sp.Popen(cmd, stdin=sp.PIPE)
428            self.frame_counter = FrameCounter(self.sample_rate)
429        except Exception as e:
430            # if pipe failed, reinit object and raise exception
431            self.init()
432            raise
433
434        return True

Method to create a audio file using parametrized access through ffmpeg. Importante note: calling create on a AudioIO will close any former open video.

Parameters

filename: str or path filename of path to the file (mp4, avi, ...)

sample_rate: int If defined as a positive value, sample_rates of the output file will be set to this value.

channels: int If defined as a positive value, number of channels of output file will be set to this value.

fps: If defined as a positive value, fps of input video will be set to this value.

outputEncoding: AudioFormat optional (default AudioFormat.PCM32LE) Define audio format for samples. Possible value is AudioFormat.PCM32LE.

encodingParams: str optional (default None) Parameter to pass to ffmpeg to encode video like audio filters.

plannar : bool optionnal (default True) Input data to write are grouped by channel if True, interleaved instead.

Returns

bool Was the creation successfull

def open( self, filename, *, sample_rate=-1, channels=-1, inputEncoding=<AudioFormat.PCM32LE: 'pcm_f32le'>, decodingParams=None, frame_size=1.0, plannar=True, start_time=0.0):
436    def open( self, filename, *, sample_rate = -1, channels = -1, inputEncoding = AudioFormat.PCM32LE,
437                    decodingParams = None, frame_size = 1.0, plannar = True, start_time = 0.0 ):
438        """
439        Method to read (video file containing) audio using parametrized access through ffmpeg. Importante note: calling open
440        on a AudioIO will close any former open file.
441
442        Parameters
443        ----------
444        filename: str or path
445            filename of path to the file (mp4, avi, ...)
446
447        sample_rate: int optional (default -1)
448            If defined as a positive value, sample rate of the input audio will be converted to this value.
449
450        channels: int optional (default -1)
451            If defined as a positive value, number of channels of the input audio will converted to this value.
452
453        inputEncoding: AudioFormat optional (default AudioFormat.PCM32LE)
454            Define audio format for samples. Possible value is AudioFormat.PCM32LE.
455
456        decodingParams: str optional (default None)
457            Parameter to pass to ffmpeg to decode video like audio filters.
458
459        plannar: bool optionnal (default True)
460            Group audio samples per channel if True. Else, samples are interleaved.
461
462        frame_size: int or float (default 1.0)
463            If frame_size is an int, it is the number of expected samples in each frame, for instance 8000 for 8000 samples.
464            if frame_size is a float, it is considered as a time in seconds for each audio frame, for instance 1.0 for 1 second, 0.010 for 10 ms.
465            Number of samples in this case is computed using frame_size and sample_rate as int(frame_size * sample_rate)
466
467        start_time: float optional (default 0.0)
468            Define the reading start time. If not set, reading at beginning of the file.
469
470        Returns
471        ----------
472        bool
473            Was the opening successfull
474        """
475
476        # Close if already opened
477        self.close()
478
479        # Force conversion of parameters
480        channels = int(channels)
481        sample_rate = float(sample_rate)
482
483        self.plannar = plannar
484
485        # get parameters from file if needed:
486        if sample_rate <= 0 or channels <= 0:
487            self.channels, self.sample_rate = self.getAudioParams(filename)
488
489        # check if parameters ask to overide video parameters
490        if channels > 0:
491            self.channels = channels
492        if sample_rate > 0:
493            self.sample_rate = sample_rate
494
495        # check parameters
496
497        if isinstance(frame_size,float):
498            # time in seconds
499            self.frame_size = int(frame_size*self.sample_rate)
500        elif isinstance(frame_size,int):
501            # number of samples
502            self.frame_size = frame_size
503        else:
504            # to do
505            pass
506
507        # Video params are set, open the video
508        cmd = [self.audioProgram, # ffmpeg
509                    '-hide_banner',
510                    '-nostats',
511                    '-loglevel', str(self.logLevel)]
512
513        if decodingParams is not None:
514            cmd.extend([decodingParams.split()])
515
516        if start_time < 0.0:
517            pass
518        elif start_time > 0.0:
519            cmd.extend(["-ss", f"{start_time}"])            
520
521        cmd.extend( ['-i', filename,
522                     '-f', 'f32le', '-acodec', inputEncoding.value, # input expected coding
523                     '-ar', f"{self.sample_rate}",
524                     '-ac', f"{self.channels}",
525                     '-' # output to stdout
526                    ]
527                )
528
529        if self.debugMode == True:
530            print( ' '.join(cmd) )
531
532        # store filename and set mode to READ_MODE
533        self.filename = filename
534        self.mode = PipeMode.READ_MODE
535
536        # call ffmpeg in read mode
537        try:
538            self.pipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg/ffprobe
539            self.frame_counter = FrameCounter(self.sample_rate)
540            if start_time > 0.0:
541                self.frame_counter += start_time # adding with float means adding time
542        except Exception as e:
543            # if pipe failed, reinit object and raise exception
544            self.init()
545            raise
546
547        return True

Method to read (video file containing) audio using parametrized access through ffmpeg. Importante note: calling open on a AudioIO will close any former open file.

Parameters

filename: str or path filename of path to the file (mp4, avi, ...)

sample_rate: int optional (default -1) If defined as a positive value, sample rate of the input audio will be converted to this value.

channels: int optional (default -1) If defined as a positive value, number of channels of the input audio will converted to this value.

inputEncoding: AudioFormat optional (default AudioFormat.PCM32LE) Define audio format for samples. Possible value is AudioFormat.PCM32LE.

decodingParams: str optional (default None) Parameter to pass to ffmpeg to decode video like audio filters.

plannar: bool optionnal (default True) Group audio samples per channel if True. Else, samples are interleaved.

frame_size: int or float (default 1.0) If frame_size is an int, it is the number of expected samples in each frame, for instance 8000 for 8000 samples. if frame_size is a float, it is considered as a time in seconds for each audio frame, for instance 1.0 for 1 second, 0.010 for 10 ms. Number of samples in this case is computed using frame_size and sample_rate as int(frame_size * sample_rate)

start_time: float optional (default 0.0) Define the reading start time. If not set, reading at beginning of the file.

Returns

bool Was the opening successfull

def read_frame(self, with_timestamps=False):
549    def read_frame(self, with_timestamps = False):
550        """
551        Read next frame from the audio file
552
553        Parameters
554        ----------
555        with_timestamps: bool optional (default False)
556            If set to True, the method returns a ``FrameContainer`` with the audio and an array containing the associated timestamp(s)
557
558        Returns
559        ----------
560        nparray or FrameContainer
561            A frame of shape (self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A frame
562            of shape (self.channels*self.frame_size) with interleaved data if self.plannar is False.
563            if with_timestamps is True, the return object is a FrameContainer with the audio data in ``FrameContainer.data`` and
564            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element).
565        """
566
567        if self.pipe is None:
568            raise self.AudioIOException("No pipe opened to {}. Call open(...) before reading a frame.".format(self.audioProgram))
569        # - pipe is in write mode
570        if self.mode != PipeMode.READ_MODE:
571            raise self.AudioIOException("Pipe to {} for '{}' not opened in read mode.".format(self.audioProgram, self.filename))
572
573        if with_timestamps:
574            # get elapsed time in video, it is time of next frame(s)
575            current_elapsed_time = self.get_elapsed_time()
576
577        # read rgb image from pipe
578        toread = self.frame_size*4
579        buffer = self.pipe.stdout.read(toread)
580        if len(buffer) != toread:
581            # not considered as an error, no more frame, no exception
582            return None
583
584        # get numpy UINT8 array from buffer
585        audio = np.frombuffer(buffer, dtype = np.float32).reshape(self.frame_size, self.channels)
586
587        # make it plannar (or not)
588        if self.plannar:
589            #transpose it
590            audio = audio.T
591
592        # increase frame_counter
593        self.frame_counter.frame_count += (self.frame_size * self.channels)
594
595        # say to gc that this buffer is no longer needed
596        del buffer
597
598        if with_timestamps:
599            return FrameContainer(1, audio, self.frame_size/self.sample_rate, current_elapsed_time)
600        
601        return audio

Read next frame from the audio file

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the audio and an array containing the associated timestamp(s)

Returns

nparray or FrameContainer A frame of shape (self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A frame of shape (self.channels*self.frame_size) with interleaved data if self.plannar is False. if with_timestamps is True, the return object is a FrameContainer with the audio data in FrameContainer.data and the associated timestamp in FrameContainer.timestamps as an array (one element).

def read_batch(self, numberOfFrames, with_timestamps=False):
603    def read_batch(self, numberOfFrames, with_timestamps = False):
604        """
605        Read next batch of audio from the file
606
607        Parameters
608        ----------
609        number_of_frames: int
610            Number of desired images within the batch. The last batch from the file may have less images.
611            
612        with_timestamps: bool optional (default False)
613            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
614
615        Returns
616        ----------
617        nparray or FrameContainer
618            A batch of shape (n, self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A batch
619            of shape (n, self.channels*self.frame_size) with interleaved data if self.plannar is False.
620            if with_timestamps is True, the return object is a FrameContainer with the audio batch in ``FrameContainer.data`` and
621            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element for each audio frame).
622        """
623
624        if self.pipe is None:
625            raise self.AudioIOException("No pipe opened to {}. Call open(...) before reading frames.".format(self.audioProgram))
626        # - pipe is in write mode
627        if self.mode != PipeMode.READ_MODE:
628            raise self.AudioIOException("Pipe to {} for '{}' not opened in read mode.".format(self.audioProgram, self.filename))
629
630        if with_timestamps:
631            # get elapsed time in video, it is time of next frame(s)
632            current_elapsed_time = self.get_elapsed_time()
633
634        # try to read complete batch
635        toread = self.frame_size*4*self.channels*numberOfFrames
636        buffer = self.pipe.stdout.read(toread)
637
638        # check if we have at least 1 Frame
639        if len(buffer) < toread:
640            # not considered as an error, no more frame, no exception
641            return None
642
643        # compute actual number of Frames
644        actualNbFrames = len(buffer)//(self.frame_size*4*self.channels)
645
646        # get and reshape batch from buffer
647        batch = np.frombuffer(buffer, dtype = np.float32).reshape((actualNbFrames, self.frame_size, self.channels,))
648
649        if self.plannar:
650            batch = batch.transpose(0, 2, 1)
651
652        # increase frame_counter
653        self.frame_counter.frame_count += (actualNbFrames * self.frame_size * self.channels)
654        
655        # say to gc that this buffer is no longer needed
656        del buffer
657
658        if with_timestamps:
659            return FrameContainer( actualNbFrames, batch, self.frame_size/self.sample_rate, current_elapsed_time)
660        
661        return batch

Read next batch of audio from the file

Parameters

number_of_frames: int Number of desired images within the batch. The last batch from the file may have less images.

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames

Returns

nparray or FrameContainer A batch of shape (n, self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A batch of shape (n, self.channels*self.frame_size) with interleaved data if self.plannar is False. if with_timestamps is True, the return object is a FrameContainer with the audio batch in FrameContainer.data and the associated timestamp in FrameContainer.timestamps as an array (one element for each audio frame).

def write_frame(self, audio) -> bool:
663    def write_frame(self, audio) -> bool:
664        """
665        Write an audio frame to the file
666
667        Parameters
668        ----------
669        audio: nparray
670            The audio frame to write to the video file of shape (self.channels,nb_samples_per_channel) if plannar is True else (self.channels*nb_samples_per_channel).
671
672        Returns
673        ----------
674        bool
675            Writing was successful or not.
676        """
677        # Check params
678        # - pipe exists
679        if self.pipe is None:
680            raise self.AudioIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.audioProgram))
681        # - pipe is in write mode
682        if self.mode != PipeMode.WRITE_MODE:
683            raise self.AudioIOException("Pipe to {} for '{}' not opened in write mode.".format(self.audioProgram, self.filename))
684        # - shape of image is fine, thus we have pixels for a full compatible frame
685        if audio.shape[0] != self.channels:
686            raise self.AudioIOException("Wong audio shape: {} expected ({},{}).".format(audio.shape,self.channels,self.frame_size))
687        # - type of data is Float32
688        if audio.dtype != np.float32:
689            raise self.AudioIOException("Wong audio type: {} expected np.float32.".format(audio.dtype))
690
691        # array must have a shape (channels, samples), reshape it it to (samples, channels) if plannar
692        if not self.plannar:
693            audio = audio.reshape(-1)
694
695        # print( audio.shape )
696
697        # garantee to have a C continuous array
698        if not audio.flags['C_CONTIGUOUS']:
699            a = np.ascontiguousarray(a) 
700
701        # write frame
702        buffer = audio.tobytes()
703        if self.pipe.stdin.write( buffer ) < len(buffer):
704            print( f"Error writing frame to {self.filename}" )
705            return False
706
707        # increase frame_counter
708        self.frame_counter.frame_count += (self.frame_size * self.channels)
709
710        # say to gc that this buffer is no longer needed 
711        del buffer
712
713        return True

Write an audio frame to the file

Parameters

audio: nparray The audio frame to write to the video file of shape (self.channels,nb_samples_per_channel) if plannar is True else (self.channels*nb_samples_per_channel).

Returns

bool Writing was successful or not.

def write_batch(self, batch):
715    def write_batch(self, batch):
716        """
717        Write a batch of audio frame to the file
718
719        Parameters
720        ----------
721        batch: nparray
722            The batch of audio frames to write to the video file of shape (n,self.channels,nb_samples_per_channel) if plannar is True else (n,self.channels*nb_samples_per_channel) of interleaved audio data.
723
724        Returns
725        ----------
726        bool
727            Writing was successful or not.
728        """
729        # Check params
730        # - pipe exists
731        if self.pipe is None:
732            raise self.AudioIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.audioProgram))
733        # - pipe is in write mode
734        if self.mode != PipeMode.WRITE_MODE:
735            raise self.AudioIOException("Pipe to {} for '{}' not opened in write mode.".format(self.audioProgram, self.filename))
736        # batch is 3D (n, channels, nb samples)
737        if batch.ndim !=3:
738            raise self.AudioIOException("Wrong batch shape: {} expected 3 dimensions (n, n_channels, n_samples_per_channel).".format(batch.shape))
739        # - shape of images in batch is fine
740        if batch.shape[2] != self.channels:
741            raise self.AudioIOException("Wrong audio channels in batch: {} expected {} {}.".format(batch.shape[2], self.channels, batch.shape))
742
743        # array must have a shape (n * n_channels * n_samples_per_channel) before writing them to pipe
744        # reshape it it to (n * n_channels * n_samples_per_channel) if plannar is False
745        if not self.plannar:
746            # goes from (n, n_channels, n_samples_per_channel) to (n * n_channels * n_samples_per_channel)
747            batch = batch.transpose(0, 2, 1) # first go to (n, n_samples_per_channel, n_channels)
748            batch = batch.reshape(-1) # then to 1D array (n * n_channels * n_samples_per_channel)
749
750        # garantee to have a C continuous array
751        if not batch.flags['C_CONTIGUOUS']:
752            batch = np.ascontiguousarray(batch)
753
754        # write frame
755        buffer = batch.tobytes()
756        if self.pipe.stdin.write( buffer ) < len(buffer):
757            # say to gc that this buffer is no longer needed
758            del buffer
759            raise self.AudioIOException("Error writing batch to '{}'.".format(self.filename))
760
761        # increase frame_counter
762        self.frame_counter.frame_count += int(batch.shape[0]/self.channels) # int conversion is mandatory to avoid confusion with time as float
763              
764        # say to gc that this buffer is no longer needed
765        del buffer
766
767        return True

Write a batch of audio frame to the file

Parameters

batch: nparray The batch of audio frames to write to the video file of shape (n,self.channels,nb_samples_per_channel) if plannar is True else (n,self.channels*nb_samples_per_channel) of interleaved audio data.

Returns

bool Writing was successful or not.

def iter_frames(self, with_timestamps=False):
769    def iter_frames(self, with_timestamps = False):
770        """
771        Method to iterate on audio frames using AudioIO obj.
772        for audio_frame in obj.iter_frames():
773            ....
774
775        Parameters
776        ----------
777        with_timestamps: bool optional (default False)
778            If set to True, the method returns a FrameContainer object with the batch and an array containing the associated timestamps to frames
779
780        Returns
781        ----------
782        nparray or FrameContainer
783            A batch of images of shape ()
784        """
785
786        try:
787            if self.mode == PipeMode.READ_MODE:
788                while self.isOpened():
789                    frame = self.readFrame(with_timestamps)
790                    if frame is not None:
791                        yield frame
792        finally:
793            self.close()

Method to iterate on audio frames using AudioIO obj. for audio_frame in obj.iter_frames(): ....

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer object with the batch and an array containing the associated timestamps to frames

Returns

nparray or FrameContainer A batch of images of shape ()

def iter_batches(self, batch_size: int, with_timestamps=False):
795    def iter_batches(self, batch_size : int, with_timestamps = False ):
796        """
797        Method to iterate on batch ofaudio  frames using VideoIO obj.
798        for audio_batch in obj.iter_batches():
799            ....
800
801        Parameters
802        ----------
803        with_timestamps: bool optional (default False)
804            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
805        """
806        try:
807            if self.mode == PipeMode.READ_MODE:
808                while self.isOpened():
809                    batch = self.readBatch(batch_size, with_timestamps)
810                    if batch is not None:
811                        yield batch
812        finally:
813            self.close()

Method to iterate on batch ofaudio frames using VideoIO obj. for audio_batch in obj.iter_batches(): ....

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames

@staticmethod
def getAudioTimeInSec(filename, *, debug=False, logLevel=16):
 88    @staticmethod
 89    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 90        """
 91        Static method to get length of an audio file (or video file containing audio) in seconds including milliseconds as decimal part (3 decimals).
 92
 93        Parameters
 94        ----------
 95        filename : str or path. 
 96            Raw audio waveform as a 1D array.
 97
 98        debug : bool (default False).
 99            Show debug info.
100
101        log_level: int (default 16).
102            Log level to pass to the underlying ffmpeg/ffprobe command.
103        
104        Returns
105        ----------
106        float
107            Length in seconds of video file (including milliseconds as decimal part with 3 decimals)
108        """
109        
110        cmd = [AudioIO.paramProgram, # ffprobe
111                    '-hide_banner',
112                    '-loglevel', str(logLevel),
113                    '-show_entries', 'format=duration',
114                    '-of', 'default=noprint_wrappers=1:nokey=1',
115                    filename
116                    ]
117
118        if debug == True:
119            print(' '.join(cmd))
120
121        # call ffprobe and get params in one single line
122        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
123        output = lpipe.stdout.readlines()
124        lpipe.terminate()
125        # transform Bytes output to one single string
126        output = ''.join( [element.decode('utf-8') for element in output])
127
128        try:
129            return float(output)
130        except (ValueError, TypeError):
131            return None

Static method to get length of an audio file (or video file containing audio) in seconds including milliseconds as decimal part (3 decimals).

Parameters

filename : str or path. Raw audio waveform as a 1D array.

debug : bool (default False). Show debug info.

log_level: int (default 16). Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

float Length in seconds of video file (including milliseconds as decimal part with 3 decimals)

@staticmethod
def getAudioParams(filename, *, debug=False, logLevel=16):
133    @staticmethod
134    def get_params(filename, *, debug=False, logLevel=16):
135        """
136        Static method to get params (channels,sample_rate) of a (video containing) audio file in seconds.
137
138        Parameters
139        ----------
140        filename : str or path.
141            Raw audio waveform as a 1D array.
142
143        debug : bool (default (False).
144            Show debug info.
145
146        log_level: int (default 16).
147            Log level to pass to the underlying ffmpeg/ffprobe command.
148
149        Returns
150        ----------
151        tuple
152            Tuple containing (channels,sample_rate) of the file
153        """
154        cmd = [AudioIO.paramProgram, # ffprobe
155                    '-hide_banner',
156                    '-loglevel', str(logLevel),
157                    '-show_entries', 'stream=channels,sample_rate',
158                    filename
159                    ]
160
161        if debug == True:
162            print(' '.join(cmd))
163
164        # call ffprobe and get params in one single line
165        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
166        output = lpipe.stdout.readlines()
167        lpipe.terminate()
168        # transform Bytes output to one single string
169        output = ''.join( [element.decode('utf-8') for element in output])
170
171        pattern_sample_rate = r'sample_rate=(\d+)'
172        pattern_channels = r'channels=(\d+)'
173
174        # Search for values in the ffprobe output
175        match_sample_rate = re.search(pattern_sample_rate, output, flags=re.MULTILINE)
176        match_channels = re.search(pattern_channels, output, flags=re.MULTILINE)
177
178        # Extraction des valeurs
179        if match_sample_rate:
180            sample_rate = int(match_sample_rate.group(1))
181        else:
182            raise AudioIO.AudioIOException("Unable to get audio sample_rate of '" + str(filename) + "'")
183
184        if match_channels:
185            channels = int(match_channels.group(1))
186        else:
187            raise AudioIO.AudioIOException("Unable to get audio channels of '" + str(filename) + "'")
188
189        return (channels,sample_rate)
190
191        # Attributes
192        mode: PipeMode
193        """ Pipemode of the current object (default PipeMode.UNK_MODE)"""
194
195        loglevel: int
196        """ loglevel of the underlying ffmpeg backend for this object (default 16)"""
197
198        debugModel: bool
199        """ debutMode flag for this object (print debut info, default False)"""
200
201        channels: int
202        """ Number of channels of images (default -1) """
203
204        sample_rate: int
205        """ sample_rate of images (default -1) """
206
207        plannar: bool
208        """ Read/write data as plannar, i.e. not interleaved (default True) """
209
210        pipe: sp.Popen
211        """ pipe object to ffmpeg/ffprobe (default None)"""
212
213        frame_size: int
214        """ Weight in bytes of one image (default -1)"""
215
216        filename: str
217        """ Filename of the file (default None)"""
218
219        frame_counter: FrameCounter
220        """ `Framecounter` object to count ellapsed time (default None)"""

Static method to get params (channels,sample_rate) of a (video containing) audio file in seconds.

Parameters

filename : str or path. Raw audio waveform as a 1D array.

debug : bool (default (False). Show debug info.

log_level: int (default 16). Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

tuple Tuple containing (channels,sample_rate) of the file

@staticmethod
def get_audio_time_in_sec(filename, *, debug=False, logLevel=16):
 88    @staticmethod
 89    def get_time_in_sec(filename, *, debug=False, logLevel=16):
 90        """
 91        Static method to get length of an audio file (or video file containing audio) in seconds including milliseconds as decimal part (3 decimals).
 92
 93        Parameters
 94        ----------
 95        filename : str or path. 
 96            Raw audio waveform as a 1D array.
 97
 98        debug : bool (default False).
 99            Show debug info.
100
101        log_level: int (default 16).
102            Log level to pass to the underlying ffmpeg/ffprobe command.
103        
104        Returns
105        ----------
106        float
107            Length in seconds of video file (including milliseconds as decimal part with 3 decimals)
108        """
109        
110        cmd = [AudioIO.paramProgram, # ffprobe
111                    '-hide_banner',
112                    '-loglevel', str(logLevel),
113                    '-show_entries', 'format=duration',
114                    '-of', 'default=noprint_wrappers=1:nokey=1',
115                    filename
116                    ]
117
118        if debug == True:
119            print(' '.join(cmd))
120
121        # call ffprobe and get params in one single line
122        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
123        output = lpipe.stdout.readlines()
124        lpipe.terminate()
125        # transform Bytes output to one single string
126        output = ''.join( [element.decode('utf-8') for element in output])
127
128        try:
129            return float(output)
130        except (ValueError, TypeError):
131            return None

Static method to get length of an audio file (or video file containing audio) in seconds including milliseconds as decimal part (3 decimals).

Parameters

filename : str or path. Raw audio waveform as a 1D array.

debug : bool (default False). Show debug info.

log_level: int (default 16). Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

float Length in seconds of video file (including milliseconds as decimal part with 3 decimals)

@staticmethod
def get_audio_params(filename, *, debug=False, logLevel=16):
133    @staticmethod
134    def get_params(filename, *, debug=False, logLevel=16):
135        """
136        Static method to get params (channels,sample_rate) of a (video containing) audio file in seconds.
137
138        Parameters
139        ----------
140        filename : str or path.
141            Raw audio waveform as a 1D array.
142
143        debug : bool (default (False).
144            Show debug info.
145
146        log_level: int (default 16).
147            Log level to pass to the underlying ffmpeg/ffprobe command.
148
149        Returns
150        ----------
151        tuple
152            Tuple containing (channels,sample_rate) of the file
153        """
154        cmd = [AudioIO.paramProgram, # ffprobe
155                    '-hide_banner',
156                    '-loglevel', str(logLevel),
157                    '-show_entries', 'stream=channels,sample_rate',
158                    filename
159                    ]
160
161        if debug == True:
162            print(' '.join(cmd))
163
164        # call ffprobe and get params in one single line
165        lpipe = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE) # stdin=sp.PIPE to prevent manipulation of shell echo mode by ffmpeg
166        output = lpipe.stdout.readlines()
167        lpipe.terminate()
168        # transform Bytes output to one single string
169        output = ''.join( [element.decode('utf-8') for element in output])
170
171        pattern_sample_rate = r'sample_rate=(\d+)'
172        pattern_channels = r'channels=(\d+)'
173
174        # Search for values in the ffprobe output
175        match_sample_rate = re.search(pattern_sample_rate, output, flags=re.MULTILINE)
176        match_channels = re.search(pattern_channels, output, flags=re.MULTILINE)
177
178        # Extraction des valeurs
179        if match_sample_rate:
180            sample_rate = int(match_sample_rate.group(1))
181        else:
182            raise AudioIO.AudioIOException("Unable to get audio sample_rate of '" + str(filename) + "'")
183
184        if match_channels:
185            channels = int(match_channels.group(1))
186        else:
187            raise AudioIO.AudioIOException("Unable to get audio channels of '" + str(filename) + "'")
188
189        return (channels,sample_rate)
190
191        # Attributes
192        mode: PipeMode
193        """ Pipemode of the current object (default PipeMode.UNK_MODE)"""
194
195        loglevel: int
196        """ loglevel of the underlying ffmpeg backend for this object (default 16)"""
197
198        debugModel: bool
199        """ debutMode flag for this object (print debut info, default False)"""
200
201        channels: int
202        """ Number of channels of images (default -1) """
203
204        sample_rate: int
205        """ sample_rate of images (default -1) """
206
207        plannar: bool
208        """ Read/write data as plannar, i.e. not interleaved (default True) """
209
210        pipe: sp.Popen
211        """ pipe object to ffmpeg/ffprobe (default None)"""
212
213        frame_size: int
214        """ Weight in bytes of one image (default -1)"""
215
216        filename: str
217        """ Filename of the file (default None)"""
218
219        frame_counter: FrameCounter
220        """ `Framecounter` object to count ellapsed time (default None)"""

Static method to get params (channels,sample_rate) of a (video containing) audio file in seconds.

Parameters

filename : str or path. Raw audio waveform as a 1D array.

debug : bool (default (False). Show debug info.

log_level: int (default 16). Log level to pass to the underlying ffmpeg/ffprobe command.

Returns

tuple Tuple containing (channels,sample_rate) of the file

def isOpened(self) -> bool:
314    def is_opened(self) -> bool:
315        """
316        Method to get status of the underlying pipe to ffmpeg.
317
318        Returns
319        ----------
320        bool
321            True if pipe is opened (reading or writing mode), False if not.
322        """
323        # is the pip opened?
324        if self.pipe is not None and self.pipe.poll() is None:
325            return True
326
327        return False

Method to get status of the underlying pipe to ffmpeg.

Returns

bool True if pipe is opened (reading or writing mode), False if not.

def readFrame(self, with_timestamps=False):
549    def read_frame(self, with_timestamps = False):
550        """
551        Read next frame from the audio file
552
553        Parameters
554        ----------
555        with_timestamps: bool optional (default False)
556            If set to True, the method returns a ``FrameContainer`` with the audio and an array containing the associated timestamp(s)
557
558        Returns
559        ----------
560        nparray or FrameContainer
561            A frame of shape (self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A frame
562            of shape (self.channels*self.frame_size) with interleaved data if self.plannar is False.
563            if with_timestamps is True, the return object is a FrameContainer with the audio data in ``FrameContainer.data`` and
564            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element).
565        """
566
567        if self.pipe is None:
568            raise self.AudioIOException("No pipe opened to {}. Call open(...) before reading a frame.".format(self.audioProgram))
569        # - pipe is in write mode
570        if self.mode != PipeMode.READ_MODE:
571            raise self.AudioIOException("Pipe to {} for '{}' not opened in read mode.".format(self.audioProgram, self.filename))
572
573        if with_timestamps:
574            # get elapsed time in video, it is time of next frame(s)
575            current_elapsed_time = self.get_elapsed_time()
576
577        # read rgb image from pipe
578        toread = self.frame_size*4
579        buffer = self.pipe.stdout.read(toread)
580        if len(buffer) != toread:
581            # not considered as an error, no more frame, no exception
582            return None
583
584        # get numpy UINT8 array from buffer
585        audio = np.frombuffer(buffer, dtype = np.float32).reshape(self.frame_size, self.channels)
586
587        # make it plannar (or not)
588        if self.plannar:
589            #transpose it
590            audio = audio.T
591
592        # increase frame_counter
593        self.frame_counter.frame_count += (self.frame_size * self.channels)
594
595        # say to gc that this buffer is no longer needed
596        del buffer
597
598        if with_timestamps:
599            return FrameContainer(1, audio, self.frame_size/self.sample_rate, current_elapsed_time)
600        
601        return audio

Read next frame from the audio file

Parameters

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the audio and an array containing the associated timestamp(s)

Returns

nparray or FrameContainer A frame of shape (self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A frame of shape (self.channels*self.frame_size) with interleaved data if self.plannar is False. if with_timestamps is True, the return object is a FrameContainer with the audio data in FrameContainer.data and the associated timestamp in FrameContainer.timestamps as an array (one element).

def readBatch(self, numberOfFrames, with_timestamps=False):
603    def read_batch(self, numberOfFrames, with_timestamps = False):
604        """
605        Read next batch of audio from the file
606
607        Parameters
608        ----------
609        number_of_frames: int
610            Number of desired images within the batch. The last batch from the file may have less images.
611            
612        with_timestamps: bool optional (default False)
613            If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames
614
615        Returns
616        ----------
617        nparray or FrameContainer
618            A batch of shape (n, self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A batch
619            of shape (n, self.channels*self.frame_size) with interleaved data if self.plannar is False.
620            if with_timestamps is True, the return object is a FrameContainer with the audio batch in ``FrameContainer.data`` and
621            the associated timestamp in ``FrameContainer.timestamps`` as an array (one element for each audio frame).
622        """
623
624        if self.pipe is None:
625            raise self.AudioIOException("No pipe opened to {}. Call open(...) before reading frames.".format(self.audioProgram))
626        # - pipe is in write mode
627        if self.mode != PipeMode.READ_MODE:
628            raise self.AudioIOException("Pipe to {} for '{}' not opened in read mode.".format(self.audioProgram, self.filename))
629
630        if with_timestamps:
631            # get elapsed time in video, it is time of next frame(s)
632            current_elapsed_time = self.get_elapsed_time()
633
634        # try to read complete batch
635        toread = self.frame_size*4*self.channels*numberOfFrames
636        buffer = self.pipe.stdout.read(toread)
637
638        # check if we have at least 1 Frame
639        if len(buffer) < toread:
640            # not considered as an error, no more frame, no exception
641            return None
642
643        # compute actual number of Frames
644        actualNbFrames = len(buffer)//(self.frame_size*4*self.channels)
645
646        # get and reshape batch from buffer
647        batch = np.frombuffer(buffer, dtype = np.float32).reshape((actualNbFrames, self.frame_size, self.channels,))
648
649        if self.plannar:
650            batch = batch.transpose(0, 2, 1)
651
652        # increase frame_counter
653        self.frame_counter.frame_count += (actualNbFrames * self.frame_size * self.channels)
654        
655        # say to gc that this buffer is no longer needed
656        del buffer
657
658        if with_timestamps:
659            return FrameContainer( actualNbFrames, batch, self.frame_size/self.sample_rate, current_elapsed_time)
660        
661        return batch

Read next batch of audio from the file

Parameters

number_of_frames: int Number of desired images within the batch. The last batch from the file may have less images.

with_timestamps: bool optional (default False) If set to True, the method returns a FrameContainer with the batch and the an array containing the associated timestamps to frames

Returns

nparray or FrameContainer A batch of shape (n, self.channels,self.frame_size) as defined in the reader/open call if self.plannar is True. A batch of shape (n, self.channels*self.frame_size) with interleaved data if self.plannar is False. if with_timestamps is True, the return object is a FrameContainer with the audio batch in FrameContainer.data and the associated timestamp in FrameContainer.timestamps as an array (one element for each audio frame).

def writeFrame(self, audio) -> bool:
663    def write_frame(self, audio) -> bool:
664        """
665        Write an audio frame to the file
666
667        Parameters
668        ----------
669        audio: nparray
670            The audio frame to write to the video file of shape (self.channels,nb_samples_per_channel) if plannar is True else (self.channels*nb_samples_per_channel).
671
672        Returns
673        ----------
674        bool
675            Writing was successful or not.
676        """
677        # Check params
678        # - pipe exists
679        if self.pipe is None:
680            raise self.AudioIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.audioProgram))
681        # - pipe is in write mode
682        if self.mode != PipeMode.WRITE_MODE:
683            raise self.AudioIOException("Pipe to {} for '{}' not opened in write mode.".format(self.audioProgram, self.filename))
684        # - shape of image is fine, thus we have pixels for a full compatible frame
685        if audio.shape[0] != self.channels:
686            raise self.AudioIOException("Wong audio shape: {} expected ({},{}).".format(audio.shape,self.channels,self.frame_size))
687        # - type of data is Float32
688        if audio.dtype != np.float32:
689            raise self.AudioIOException("Wong audio type: {} expected np.float32.".format(audio.dtype))
690
691        # array must have a shape (channels, samples), reshape it it to (samples, channels) if plannar
692        if not self.plannar:
693            audio = audio.reshape(-1)
694
695        # print( audio.shape )
696
697        # garantee to have a C continuous array
698        if not audio.flags['C_CONTIGUOUS']:
699            a = np.ascontiguousarray(a) 
700
701        # write frame
702        buffer = audio.tobytes()
703        if self.pipe.stdin.write( buffer ) < len(buffer):
704            print( f"Error writing frame to {self.filename}" )
705            return False
706
707        # increase frame_counter
708        self.frame_counter.frame_count += (self.frame_size * self.channels)
709
710        # say to gc that this buffer is no longer needed 
711        del buffer
712
713        return True

Write an audio frame to the file

Parameters

audio: nparray The audio frame to write to the video file of shape (self.channels,nb_samples_per_channel) if plannar is True else (self.channels*nb_samples_per_channel).

Returns

bool Writing was successful or not.

def writeBatch(self, batch):
715    def write_batch(self, batch):
716        """
717        Write a batch of audio frame to the file
718
719        Parameters
720        ----------
721        batch: nparray
722            The batch of audio frames to write to the video file of shape (n,self.channels,nb_samples_per_channel) if plannar is True else (n,self.channels*nb_samples_per_channel) of interleaved audio data.
723
724        Returns
725        ----------
726        bool
727            Writing was successful or not.
728        """
729        # Check params
730        # - pipe exists
731        if self.pipe is None:
732            raise self.AudioIOException("No pipe opened to {}. Call create(...) before writing frames.".format(self.audioProgram))
733        # - pipe is in write mode
734        if self.mode != PipeMode.WRITE_MODE:
735            raise self.AudioIOException("Pipe to {} for '{}' not opened in write mode.".format(self.audioProgram, self.filename))
736        # batch is 3D (n, channels, nb samples)
737        if batch.ndim !=3:
738            raise self.AudioIOException("Wrong batch shape: {} expected 3 dimensions (n, n_channels, n_samples_per_channel).".format(batch.shape))
739        # - shape of images in batch is fine
740        if batch.shape[2] != self.channels:
741            raise self.AudioIOException("Wrong audio channels in batch: {} expected {} {}.".format(batch.shape[2], self.channels, batch.shape))
742
743        # array must have a shape (n * n_channels * n_samples_per_channel) before writing them to pipe
744        # reshape it it to (n * n_channels * n_samples_per_channel) if plannar is False
745        if not self.plannar:
746            # goes from (n, n_channels, n_samples_per_channel) to (n * n_channels * n_samples_per_channel)
747            batch = batch.transpose(0, 2, 1) # first go to (n, n_samples_per_channel, n_channels)
748            batch = batch.reshape(-1) # then to 1D array (n * n_channels * n_samples_per_channel)
749
750        # garantee to have a C continuous array
751        if not batch.flags['C_CONTIGUOUS']:
752            batch = np.ascontiguousarray(batch)
753
754        # write frame
755        buffer = batch.tobytes()
756        if self.pipe.stdin.write( buffer ) < len(buffer):
757            # say to gc that this buffer is no longer needed
758            del buffer
759            raise self.AudioIOException("Error writing batch to '{}'.".format(self.filename))
760
761        # increase frame_counter
762        self.frame_counter.frame_count += int(batch.shape[0]/self.channels) # int conversion is mandatory to avoid confusion with time as float
763              
764        # say to gc that this buffer is no longer needed
765        del buffer
766
767        return True

Write a batch of audio frame to the file

Parameters

batch: nparray The batch of audio frames to write to the video file of shape (n,self.channels,nb_samples_per_channel) if plannar is True else (n,self.channels*nb_samples_per_channel) of interleaved audio data.

Returns

bool Writing was successful or not.

audioProgram = '/usr/local/lib/python3.12/site-packages/static_ffmpeg/bin/linux/ffmpeg'
paramProgram = '/usr/local/lib/python3.12/site-packages/static_ffmpeg/bin/linux/ffprobe'
class AudioIO.AudioIOException(builtins.Exception):
36    class AudioIOException(Exception):
37        """
38        Dedicated exception class for AudioIO class.
39        """
40        def __init__(self, message="Error while reading/writing video occurs"):
41            self.message = message
42            super().__init__(self.message)

Dedicated exception class for AudioIO class.

AudioIO.AudioIOException(message='Error while reading/writing video occurs')
40        def __init__(self, message="Error while reading/writing video occurs"):
41            self.message = message
42            super().__init__(self.message)
message
class AudioIO.AudioFormat(enum.Enum):
44    class AudioFormat(Enum):
45        """
46        Enum class for supported input video type: 32-bit float is the only supported type for the moment.
47        """
48        PCM32LE = 'pcm_f32le' # default format (unique mode for the moment)

Enum class for supported input video type: 32-bit float is the only supported type for the moment.

PCM32LE = <AudioFormat.PCM32LE: 'pcm_f32le'>
class FrameCounter:
 16class FrameCounter:
 17    """
 18    Create a ``FrameCounter`` to follow elapsed time in audio/video file in read or write mode. Static utility functions allow to format elapsed time.
 19    """
 20
 21    class FrameCounterException(Exception):
 22        """
 23        Dedicated exception class for FrameCounter class.
 24        """
 25        def __init__(self, message="Error while setting FrameCounter parameters."):
 26            self.message = message
 27            super().__init__(self.message)
 28
 29    _fps: float # (private)
 30    """ Fps of current stream """
 31
 32    _frame_count: int # (private)
 33    """ Frame count in the current stream """
 34
 35    def __init__(self, fps: Union[int, float]):
 36        """
 37        Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode
 38
 39        Parameters
 40        ----------
 41        fps: int or float.
 42            Frames per second of the associated stream.
 43        """
 44        # check init fps value
 45        self._fps = float(fps)
 46        if self._fps <= 0.0:
 47            raise FrameCounterException("fps must be > 0.0.")
 48
 49        # 2 modes
 50        self._frame_count = 0       # at 00:00:00.000
 51
 52    # support +=
 53    def __iadd__(self, other: Union[int, float]):
 54        """
 55        Support += operator for FrameCounter. 
 56
 57        Parameters
 58        ----------
 59        other: int or float.
 60            If other is a float, add the number of frame to add 'other' seconds (thus other * self._fps samples).
 61            If other is an int, add the value as a number of samples in the stream.
 62        """        
 63        if isinstance(other,float):
 64            # float means adding time
 65            self._frame_count += int(other * self._fps) # number of second * Nb of elements per seconds
 66        else:
 67            # for int, add number of element
 68            self._frame_count += other
 69        return self
 70    
 71    @property
 72    def frame_count(self):
 73        """
 74        Property to get underlying self._frame_count. Idea is to control setter to valid setting values.
 75        """   
 76        return self._frame_count
 77
 78    @frame_count.setter
 79    def frame_count(self, value: int):
 80        """
 81        Setter for underlying self._frame_count controlling setting value.
 82        """   
 83        if value < 0:
 84            raise FrameCounterException("frame_count must be >= 0")
 85        self._frame_count = value
 86
 87    @property
 88    def fps(self):
 89        """
 90        Property to get underlying self._fps.
 91        """
 92        return self._fps
 93
 94    @staticmethod
 95    def format_time(nb_frames: int, fps: float, show_ms : bool = True, show_days : bool = False) -> str:
 96        """
 97        Static function to format time given by a number of frames and an fps. show_ms value defines if we show milliseconds,
 98        show_days to show days instead of cummulative hour count.
 99
100        Parameters
101        ----------
102        nb_frames: int.
103            Number of samples already present in the stream.
104            
105        fps: float.
106            Fps of the associated stream.
107            
108        show_ms: bool.
109            Flag to say if we want to show milliseconds in the output str.
110            
111        show_days: bool.
112            Flag to say if we want to show says instead of cumulative hours in the output str.
113
114        Returns
115        -------
116            str representing corresponding time. Either 26:15:00 (show_ms=False, show_days=False), 26:15:00.500 (show_ms=True, show_days=False)
117            or 1 day(s) 02:15:00.500 (show_ms=True, show_days=True)
118        """  
119        # exact time in seconds (float)
120        exact_seconds = nb_frames / fps
121
122        # integer part for days/hours/minutes/seconds
123        total_seconds = int(exact_seconds)
124
125        # milliseconds = decimal part * 1000
126        millis = int(round((exact_seconds - total_seconds) * 1000))
127
128        # handle the case where rounding results in 1000 ms
129        if millis == 1000:
130            millis = 0
131            total_seconds += 1
132
133        if show_days:
134            # compute number of days, hours, minutes, seconds
135            days, mod = divmod(total_seconds, 24 * 3600)
136            hours, mod = divmod(mod, 3600)
137            minutes, seconds = divmod(mod, 60)
138
139            if days > 0:
140                days = f"{days} days(s) "
141            else:
142                days = ""
143        else:
144            days = "" # no day as show_days = False
145            hours, mod = divmod(total_seconds, 3600)
146            minutes, seconds = divmod(mod, 60)
147
148        if show_ms == True:
149            millis = f".{millis:03d}"
150        else:
151            millis = ""
152
153        return f"{days}{hours:02d}:{minutes:02d}:{seconds:02d}{millis}"
154
155    def get_elapsed_time_as_str(self) -> str:
156        """
157        Get elapsed time as string representing a float value rounded to 3 decimals.
158
159        Returns
160        -------
161            str representing corresponding time in float format rounded to 3 decimals.
162        """
163        return f"{float(self._frame_count)/self._fps:.3f}"
164
165    def get_formated_elapsed_time_as_str(self, show_ms : bool = True, show_days : bool = False) -> str:
166        """
167        Get elapsed time as string representing time with different mode (see ``FrameCounter.format_time`` for parameter explanation).
168        Returns
169        -------
170            str representing corresponding time in float format rounded to 3 decimals.
171        """
172        # frame count to time correction is done in format_time
173        return FrameCounter.format_time(self._frame_count, self._fps, show_ms, show_days)
174
175    def get_elapsed_time(self) -> float:
176        """
177        Get elapsed time as float value rounded to 3 decimals.
178
179        Returns
180        -------
181            str representing corresponding time in float format rounded to 3 decimals.
182        """
183        return round(float(self._frame_count)/self._fps,3)

Create a FrameCounter to follow elapsed time in audio/video file in read or write mode. Static utility functions allow to format elapsed time.

FrameCounter(fps: Union[int, float])
35    def __init__(self, fps: Union[int, float]):
36        """
37        Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode
38
39        Parameters
40        ----------
41        fps: int or float.
42            Frames per second of the associated stream.
43        """
44        # check init fps value
45        self._fps = float(fps)
46        if self._fps <= 0.0:
47            raise FrameCounterException("fps must be > 0.0.")
48
49        # 2 modes
50        self._frame_count = 0       # at 00:00:00.000

Create a VideoIO object giving ffmpeg/ffrobe loglevel and defining debug mode

Parameters

fps: int or float. Frames per second of the associated stream.

frame_count
71    @property
72    def frame_count(self):
73        """
74        Property to get underlying self._frame_count. Idea is to control setter to valid setting values.
75        """   
76        return self._frame_count

Property to get underlying self._frame_count. Idea is to control setter to valid setting values.

fps
87    @property
88    def fps(self):
89        """
90        Property to get underlying self._fps.
91        """
92        return self._fps

Property to get underlying self._fps.

@staticmethod
def format_time( nb_frames: int, fps: float, show_ms: bool = True, show_days: bool = False) -> str:
 94    @staticmethod
 95    def format_time(nb_frames: int, fps: float, show_ms : bool = True, show_days : bool = False) -> str:
 96        """
 97        Static function to format time given by a number of frames and an fps. show_ms value defines if we show milliseconds,
 98        show_days to show days instead of cummulative hour count.
 99
100        Parameters
101        ----------
102        nb_frames: int.
103            Number of samples already present in the stream.
104            
105        fps: float.
106            Fps of the associated stream.
107            
108        show_ms: bool.
109            Flag to say if we want to show milliseconds in the output str.
110            
111        show_days: bool.
112            Flag to say if we want to show says instead of cumulative hours in the output str.
113
114        Returns
115        -------
116            str representing corresponding time. Either 26:15:00 (show_ms=False, show_days=False), 26:15:00.500 (show_ms=True, show_days=False)
117            or 1 day(s) 02:15:00.500 (show_ms=True, show_days=True)
118        """  
119        # exact time in seconds (float)
120        exact_seconds = nb_frames / fps
121
122        # integer part for days/hours/minutes/seconds
123        total_seconds = int(exact_seconds)
124
125        # milliseconds = decimal part * 1000
126        millis = int(round((exact_seconds - total_seconds) * 1000))
127
128        # handle the case where rounding results in 1000 ms
129        if millis == 1000:
130            millis = 0
131            total_seconds += 1
132
133        if show_days:
134            # compute number of days, hours, minutes, seconds
135            days, mod = divmod(total_seconds, 24 * 3600)
136            hours, mod = divmod(mod, 3600)
137            minutes, seconds = divmod(mod, 60)
138
139            if days > 0:
140                days = f"{days} days(s) "
141            else:
142                days = ""
143        else:
144            days = "" # no day as show_days = False
145            hours, mod = divmod(total_seconds, 3600)
146            minutes, seconds = divmod(mod, 60)
147
148        if show_ms == True:
149            millis = f".{millis:03d}"
150        else:
151            millis = ""
152
153        return f"{days}{hours:02d}:{minutes:02d}:{seconds:02d}{millis}"

Static function to format time given by a number of frames and an fps. show_ms value defines if we show milliseconds, show_days to show days instead of cummulative hour count.

Parameters

nb_frames: int. Number of samples already present in the stream.

fps: float. Fps of the associated stream.

show_ms: bool. Flag to say if we want to show milliseconds in the output str.

show_days: bool. Flag to say if we want to show says instead of cumulative hours in the output str.

Returns

str representing corresponding time. Either 26:15:00 (show_ms=False, show_days=False), 26:15:00.500 (show_ms=True, show_days=False)
or 1 day(s) 02:15:00.500 (show_ms=True, show_days=True)
def get_elapsed_time_as_str(self) -> str:
155    def get_elapsed_time_as_str(self) -> str:
156        """
157        Get elapsed time as string representing a float value rounded to 3 decimals.
158
159        Returns
160        -------
161            str representing corresponding time in float format rounded to 3 decimals.
162        """
163        return f"{float(self._frame_count)/self._fps:.3f}"

Get elapsed time as string representing a float value rounded to 3 decimals.

Returns

str representing corresponding time in float format rounded to 3 decimals.
def get_formated_elapsed_time_as_str(self, show_ms: bool = True, show_days: bool = False) -> str:
165    def get_formated_elapsed_time_as_str(self, show_ms : bool = True, show_days : bool = False) -> str:
166        """
167        Get elapsed time as string representing time with different mode (see ``FrameCounter.format_time`` for parameter explanation).
168        Returns
169        -------
170            str representing corresponding time in float format rounded to 3 decimals.
171        """
172        # frame count to time correction is done in format_time
173        return FrameCounter.format_time(self._frame_count, self._fps, show_ms, show_days)

Get elapsed time as string representing time with different mode (see FrameCounter.format_time for parameter explanation).

Returns

str representing corresponding time in float format rounded to 3 decimals.
def get_elapsed_time(self) -> float:
175    def get_elapsed_time(self) -> float:
176        """
177        Get elapsed time as float value rounded to 3 decimals.
178
179        Returns
180        -------
181            str representing corresponding time in float format rounded to 3 decimals.
182        """
183        return round(float(self._frame_count)/self._fps,3)

Get elapsed time as float value rounded to 3 decimals.

Returns

str representing corresponding time in float format rounded to 3 decimals.
class FrameCounter.FrameCounterException(builtins.Exception):
21    class FrameCounterException(Exception):
22        """
23        Dedicated exception class for FrameCounter class.
24        """
25        def __init__(self, message="Error while setting FrameCounter parameters."):
26            self.message = message
27            super().__init__(self.message)

Dedicated exception class for FrameCounter class.

FrameCounter.FrameCounterException(message='Error while setting FrameCounter parameters.')
25        def __init__(self, message="Error while setting FrameCounter parameters."):
26            self.message = message
27            super().__init__(self.message)
message
class FrameContainer:
16class FrameContainer:
17    """
18    Create a Container with audio or image data and associated timestamp(s).
19    """
20
21    class FrameContainerException(Exception):
22        """
23        Dedicated exception class for FrameContainer class.
24        """
25        def __init__(self, message="Error while setting FrameCounter parameters."):
26            self.message = message
27            super().__init__(self.message)
28
29    nb_frames: int
30    """ number of frames in the frame container. 1 for audio frame or images, n for a batch. """
31
32    data: np.array
33    """ Audio frame or image, or batch of images or audio frames. """
34
35    timestamps: np.array
36    """ an np.array of timestamp(s) associated to the data frame(s). """
37
38    def __init__(self, nb_frames : int, frames : np.array, fps : float, start_time : float = 0.0 ):
39        """
40        Create a FrameContainer object with data and associated timestamps
41
42        Parameters
43        ----------
44        nb_frames: int.
45            Number of frame(s) in the frame container. Either 1, either number of element in the batch.
46
47        frames:  np.array
48            Data for image, audio frame or batch of them.
49
50        fps : float.
51            fps of the read stream.
52            
53        start_time: float (default = 0.0).
54            Start time of the current FrameContainer, i.e. time of the (first) frame.
55        """
56        
57        # check params
58        if nb_frames <= 0:
59            raise self.FrameContainerException("nb_frames must be > 0.")
60        if nb_frames > 1 and frames.shape[0] != nb_frames:
61            raise self.FrameContainerException("nb_frames and batch size (frames.shape[0]) differs.")
62        if fps <= 0.0:
63            raise self.FrameContainerException("fps must be > 0.0.")
64        if start_time < 0.0:
65            raise self.FrameContainerException("start_time must be >= 0.0.")
66
67        self.nb_frames = nb_frames
68        self.data = frames
69        # first frame is at time start_time
70        self.timestamps = np.linspace(start_time, start_time+(self.nb_frames-1)*fps, self.nb_frames).tolist()
71
72    def totorch(self):
73        """
74        Convert the numy array into a pytorch tensor.
75
76        Returns
77        ----------
78            pytorch tensor.
79        """
80        # import torch late as it is not in the standard requirements for simple-ffmpeg-batch-io
81        import torch
82        return torch.from_numpy(self.data)

Create a Container with audio or image data and associated timestamp(s).

FrameContainer( nb_frames: int, frames: <built-in function array>, fps: float, start_time: float = 0.0)
38    def __init__(self, nb_frames : int, frames : np.array, fps : float, start_time : float = 0.0 ):
39        """
40        Create a FrameContainer object with data and associated timestamps
41
42        Parameters
43        ----------
44        nb_frames: int.
45            Number of frame(s) in the frame container. Either 1, either number of element in the batch.
46
47        frames:  np.array
48            Data for image, audio frame or batch of them.
49
50        fps : float.
51            fps of the read stream.
52            
53        start_time: float (default = 0.0).
54            Start time of the current FrameContainer, i.e. time of the (first) frame.
55        """
56        
57        # check params
58        if nb_frames <= 0:
59            raise self.FrameContainerException("nb_frames must be > 0.")
60        if nb_frames > 1 and frames.shape[0] != nb_frames:
61            raise self.FrameContainerException("nb_frames and batch size (frames.shape[0]) differs.")
62        if fps <= 0.0:
63            raise self.FrameContainerException("fps must be > 0.0.")
64        if start_time < 0.0:
65            raise self.FrameContainerException("start_time must be >= 0.0.")
66
67        self.nb_frames = nb_frames
68        self.data = frames
69        # first frame is at time start_time
70        self.timestamps = np.linspace(start_time, start_time+(self.nb_frames-1)*fps, self.nb_frames).tolist()

Create a FrameContainer object with data and associated timestamps

Parameters

nb_frames: int. Number of frame(s) in the frame container. Either 1, either number of element in the batch.

frames: np.array Data for image, audio frame or batch of them.

fps : float. fps of the read stream.

start_time: float (default = 0.0). Start time of the current FrameContainer, i.e. time of the (first) frame.

nb_frames: int

number of frames in the frame container. 1 for audio frame or images, n for a batch.

data: <built-in function array>

Audio frame or image, or batch of images or audio frames.

timestamps: <built-in function array>

an np.array of timestamp(s) associated to the data frame(s).

def totorch(self):
72    def totorch(self):
73        """
74        Convert the numy array into a pytorch tensor.
75
76        Returns
77        ----------
78            pytorch tensor.
79        """
80        # import torch late as it is not in the standard requirements for simple-ffmpeg-batch-io
81        import torch
82        return torch.from_numpy(self.data)

Convert the numy array into a pytorch tensor.

Returns

pytorch tensor.
class FrameContainer.FrameContainerException(builtins.Exception):
21    class FrameContainerException(Exception):
22        """
23        Dedicated exception class for FrameContainer class.
24        """
25        def __init__(self, message="Error while setting FrameCounter parameters."):
26            self.message = message
27            super().__init__(self.message)

Dedicated exception class for FrameContainer class.

FrameContainer.FrameContainerException(message='Error while setting FrameCounter parameters.')
25        def __init__(self, message="Error while setting FrameCounter parameters."):
26            self.message = message
27            super().__init__(self.message)
message
class PipeMode(enum.Enum):
15class PipeMode(Enum):
16    """
17    Enum class for pipe opening type.
18    """
19    UNK_MODE = 0
20    """ No pipe mode defined """
21
22    READ_MODE = 1  
23    """ Read mode (get data from pipe) """
24
25    WRITE_MODE = 2 
26    """ Write mode (write data to pipe) """

Enum class for pipe opening type.

UNK_MODE = <PipeMode.UNK_MODE: 0>

No pipe mode defined

READ_MODE = <PipeMode.READ_MODE: 1>

Read mode (get data from pipe)

WRITE_MODE = <PipeMode.WRITE_MODE: 2>

Write mode (write data to pipe)