Playing media from external device with python-vlc on linux - python

I'm trying to play a media file. This is the example on the python-vlc repository:
import platform
import os
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import vlc
class Player(QtWidgets.QMainWindow):
"""A simple Media Player using VLC and Qt
"""
def __init__(self, master=None):
QtWidgets.QMainWindow.__init__(self, master)
self.setWindowTitle("Media Player")
# Create a basic vlc instance
self.instance = vlc.Instance()
self.media = None
# Create an empty vlc media player
self.mediaplayer = self.instance.media_player_new()
self.create_ui()
self.is_paused = False
def create_ui(self):
"""Set up the user interface, signals & slots
"""
self.widget = QtWidgets.QWidget(self)
self.setCentralWidget(self.widget)
# In this widget, the video will be drawn
if platform.system() == "Darwin": # for MacOS
self.videoframe = QtWidgets.QMacCocoaViewContainer(0)
else:
self.videoframe = QtWidgets.QFrame()
self.palette = self.videoframe.palette()
self.palette.setColor(QtGui.QPalette.Window, QtGui.QColor(0, 0, 0))
self.videoframe.setPalette(self.palette)
self.videoframe.setAutoFillBackground(True)
self.positionslider = QtWidgets.QSlider(QtCore.Qt.Horizontal, self)
self.positionslider.setToolTip("Position")
self.positionslider.setMaximum(1000)
self.positionslider.sliderMoved.connect(self.set_position)
self.positionslider.sliderPressed.connect(self.set_position)
self.hbuttonbox = QtWidgets.QHBoxLayout()
self.playbutton = QtWidgets.QPushButton("Play")
self.hbuttonbox.addWidget(self.playbutton)
self.playbutton.clicked.connect(self.play_pause)
self.stopbutton = QtWidgets.QPushButton("Stop")
self.hbuttonbox.addWidget(self.stopbutton)
self.stopbutton.clicked.connect(self.stop)
self.hbuttonbox.addStretch(1)
self.volumeslider = QtWidgets.QSlider(QtCore.Qt.Horizontal, self)
self.volumeslider.setMaximum(100)
self.volumeslider.setValue(self.mediaplayer.audio_get_volume())
self.volumeslider.setToolTip("Volume")
self.hbuttonbox.addWidget(self.volumeslider)
self.volumeslider.valueChanged.connect(self.set_volume)
self.vboxlayout = QtWidgets.QVBoxLayout()
self.vboxlayout.addWidget(self.videoframe)
self.vboxlayout.addWidget(self.positionslider)
self.vboxlayout.addLayout(self.hbuttonbox)
self.widget.setLayout(self.vboxlayout)
menu_bar = self.menuBar()
# File menu
file_menu = menu_bar.addMenu("File")
# Add actions to file menu
open_action = QtWidgets.QAction("Load Video", self)
close_action = QtWidgets.QAction("Close App", self)
file_menu.addAction(open_action)
file_menu.addAction(close_action)
open_action.triggered.connect(self.open_file)
close_action.triggered.connect(sys.exit)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(100)
self.timer.timeout.connect(self.update_ui)
def play_pause(self):
"""Toggle play/pause status
"""
if self.mediaplayer.is_playing():
self.mediaplayer.pause()
self.playbutton.setText("Play")
self.is_paused = True
self.timer.stop()
else:
if self.mediaplayer.play() == -1:
self.open_file()
return
self.mediaplayer.play()
self.playbutton.setText("Pause")
self.timer.start()
self.is_paused = False
def stop(self):
"""Stop player
"""
self.mediaplayer.stop()
self.playbutton.setText("Play")
def open_file(self):
"""Open a media file in a MediaPlayer
"""
dialog_txt = "Choose Media File"
filename = QtWidgets.QFileDialog.getOpenFileName(self, dialog_txt, os.path.expanduser('~'))
if not filename:
return
# getOpenFileName returns a tuple, so use only the actual file name
self.media = self.instance.media_new(filename[0])
# Put the media in the media player
self.mediaplayer.set_media(self.media)
# Parse the metadata of the file
self.media.parse()
# Set the title of the track as window title
self.setWindowTitle(self.media.get_meta(0))
# The media player has to be 'connected' to the QFrame (otherwise the
# video would be displayed in it's own window). This is platform
# specific, so we must give the ID of the QFrame (or similar object) to
# vlc. Different platforms have different functions for this
if platform.system() == "Linux": # for Linux using the X Server
self.mediaplayer.set_xwindow(int(self.videoframe.winId()))
elif platform.system() == "Windows": # for Windows
self.mediaplayer.set_hwnd(int(self.videoframe.winId()))
elif platform.system() == "Darwin": # for MacOS
self.mediaplayer.set_nsobject(int(self.videoframe.winId()))
self.play_pause()
def set_volume(self, volume):
"""Set the volume
"""
self.mediaplayer.audio_set_volume(volume)
def set_position(self):
"""Set the movie position according to the position slider.
"""
# The vlc MediaPlayer needs a float value between 0 and 1, Qt uses
# integer variables, so you need a factor; the higher the factor, the
# more precise are the results (1000 should suffice).
# Set the media position to where the slider was dragged
self.timer.stop()
pos = self.positionslider.value()
self.mediaplayer.set_position(pos / 1000.0)
self.timer.start()
def update_ui(self):
"""Updates the user interface"""
# Set the slider's position to its corresponding media position
# Note that the setValue function only takes values of type int,
# so we must first convert the corresponding media position.
media_pos = int(self.mediaplayer.get_position() * 1000)
self.positionslider.setValue(media_pos)
# No need to call this function if nothing is played
if not self.mediaplayer.is_playing():
self.timer.stop()
# After the video finished, the play button stills shows "Pause",
# which is not the desired behavior of a media player.
# This fixes that "bug".
if not self.is_paused:
self.stop()
def main():
"""Entry point for our simple vlc player
"""
app = QtWidgets.QApplication(sys.argv)
player = Player()
player.show()
player.resize(640, 480)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
However, when I select a file from my phone connected with USB, I get this warning.
libdvdread: Attempting to use device /dev/sda4 mounted on / for CSS authentication
libdvdread: Could not open input: Permission denied
libdvdread: Can't open /dev/sda4 for reading
libdvdread: Device /dev/sda4 inaccessible, CSS authentication not available.
libdvdread:DVDOpenFilePath:findDVDFile /VIDEO_TS/VIDEO_TS.IFO failed
libdvdread:DVDOpenFilePath:findDVDFile /VIDEO_TS/VIDEO_TS.BUP failed
libdvdread: Can't open file VIDEO_TS.IFO.
libdvdnav: vm: failed to read VIDEO_TS.IFO
Do I have to copy over the files temporarily to play them? Or is there another way?

