PySide/python video player issue
Question: I am trying to write a simple YUV video player using python. After some
initial study, I thought I could use PySide and started with it. As a first
step, I have taken the following approach without consideration for real-time
performance. Read YUV buffer (420 planar) -> convert the YUV image to RGB
(32bit format) - > call PySide utilities for display. The basic problem that I
have with my simple program is that I am able to get only the first frame to
display and the rest are not displayed, eventhough the paint event seems to be
happening according to the counter in the (below) code. I would appreciate any
comments to understand (i) any mistakes and lack of understanding from my side
regarding painting/repainting at regular intervals on QLabel/QWidget. (ii) Any
pointers to Python based video players/display from YUV or RGB source.

    
    
        #!/usr/bin/python
    
    import sys
    from PySide.QtCore import *
    from PySide.QtGui import *
    import array
    import numpy as np
    
    class VideoWin(QWidget):
        def __init__(self, width, height, f_yuv):
            QWidget.__init__(self)
            self.width = width
            self.height = height
            self.f_yuv = f_yuv
            self.setWindowTitle('Video Window')
            self.setGeometry(10, 10, width, height)
            self.display_counter = 0
            self.img = QImage(width, height, QImage.Format_ARGB32)
            #qApp.processEvents()
    
        def getImageBuf(self):
            return self.img.bits()
    
        def paintEvent(self, e):
            painter = QPainter(self)
            self.display_counter += 1
            painter.drawImage(QPoint(0, 0), self.img)
        def timerSlot(self):
            print "In timer"
            yuv = array.array('B')
            pix = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=self.getImageBuf())
    
            for i in range(0,self.height):
                for j in range(0, self.width):
                    pix[i, j] = 0
    
            for k in range (0, 10):
                #qApp.processEvents()
                yuv.fromfile(self.f_yuv, 3*self.width*self.height/2)
                for i in range(0, self.height):
                    for j in range(0, self.width):
                        Y_val = yuv[(i*self.width)+j]
                        U_val = yuv[self.width*self.height + ((i/2)*(self.width/2))+(j/2)]
                        V_val = yuv[self.width*self.height + self.width*self.height/4 + ((i/2)*(self.width/2))+(j/2)]
                        C = Y_val - 16
                        D = U_val - 128
                        E = V_val - 128
                        R = (( 298 * C           + 409 * E + 128) >> 8)
                        G = (( 298 * C - 100 * D - 208 * E + 128) >> 8)
                        B = (( 298 * C + 516 * D           + 128) >> 8)
                        if R > 255:
                            R = 255
                        if G > 255:
                            G = 255
                        if B > 255:
                            B = 255
    
                        assert(int(R) < 256)
                        pix[i, j] = (255 << 24 | ((int(R) % 256 )<< 16) | ((int(G) % 256 ) << 8) | (int(B) % 256))
    
                self.repaint()
                print "videowin.display_counter = %d" % videowin.display_counter
    
    
    if __name__ == "__main__":
        try:
            yuv_file_name = sys.argv[1]
            width = int(sys.argv[2])
            height = int(sys.argv[3])
            f_yuv = open(yuv_file_name, "rb")
    
            videoApp = QApplication(sys.argv)
    
            videowin = VideoWin(width, height, f_yuv)
    
            timer = QTimer()
            timer.singleShot(100, videowin.timerSlot)
    
            videowin.show()
            videoApp.exec_()
    
    
            sys.exit(0)
        except NameError:
            print("Name Error : ", sys.exc_info()[1])
        except SystemExit:
            print("Closing Window...")
        except Exception:
            print(sys.exc_info()[1])
    

I have tried a second approach where I have tried a combination of creating a
Signal object which "emits" each decoded RGB image (converted from YUV)as a
signal which is caught by the "updateFrame" method in the displaying class
which displays the received RGB buffer/frame using QPainter.drawImage(...)
method. YUV-to-RGB decode--->Signal(Image buffer) --->updateFrame --->
QPainter.drawImage(...) This also displays only the first image alone although
the slot which catches the signal (getting the image) shows that it is called
as many times as the signal is sent by the YUV->RGB converter/decoder. I have
also tried running the YUV->RGB converter and Video display (calling
drawImage) in seperate threads, but the result is the same.

Please note that in both the cases, I am writing the RGB pixel values directly
into the bit buffer of the QImage object which is part of the VideoWin class
in the code shown (NOTE: the code line pix = np.ndarray(shape=(height, width),
dtype=np.uint32, buffer=videowin.getImageBuf()) which gets the img.bits()
buffer of the QImage class) Also, for this test I am decoding and displaying
only the first 10 frames of the video file. Versions: Python - 2.7, Qt - 4.8.5
using Pyside


Answer: From the docs for
[`array.fromfile()`](https://docs.python.org/2/library/array.html#array.array.fromfile):

> Read n items (as machine values) from the file object f and **append them to
> the end of the array**. [emphasis added]

The example code does not include an offset into the array, and so the first
frame is read over and over again. A simple fix would be to clear the array
before reading the next frame:

    
    
        for k in range (0, 100):
            del yuv[:]
            yuv.fromfile(self.f_yuv, 3*self.width*self.height/2)
    

And note that, to see a difference, you will need to read at least sixty
frames of the test file you linked to, because the first fifty or so are all
the same (i.e. a plain green background).

