Source code for pyctools.components.io.rawfilereader

#  Pyctools - a picture processing algorithm development kit.
#  http://github.com/jim-easterbrook/pyctools
#  Copyright (C) 2014-20  Pyctools contributors
#
#  This program is free software: you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see
#  <http://www.gnu.org/licenses/>.

__all__ = ['RawFileReader']
__docformat__ = 'restructuredtext en'

import io
import os

import numpy

from pyctools.core.config import ConfigBool, ConfigEnum, ConfigInt, ConfigPath
from pyctools.core.base import Component
from pyctools.core.frame import Metadata
from pyctools.core.types import pt_float


[docs] class RawFileReader(Component): """Read "raw" YUV or RGB files. :py:class:`RawFileReader` has been superseded by :py:class:`~.videofilereader2.VideoFileReader2`. Video is usually stored in file formats (such as AVI) with a complex structure to allow a mix of audio, video and other data. These can be read with the :py:class:`~pyctools.components.io.videofilereader.VideoFileReader` component. This component reads simple "raw" files that contain nothing but the picture data. Even the image dimensions have to be stored in a separate "metadata" file. (Use the :py:mod:`pyctools-setmetadata <pyctools.tools.setmetadata>` tool to create or modify the metadata file.) There are many possible arrangements of data in raw files. For example, the colour components can be packed (multiplexed) together or stored in separate planes. The formats are labelled with a short string knows as a fourcc_ code. This code needs to be in the metadata file with the image dimensions. Note that when reading "YUV" formats the U & V outputs are offset by 128 to restore their range to -128..127 (from the file range of 0..255). This makes subsequent processing a lot easier. The ``zperiod`` config item can be used to adjust the repeat period so it is an integer multiple of a chosen number, e.g. 4 frames for a PAL encoded sequence. It has no effect if ``looping`` is not ``repeat``. =========== ==== ==== Config =========== ==== ==== ``path`` str Path name of file to be read. ``looping`` str Whether to play continuously. Can be ``'off'``, ``'repeat'`` or ``'reverse'``. ``noaudit`` bool Don't output file's "audit trail" metadata. ``zperiod`` int Adjust repeat period to an integer multiple of ``zperiod``. =========== ==== ==== .. _fourcc: http://www.fourcc.org/ """ inputs = [] outputs = ['output_Y_RGB', 'output_UV'] #: def initialise(self): print('Deprecation warning: ' 'please use VideoFileReader2 instead of RawFileReader') self.config['path'] = ConfigPath() self.config['looping'] = ConfigEnum(choices=('off', 'repeat', 'reverse')) self.config['noaudit'] = ConfigBool() self.config['zperiod'] = ConfigInt(min_value=0) def on_start(self): # create file reader self.generator = self.file_reader() def process_frame(self): Y_frame, UV_frame = next(self.generator) self.send('output_Y_RGB', Y_frame) if UV_frame: self.send('output_UV', UV_frame) params = { # fourcc planar ssy ssx bpp 'RGB[24]': (False, 1, 1, 24), 'HDYC' : (False, 1, 2, 16), 'IYU2' : (False, 1, 1, 24), 'UYNV' : (False, 1, 2, 16), 'UYVY' : (False, 1, 2, 16), 'V422' : (False, 1, 2, 16), 'Y16' : (True, 1, 1, 16), 'Y422' : (False, 1, 2, 16), 'Y8' : (True, 1, 1, 8), 'YUNV' : (False, 1, 2, 16), 'YUYV' : (False, 1, 2, 16), 'YUY2' : (False, 1, 2, 16), 'YVYU' : (False, 1, 2, 16), 'I420' : (True, 2, 2, 12), 'IYUV' : (True, 2, 2, 12), 'YV12' : (True, 2, 2, 12), 'YV16' : (True, 1, 2, 16), 'YVU9' : (True, 4, 4, 9), } def file_reader(self): # set metadata self.update_config() path = self.config['path'] metadata = Metadata().from_file(path) fourcc = metadata.get('fourcc') xlen, ylen = metadata.image_size() # set params according to dimensions and fourcc if fourcc not in self.params: self.logger.critical("Can't read '%s' files", fourcc) return planar, ssy, ssx, bpp = self.params[fourcc] bytes_per_frame = ylen * xlen * bpp // 8 zlen = os.path.getsize(path) // bytes_per_frame if zlen < 1: self.logger.critical("Zero length file %s", path) return file_frame = 0 direction = 1 frame_no = 0 with io.open(path, 'rb', 0) as raw_file: while True: self.update_config() looping = self.config['looping'] zperiod = self.config['zperiod'] frames = zlen if zlen > zperiod and zperiod > 1 and looping == 'repeat': frames -= zlen % zperiod if file_frame >= frames: if looping == 'off': return elif looping == 'repeat': file_frame = 0 raw_file.seek(0) else: file_frame = frames - 2 direction = -1 elif file_frame < 0: if looping == 'off': return file_frame = 1 direction = 1 if direction < 0: raw_file.seek(-2 * bytes_per_frame, io.SEEK_CUR) file_frame += direction # read frame into 1D array raw_data = raw_file.read(bytes_per_frame) if fourcc == 'Y16': data = numpy.ndarray( shape=(ylen * xlen,), dtype='<u2', buffer=raw_data) data = data.astype(pt_float) / pt_float(256.0) else: data = numpy.ndarray( shape=(bytes_per_frame,), dtype=numpy.uint8, buffer=raw_data) # demux data Y_frame = self.outframe_pool['output_Y_RGB'].get() Y_frame.metadata.copy(metadata) Y_frame.frame_no = frame_no Y_frame.type = 'Y' Y_audit = 'data = {}\n fourcc: {}\n' UV_frame = None if fourcc in ('Y16', 'Y8'): Y = data.reshape((ylen, xlen, 1)) elif fourcc == 'RGB[24]': Y_frame.type = 'RGB' B, G, R = numpy.dsplit(data.reshape((ylen, xlen, 3)), 3) Y = numpy.dstack((R, G, B)) else: # YUV data Y_audit = 'data = demultiplex({})[Y]\n fourcc: {}\n' UV_audit = 'data = demultiplex({})[UV]\n fourcc: {}\n' UV_frame = self.outframe_pool['output_UV'].get() UV_frame.metadata.copy(metadata) UV_frame.set_audit( self, UV_audit.format(os.path.basename(path), fourcc), with_history=not self.config['noaudit'], with_config=self.config) UV_frame.frame_no = frame_no UV_frame.type = 'CbCr' if planar: Y = data[0:xlen * ylen].reshape((ylen, xlen, 1)) UV = data[xlen * ylen:].reshape(( 2, ylen // ssy, xlen // ssx, 1)) if fourcc in ('IYUV', 'I420'): # planar format, YUV order U, V = UV[0], UV[1] else: # planar format, YVU order U, V = UV[1], UV[0] elif fourcc == 'IYU2': U, Y, V = numpy.dsplit(data.reshape((ylen, xlen, 3)), 3) else: quad = numpy.dsplit( data.reshape((ylen, xlen // 2, 4)), 4) if fourcc in ('UYVY', 'UYNV', 'Y422', 'HDYC'): # packed format, UYVY order U, Y0, V, Y1 = quad elif fourcc in ('YVYU',): # packed format, YVYU order Y0, V, Y1, U = quad elif fourcc in ('YUYV', 'YUY2', 'YUNV', 'V422'): # packed format, YUYV order Y0, U, Y1, V = quad Y = numpy.dstack((Y0, Y1)) Y = Y.reshape((ylen, xlen, 1)) UV = numpy.dstack((U, V)) # remove offset UV_frame.data = UV.astype(pt_float) - pt_float(128.0) Y_frame.set_audit( self, Y_audit.format(os.path.basename(path), fourcc), with_history=not self.config['noaudit'], with_config=self.config) Y_frame.data = Y yield Y_frame, UV_frame frame_no += 1