If the device is mass storage you don't have to copy over but for phones that don't have a feature like that I had to copy over the file before playing. I used shutil for it. In a thread other than the main thread, of course.
import shutil
copied_file = shutil.copyfile(src_filepath, dest_filepath)
self.media = self.instance.media_new(copied_file)

Related

How to put QTableWidget child window in front of parent window or below the parent window?

I am trying to make a simple input form that accepts certain inputs and shows output in a table.
As per the code, I get the following output.
But I am looking for the following output.
Following is the code snippet that I have attempted.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
# Subclass QMainWindow to customize your application's main window
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("Check Distance Travelled By Vehicles")
self.vehicleNamelbl = QLabel('VehicleName : ')
self.vehicleNamecombo = QComboBox()
self.vehicleNamecombo.addItem('SwitftDzire')
self.dateEdit = QDateEdit()
self.dateEdit.__init__(calendarPopup=True)
self.dateEdit.setDateTime(QtCore.QDateTime.currentDateTime())
self.dateEdit.editingFinished.connect(lambda: date_method())
self.petrolCB = QCheckBox('Petrol')
self.petrolCB.setChecked(True)
self.dieselCB = QCheckBox('Diesel')
self.dieselCB.setChecked(False)
self.petrolCB.stateChanged.connect(self.checkpetrolCB)
self.dieselCB.stateChanged.connect(self.checkdieselCB)
self.submitBtn = QPushButton('Submit ')
# adding action to the date when enter key is pressed
self.submitBtn.clicked[bool].connect(self.collecInput)
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(self.vehicleNamelbl,1,0)
grid.addWidget(self.vehicleNamecombo,1,1)
grid.addWidget(self.dateEdit,1,2)
grid.addWidget(self.petrolCB,1,3)
grid.addWidget(self.dieselCB,1,4)
grid.addWidget(self.submitBtn,1,5)
# geometry of the main window
qtRectangle = self.frameGeometry()
# center point of screen
centerPoint = QDesktopWidget().availableGeometry().center()
# move rectangle's center point to screen's center point
qtRectangle.moveCenter(centerPoint)
# top left of rectangle becomes top left of window centering it
self.move(qtRectangle.topLeft())
self.setLayout(grid)
self.show()
# method called by date edit
def date_method():
print('Inside date_method')
# getting the date
value = self.dateEdit.date()
print(value)
print(value.toPyDate())
def checkpetrolCB(self,checked):
if checked :
print('Petrol Vehicle Is Selected')
self.OdFlag = 1
else:
print('Petrol Vehicle Is De-Selected')
def checkdieselCB(self,checked):
if checked :
print('Diesel Vehicle Is Selected')
self.OdFlag = 2
else:
print('Diesel Vehicle Is De-Selected')
def collecInput(self) :
print('Submit Button Pressed!! Inputs Selected')
print(self.vehicleNamecombo.currentText())
print(self.dateEdit.date().toPyDate())
if self.petrolCB.isChecked() == True and self.dieselCB.isChecked() == False :
print('Petrol Vehicle Is Selected')
if self.dieselCB.isChecked() == True and self.petrolCB.isChecked() == False :
print('Diesel Vehicle Is Selected')
if self.petrolCB.isChecked() == True and self.dieselCB.isChecked() == True :
print('Both Petrol And Diesel Vehicle Are Selected')
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Critical)
msgBox.setText('Select Either Petrol Or Diesel')
msgBox.setWindowTitle("Alert PopUp")
returnValue = msgBox.exec_()
return
# Call A Module To Get The List Of Files Present As per The Input
vehicleFileList = [ 'dist_SwitftDzire_CityA.13OCT2020','dist_SwitftDzire_CityB.13OCT2020','dist_SwitftDzire_CityC.13OCT2020']
print('Back to collecInput')
print(vehicleFileList)
print('Num Of Files Found : '+str(len(vehicleFileList)))
numOfFiles = len(vehicleFileList)
if numOfFiles == 0 : # No Files Have Been Detected
print('No Files Found')
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Critical)
msgBox.setText('No Files Found')
msgBox.setWindowTitle("Alert PopUp")
returnValue = msgBox.exec_()
else: # Atleast 1 File Is Detected
print('Populating table entries')
table = MyTable(vehicleFileList, numOfFiles, 2, self)
# add Qt.Window to table's flags
table.setWindowFlags(table.windowFlags() | Qt.Window)
table.show()
class MyTable(QTableWidget):
def __init__(self, vehicleFileList, *args):
QTableWidget.__init__(self, *args)
self.data = vehicleFileList
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.resizeColumnsToContents()
self.resizeRowsToContents()
self.horizontalHeader().setStretchLastSection(False)
self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.setHorizontalHeaderLabels(['Available Vehicle Data Files','Missing Files'])
print('Inside MyTable')
print(vehicleFileList)
rowCount=0
for item in vehicleFileList :
print(item)
self.setItem(rowCount,0,QTableWidgetItem(item))
rowCount+=1
def main():
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What changes do I need to make to get the window positioning of my choice?
Note: The second window is not a child of the first window.
The idea is to calculate the geometry of the first window and use that information to modify the geometry of the second window:
table = MyTable(vehicleFileList, numOfFiles, 2, self)
# add Qt.Window to table's flags
table.setWindowFlags(table.windowFlags() | Qt.Window)
table.resize(self.width(), table.sizeHint().height())
table.window().move(self.geometry().bottomLeft())
table.show()

How can I detect when one window occludes another in PyQt5?

I'm using PyQt5 to create an app with multiple main windows. I want to be able to allow the user to save and load window sizes and window positions. That's easy with, e.g., QMainWindow.saveGeometry() and QMainWindow.loadGeometry() or the corresponding .saveState() and .loadState() variants. These work great for position and size, but if the user moves or resizes one window so that it occludes another, I want to also restore this positioning. I don't mind writing my own code to save the info for each window, but I can't see any way to detect the relative Z order of windows. Am I missing it in the docs, or is this not possible?
To see what I mean, try this:
from PyQt5.QtWidgets import QApplication, QMainWindow, QPlainTextEdit
from PyQt5.QtCore import QSettings
from PyQt5.QtGui import QCloseEvent
'''
context: Linux Mint 19.3 Tricia x86_64
Python 3.9
PyQt5 5.15.1
'''
class RememberWin(QMainWindow):
def __init__(self, win_name: str):
super(RememberWin, self).__init__()
self.win_name = win_name
self.setWindowTitle(win_name)
self.can_close = False
def restore_window(self) -> bool:
try:
settings = QSettings("PyQtExamples", "RememberWinTest")
self.restoreGeometry(settings.value(f'{self.win_name} Geometry'))
self.restoreState(settings.value(f'{self.win_name} State'))
return True
except:
return False
def closeEvent(self, event: QCloseEvent):
if not self.can_close:
event.ignore()
else:
settings = QSettings("PyQtExamples", "RememberWinTest")
settings.setValue(f'{self.win_name} Geometry', self.saveGeometry())
settings.setValue(f'{self.win_name} State', self.saveState())
QMainWindow.closeEvent(self, event)
class ControlWindow(RememberWin):
def __init__(self, win_name: str = "ControlWindow"):
super(ControlWindow, self).__init__(win_name=win_name)
self.can_close = True
self.window1 = RememberWin(win_name='WindowOne')
self.window2 = RememberWin(win_name='WindowTwo')
self.text = QPlainTextEdit(self)
s = "Try making Window1 wide enough to cover Window2.\n" \
"Then close this window (auto closes others).\n" \
"Re-run the app and you'll notice that Window2\n" \
"is not on top of Window1 which means that this\n" \
"info isn't getting saved."
self.text.setPlainText(s)
self.setCentralWidget(self.text)
if not self.restore_window():
self.setGeometry(100, 390, 512, 100)
if not self.window1.restore_window():
self.window1.setGeometry(100, 100, 512, 384)
if not self.window2.restore_window():
self.window2.setGeometry(622, 100, 512, 384)
self.window1.show()
self.window2.show()
def closeEvent(self, event: QCloseEvent):
for win in (self.window1, self.window2):
win.can_close = True
win.close()
super(ControlWindow, self).closeEvent(event)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = ControlWindow(win_name='ControlWindow (You can only close this one)')
window.show()
sys.exit(app.exec_())
The simplest way to do what you want to achieve is to keep track of the current focused widget, or, to be precise, the top level window of the last focused widget.
You can store the focused windows in the settings as a list, using a unique objectName for each window (you are already doing this, so you just need to use setObjectName()), then restore the window by showing them in the correct order as long as the object name matches.
class RememberWin(QMainWindow):
def __init__(self, win_name: str):
super(RememberWin, self).__init__()
self.win_name = win_name
self.setObjectName(win_name)
self.setWindowTitle(win_name)
self.can_close = False
# ...
class ControlWindow(RememberWin):
def __init__(self, win_name: str = "ControlWindow"):
# ...
self.settings = QSettings("PyQtExamples", "RememberWinTest")
self.zOrder = []
QApplication.instance().focusObjectChanged.connect(self.focusChanged)
windowOrder = self.settings.value('windowOrder', type='QStringList')
topLevelWindows = QApplication.topLevelWidgets()
if windowOrder:
for objName in windowOrder:
for win in topLevelWindows:
if win.objectName() == objName:
win.show()
else:
self.window1.show()
self.window2.show()
def focusChanged(self, obj):
if not obj or obj.window() == self.window():
return
if obj.window() in self.zOrder[:-1]:
self.zOrder.remove(obj.window())
self.zOrder.append(obj.window())
def closeEvent(self, event: QCloseEvent):
for win in (self.window1, self.window2):
win.can_close = True
win.close()
self.settings.setValue('windowOrder',
[w.window().objectName() for w in self.zOrder])
super(ControlWindow, self).closeEvent(event)

Impossible to save the size and the position of my windows on Pyqt5

When I move or/and resize my windows on my screen anyway , I would like to keep this parameters for the next time that I will run my application, but impossible.
I open a new issue because I don't see in the pyqt5 section what I've posted here
I'm on Linux (Manjaro) using PyCharm with the lastest PyQT5, Python and QT5 on a kernel 5.1.18-1.
I have tried several things like this : https://github.com/baoboa/pyqt5/blob/master/examples/mainwindows/application/application.py#L197-L207
I have not used the QDesktopWidget class : it seems to be depreciated : QDesktopWidget class
I'm on Linux (Manjaro) using PyCharm with the lastest PyQT5, Python and QT5 on a kernel 5.1.18-1.
#check if the hidden project folder is created by default, if not it is created
HOME_PATH = os.path.expanduser("~")
USER_PATH = os.path.join(HOME_PATH, ".mx5000")
if not os.path.exists(USER_PATH):
os.mkdir(USER_PATH)
#configFolder = os.path.join(QDir.homePath(), ".mx5000")
#configFile = os.path.join(QDir.homePath(), ".mx5000/config.conf")
#configLog = os.path.join(QDir.homePath(), ".mx5000/logfile")
config_path = os.path.join(USER_PATH, "config.ini")
#settings = QSettings()
# config = configparser.ConfigParser()
# config['DEFAULT'] = {'path_keyboard': "/dev/hiddev0",
# 'keyboard_name': "Whatever",
# 'server_type': "self.ui.rdbimap.setChecked(True)",
# 'server_adress': "self.ui.lneserveradress.setText(imap.yourserver.com)",
# 'username': 'username',
# 'password': "cnffjbep",
# 'time_to_check': '10',
# 'keyboard_beep': '1',
# 'play_sound': '1',
# 'sound_directory': "/home/user/.mx5000/notify.ogg"}
#if not os.path.isfile("config"):
#os.mkdir(USER_PATH)
# with open('config.ini', 'w') as configfile:
config.write(configfile)
class Mx5000(QDialog):
def __init__(self, parent=None):
super(Mx5000, self).__init__(parent)
self.setupUi()
self.connectActions()
self.dirty = False
self.loadSettings()
# self.default_size = QSize(615, 800)
# self.default_position = QPoint(20, 20)
# self.ui.lneserveradress.setText('imap.yourserver.com')
# settings = QSettings()
# pos = settings.value("pos", QPoint(200, 200))
# size = settings.value("size", QSize(615, 800))
# self.resize(size)
# self.move(pos)
#===============================================================================
...
def closeEvent(self, event):
if self.okToContinue():
# settings = QSettings()
# pass
self.writeSettings()
event.accept()
else:
event.ignore()
#====================================================================================================================
def loadSettings(self):
settings = QSettings('Exemple app', 'MX5000')
pos = settings.value("pos", QPoint(200, 200))
size = settings.value("size", QSize(615, 800))
self.resize(size)
self.move(pos)
def writeSettings(self):
settings = QSettings('Exemple app', 'MX5000')
settings.setValue("pos", self.pos())
settings.setValue("size", self.size())
#====================================================
# settings.setValue("size", QSize(615, 800).tosize())
# settings.setValue("pos", QPoint(200, 200).toPoint())
#====================================================
settings.sync()
#===================================================================================================================
def okToContinue(self):
if self.dirty is True:
reply = QMessageBox.question(self, self.tr("MX 5000", "Did you want to close the application ?"),
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
if reply == QMessageBox.Cancel:
return False
elif reply == QMessageBox.Yes:
self.Mx5000.close()
self.application.quit()
self.writeSettings()
return True
The entire files (Python and ui) are available if it is not clear for a while here :
python file
and
ui file
By Advance Thanks a lot.
Your logic is a bit strange, each method / function must have a specific task, in the case of okToContinue() method I guess you should only verify if the user accepts the closing or not of the window if a flag was activated otherwise it is considered that I accept the close, it is not necessary to add more code.
Considering the above the solution is:
class Mx5000(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Mx5000, self).__init__(parent)
self.setupUi()
self.dirty = False
self.loadSettings()
def setupUi(self):
self.ui = Ui_MX5000()
self.ui.setupUi(self)
# ...
def closeEvent(self, event):
if self.okToContinue():
self.writeSettings()
event.accept()
else:
event.ignore()
def loadSettings(self):
settings = QtCore.QSettings("Exemple app", "MX5000")
pos = settings.value("pos", QtCore.QPoint(200, 200))
size = settings.value("size", QtCore.QSize(615, 800))
self.resize(size)
self.move(pos)
def writeSettings(self):
settings = QtCore.QSettings("Exemple app", "MX5000")
settings.setValue("pos", self.pos())
settings.setValue("size", self.size())
def okToContinue(self):
if self.dirty:
reply = QtWidgets.QMessageBox.question(
self,
self.tr("MX 5000", "Did you want to close the application ?"),
QtWidgets.QMessageBox.Yes
| QtWidgets.QMessageBox.No
| QtWidgets.QMessageBox.Cancel,
)
return reply == QtWidgets.QMessageBox.Yes
return True

PyQt5 retrieving frames before/after current frame

I'm new to PyQt and I'm trying to create a videoplayer. When the user screenshots a certain frame, I should be able to retrieve 5 frames prior to the current frame and another 5 frames after the current frame. So far I'm only able to retrieve the current frame and I'm having a hard time figuring out which part of the code to alter to store the previous frames and the after frames. Here's the complete code:
# PyQt5 Video player
#!/usr/bin/env python
from PyQt5.QtCore import QDir, Qt, QUrl, pyqtSignal, QPoint, QRect, QObject
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer, QVideoFrame, QAbstractVideoSurface, QAbstractVideoBuffer, QVideoSurfaceFormat
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLabel,
QPushButton, QSizePolicy, QSlider, QStyle, QVBoxLayout, QWidget)
from PyQt5.QtWidgets import QMainWindow,QWidget, QPushButton, QAction
from PyQt5.QtGui import QIcon, QPainter, QImage
import sys
import os
import os.path as osp
class VideoFrameGrabber(QAbstractVideoSurface):
frameAvailable = pyqtSignal(QImage)
def __init__(self, widget: QWidget, parent: QObject):
super().__init__(parent)
self.widget = widget
def supportedPixelFormats(self, handleType):
return [QVideoFrame.Format_ARGB32, QVideoFrame.Format_ARGB32_Premultiplied,
QVideoFrame.Format_RGB32, QVideoFrame.Format_RGB24, QVideoFrame.Format_RGB565,
QVideoFrame.Format_RGB555, QVideoFrame.Format_ARGB8565_Premultiplied,
QVideoFrame.Format_BGRA32, QVideoFrame.Format_BGRA32_Premultiplied, QVideoFrame.Format_BGR32,
QVideoFrame.Format_BGR24, QVideoFrame.Format_BGR565, QVideoFrame.Format_BGR555,
QVideoFrame.Format_BGRA5658_Premultiplied, QVideoFrame.Format_AYUV444,
QVideoFrame.Format_AYUV444_Premultiplied, QVideoFrame.Format_YUV444,
QVideoFrame.Format_YUV420P, QVideoFrame.Format_YV12, QVideoFrame.Format_UYVY,
QVideoFrame.Format_YUYV, QVideoFrame.Format_NV12, QVideoFrame.Format_NV21,
QVideoFrame.Format_IMC1, QVideoFrame.Format_IMC2, QVideoFrame.Format_IMC3,
QVideoFrame.Format_IMC4, QVideoFrame.Format_Y8, QVideoFrame.Format_Y16,
QVideoFrame.Format_Jpeg, QVideoFrame.Format_CameraRaw, QVideoFrame.Format_AdobeDng]
def isFormatSupported(self, format):
imageFormat = QVideoFrame.imageFormatFromPixelFormat(format.pixelFormat())
size = format.frameSize()
return imageFormat != QImage.Format_Invalid and not size.isEmpty() and \
format.handleType() == QAbstractVideoBuffer.NoHandle
def start(self, format: QVideoSurfaceFormat):
imageFormat = QVideoFrame.imageFormatFromPixelFormat(format.pixelFormat())
size = format.frameSize()
if imageFormat != QImage.Format_Invalid and not size.isEmpty():
self.imageFormat = imageFormat
self.imageSize = size
self.sourceRect = format.viewport()
super().start(format)
self.widget.updateGeometry()
self.updateVideoRect()
return True
else:
return False
def stop(self):
self.currentFrame = QVideoFrame()
self.targetRect = QRect()
super().stop()
self.widget.update()
def present(self, frame):
if frame.isValid():
cloneFrame = QVideoFrame(frame)
cloneFrame.map(QAbstractVideoBuffer.ReadOnly)
image = QImage(cloneFrame.bits(), cloneFrame.width(), cloneFrame.height(),
QVideoFrame.imageFormatFromPixelFormat(cloneFrame.pixelFormat()))
self.frameAvailable.emit(image) # this is very important
cloneFrame.unmap()
if self.surfaceFormat().pixelFormat() != frame.pixelFormat() or \
self.surfaceFormat().frameSize() != frame.size():
self.setError(QAbstractVideoSurface.IncorrectFormatError)
self.stop()
return False
else:
self.currentFrame = frame
self.widget.repaint(self.targetRect)
return True
def updateVideoRect(self):
size = self.surfaceFormat().sizeHint()
size.scale(self.widget.size().boundedTo(size), Qt.KeepAspectRatio)
self.targetRect = QRect(QPoint(0, 0), size)
self.targetRect.moveCenter(self.widget.rect().center())
def paint(self, painter):
if self.currentFrame.map(QAbstractVideoBuffer.ReadOnly):
oldTransform = self.painter.transform()
if self.surfaceFormat().scanLineDirection() == QVideoSurfaceFormat.BottomToTop:
self.painter.scale(1, -1)
self.painter.translate(0, -self.widget.height())
image = QImage(self.currentFrame.bits(), self.currentFrame.width(), self.currentFrame.height(),
self.currentFrame.bytesPerLine(), self.imageFormat)
self.painter.drawImage(self.targetRect, image, self.sourceRect)
self.painter.setTransform(oldTransform)
self.currentFrame.unmap()
class VideoWindow(QMainWindow):
def __init__(self, parent=None):
super(VideoWindow, self).__init__(parent)
self.setWindowTitle("PyQt Video Player Widget")
self.counter = 0
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.videoWidget = QVideoWidget()
self.videoFrame = QVideoFrame()
self.playButton = QPushButton()
self.playButton.setEnabled(False)
self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playButton.clicked.connect(self.play)
self.positionSlider = QSlider(Qt.Horizontal)
self.positionSlider.setRange(0, 0)
self.positionSlider.sliderMoved.connect(self.setPosition)
self.errorLabel = QLabel()
self.errorLabel.setSizePolicy(QSizePolicy.Preferred,
QSizePolicy.Maximum)
# Create new action
openAction = QAction(QIcon('open.png'), '&Open', self)
openAction.setShortcut('Ctrl+O')
openAction.setStatusTip('Open video')
openAction.triggered.connect(self.openFile)
# Create exit action
exitAction = QAction(QIcon('quit.png'), '&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.exitCall)
screenshotAction = QAction(QIcon('screenshot.png'), '&Screenshot', self)
screenshotAction.setShortcut('Ctrl+S')
screenshotAction.setStatusTip('Screenshot scenes')
screenshotAction.triggered.connect(self.screenshotCall)
# Create menu bar and add action
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
#fileMenu.addAction(newAction)
fileMenu.addAction(openAction)
fileMenu.addAction(screenshotAction)
fileMenu.addAction(exitAction)
# Create a widget for window contents
wid = QWidget(self)
self.setCentralWidget(wid)
# Create layouts to place inside widget
controlLayout = QHBoxLayout()
controlLayout.setContentsMargins(0, 0, 0, 0)
controlLayout.addWidget(self.playButton)
controlLayout.addWidget(self.positionSlider)
layout = QVBoxLayout()
layout.addWidget(self.videoWidget)
layout.addLayout(controlLayout)
layout.addWidget(self.errorLabel)
# Set widget to contain window contents
wid.setLayout(layout)
self.mediaPlayer.setVideoOutput(self.videoWidget)
self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
self.mediaPlayer.positionChanged.connect(self.positionChanged)
self.mediaPlayer.durationChanged.connect(self.durationChanged)
self.mediaPlayer.error.connect(self.handleError)
def openFile(self):
fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie",
QDir.homePath())
self.path = osp.dirname(str(fileName))
if fileName != '':
self.mediaPlayer.setMedia(
QMediaContent(QUrl.fromLocalFile(fileName)))
self.playButton.setEnabled(True)
def exitCall(self):
sys.exit(app.exec_())
def screenshotCall(self):
#Call video frame grabber
self.grabber = VideoFrameGrabber(self.videoWidget, self)
self.mediaPlayer.setVideoOutput(self.grabber)
self.mediaPlayer.pause()
self.grabber.frameAvailable.connect(self.process_frame)
self.errorLabel.setText("Taking a screenshot of image "+str(self.counter)+" ....")
self.mediaPlayer.play()
self.mediaPlayer.setVideoOutput(self.videoWidget)
def play(self):
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
self.mediaPlayer.pause()
else:
self.mediaPlayer.play()
def mediaStateChanged(self, state):
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
self.playButton.setIcon(
self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playButton.setIcon(
self.style().standardIcon(QStyle.SP_MediaPlay))
def positionChanged(self, position):
self.positionSlider.setValue(position)
def durationChanged(self, duration):
self.positionSlider.setRange(0, duration)
def setPosition(self, position):
self.mediaPlayer.setPosition(position)
def handleError(self):
self.playButton.setEnabled(False)
self.errorLabel.setText("Error: " + self.mediaPlayer.errorString())
def process_frame(self, image):
# Save image here
filename = "screenshot" + str(self.counter).zfill(6)
self.path = 'C:/Desktop/temp'
image.save(self.path+'/{}.png'.format(str(filename)))
self.counter = self.counter+1
if __name__ == '__main__':
app = QApplication(sys.argv)
player = VideoWindow()
player.resize(720, 480)
player.show()
sys.exit(app.exec_())
I'm thinking of retrieving the current frame number and creating loops to retrieve the before and after frames and then send them to process_frame for saving, but I'm not sure how to implement that because as of now, I'm still having a hard time grasping the principles behind the video frame grabber.
You have a few options.
First is to seek the video to the frames you need using QMediaPlayer.setPosition, then use the grabber you have already implemented to grab the current frame. Then you'd restore the position.
In reality, this is difficult to implement, since you need to know precise frame times, which is something that PyQT doesn't offer as part of their API.
To obtain frame times, you could use FFMS2 library and its Python bindings:
import ffms
source_file = "test/x264.mkv"
vsource = ffms.VideoSource(source_file)
print("Frames:", vsource.properties.NumFrames)
print("Times:", vsource.track.timecodes)
This is where the other solution kicks in. If you use FFMS2 anyway, why even use the media player to obtain the frames? You can just use FFMS2 directly:
>>> frame = vsource.get_frame(0)
>>> frame.EncodedWidth, frame.EncodedHeight
(128, 72)
>>> frame.planes[0]
array([16, 16, 16, ..., 16, 16, 16], dtype=uint8)
You input the frame number and receive a numpy array that you can convert into an actual image with library such as PIL or scikit-image. So you only need to convert current stream position to the frame number. To do it, you can query the vsource.track.timecodes variable and find the index of the closest frame number. To do it fast, you can use divide-and-conquer technique (the builtin Python bisect module).
Note that you should load the ffms.VideoSource once, when you select the video, since it may take a bit of time to index the time and frame information and you don't want the UI to lag when the user chooses to do a screenshot. To further speed up things, you could also cache the indexing information, if you need to reopen the file often. Manual indexing is covered in FFMS2 documentation.

PyQt: Call a TrayMinimized application

I have an application wich is minimized to the tray (showing an icon) when the user close it. What I need to know is how can I call it back with a combination of keys, like Ctrl+Alt+Something. Actually I call it back when I double-click it, but it will be nice to do the same on a keystroke. Here is a portion of the code:
# -*- coding: utf-8 -*-
"""The user interface for our app"""
import os,sys
import ConfigParser
# Import Qt modules
from PyQt4 import QtCore,QtGui
# Import the compiled UI module
from octo import Ui_Form
CFG_PATH = "etc/config.list" #Config File Path
#config.list vars DEFAULT Values
ClipCount = 8
Static = ""
window = None
# Create a class for our main window
class Main(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
# This is always the same
self.ui=Ui_Form()
self.ui.setupUi(self)
# Window Icon
icon = QtGui.QIcon("SSaver.ico")
self.setWindowIcon(icon)
self.setWindowTitle("Octopy")
# Set the timer =)
self.timer = self.startTimer(1000) #self.killTimer(self.timer)
# Clipboard Counter
self.counter = 0
#Last trapped clipboard
self.LastClip = ""
self.tentacles = [""] * 8
self.cmd = []
self.cmd.append(self.ui.cmd_1)
self.cmd.append(self.ui.cmd_2)
self.cmd.append(self.ui.cmd_3)
self.cmd.append(self.ui.cmd_4)
self.cmd.append(self.ui.cmd_5)
self.cmd.append(self.ui.cmd_6)
self.cmd.append(self.ui.cmd_7)
self.cmd.append(self.ui.cmd_8)
## Events ##
def on_cmd_8_pressed(self): #Clear
for i in range(0,7):
self.tentacles[i] = ""
self.cmd[i].setText(self.tentacles[i])
def on_cmd_1_pressed(self):
t = self.ui.cmd_1.text()
self.setClp(t)
def on_cmd_2_pressed(self):
t = self.ui.cmd_2.text()
self.setClp(t)
def on_cmd_3_pressed(self):
t = self.ui.cmd_3.text()
self.setClp(t)
def on_cmd_4_pressed(self):
t = self.ui.cmd_4.text()
self.setClp(t)
def on_cmd_5_pressed(self):
t = self.ui.cmd_5.text()
self.setClp(t)
def on_cmd_6_pressed(self):
t = self.ui.cmd_6.text()
self.setClp(t)
def on_cmd_7_pressed(self):
t = self.ui.cmd_7.text()
self.setClp(t)
def hideEvent(self,event): # Capture close and minimize events
pass
def keyPressEvent(self,ev):
if ev.key() == 16777216:
self.hide()
def showEvent(self,ev):
self.fillClp()
def timerEvent(self,ev):
c = self.getClp()
if c:
#print c, self.counter
self.tentacles[self.counter] = c
if self.counter < 7:
self.counter += 1
else:
self.counter = 0
self.fillClp()
## Functions ##
def fillClp(self):
for i in range(0,7):
self.cmd[i].setText(self.tentacles[i])
def getClp(self):
clp = QtGui.QApplication.clipboard()
c = clp.text()
if self.LastClip != c:
self.LastClip = c
return c
else:
return None
def setClp(self, t):
clp = QtGui.QApplication.clipboard()
clp.setText(t)
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
# Actions
self.action_quit = QtGui.QAction("Quit", self)
self.action_about = QtGui.QAction("About Octopy", self)
# Add actions to menu
menu.addAction(self.action_about)
menu.addSeparator()
menu.addAction(self.action_quit)
# Connect menu with signals
self.connect(self.action_about, QtCore.SIGNAL("triggered()"), self.about)
self.connect(self.action_quit, QtCore.SIGNAL("triggered()"), self.quit)
# Other signals
traySignal = "activated(QSystemTrayIcon::ActivationReason)"
QtCore.QObject.connect(self, QtCore.SIGNAL(traySignal), self.icon_activated)
# Create Menu
self.setContextMenu(menu)
def quit(self):
w = QtGui.QWidget()
reply = QtGui.QMessageBox.question(w, 'Confirm Action',"Are you sure to quit?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
QtGui.QApplication.quit()
def about(self):
w = QtGui.QWidget()
QtGui.QMessageBox.information(w, 'About', "Octopy Multi-Clipboard Manager\n Developed by mRt.")
def icon_activated(self, reason):
if reason == QtGui.QSystemTrayIcon.DoubleClick:
window.show()
else:
print "otro"
def main():
# Again, this is boilerplate, it's going to be the same on
# almost every app you write
app = QtGui.QApplication(sys.argv)
# TrayIcon
w = QtGui.QWidget()
icon = QtGui.QIcon("SSaver.ico")
trayIcon = SystemTrayIcon(icon, w)
trayIcon.show()
trayIcon.setToolTip("Octopy Multi-Clipboard Manager")
# Main Window
global window
window=Main()
window.show()
window.setWindowTitle("Octopy")
app.setQuitOnLastWindowClosed(0)
sys.exit(app.exec_())
def readIni():
cfg = ConfigParser.ConfigParser()
cfg.read(CFG_PATH)
ClipCount = int(cfg.get("Other","ClipCount"))
Static = cfg.get("Other","Static")
clip = [""] * int(ClipCount+1)
if __name__ == "__main__":
readIni()
main()
The complete program is hosted on google: http://code.google.com/p/octopys/downloads/list
For a keystroke to be handled by your application when it does not have keyboard focus, you need to install a global shortcut. Qt doesn't support this, but Qxt, a Qt extension library, does. See
http://doc.libqxt.org/0.5.0/classQxtGlobalShortcut.html. I don't know if PyQt bindings exist for Qxt.

Categories

Resources