Related
Summary:
I am trying to make a pyqt5 UI that reads in a dictionary from a json file and dynamically creates an editable form. I would then like to be able to change the json file and have my form update.
What I've tried:
I think the simplest solution would be just to somehow destroy the ui and re-initialize the ui with a new dictionary, I have tried to understand this post, but I am not to sure how to modify this answer to reboot and then instantiate a new UI class with a new dictionary?
I have also read a few posts like this post which I think I can use to first delete all my widgets, then delete my layout and then re add new widgets and layouts from a new dictionary, but I wonder if this is just over complicating the problem?
Some example code:
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.addLayout(self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
if __name__ == "__main__":
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary1)
MainWindow.show()
sys.exit(test_app.exec_())
I suppose I am at the stage of learning where I probably don't know exactly the right questions to ask or how to describe terminology correctly, so I hope this makes sense.
As long as you're using a QFormLayout, you can consider using removeRow(), and do that before adding the new widgets (even the first time). Note that the layout has to be created outside that function, so that you can always reuse it as needed.
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QFormLayout,
QMainWindow, QLineEdit, QPushButton)
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary.copy()
self.setWindowTitle("Main Window")
central = QWidget(self)
self.setCentralWidget(central)
self.main_layout = QVBoxLayout(central)
self.form_layout = QFormLayout()
self.main_layout.addLayout(self.form_layout)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
while self.form_layout.rowCount():
self.form_layout.removeRow(0)
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.form_layout.addRow(key, self.dynamic_dictionary[key])
QApplication.processEvents()
self.adjustSize()
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
self.createDynamicForm()
As suggested in the comments, I tried out the Qtable widget. In this script the table builder refreshes, removes all table rows and then builds the table from a dictionary.
It builds the first table as a show event, although I don't think that's actually needed in the end. I thought it might be a way to have the table rebuild upon closing and reopening the window as suggested in the comments too. In the end the refresh button is enough on it's own I think, without closing the window.
The research I did was from a training video by Chris Zurbrigg on the the Table widget in pyside2.
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QMainWindow, QLineEdit, QPushButton,
QTableWidget, QTableWidgetItem, QHeaderView)
class TableEditUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('table edit')
self.setMinimumWidth(500)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.create_widgets()
self.create_layouts()
self.toggle = True
self.set_dictionary()
self.refresh_table()
self.create_connections()
def create_widgets(self):
self.table = QTableWidget()
self.table.setColumnCount(2)
self.table.setHorizontalHeaderLabels(["Attribute", "Data"])
header_view = self.table.horizontalHeader()
header_view.setSectionResizeMode(1, QHeaderView.Stretch)
self.toggle_dictionary_btn = QPushButton('toggle dictionary')
def refresh_table(self):
self.table.setRowCount(0)
self.dynamic_dictionary = {}
for count, (key, value) in enumerate(self.dictionary.items()):
print(count, key, value)
self.dynamic_dictionary[key] = QLineEdit(value)
self.table.insertRow(count)
self.insert_item(count, 0, str(key))
self.table.setCellWidget(count, 1, self.dynamic_dictionary[key])
def insert_item(self, row, column, text):
item = QTableWidgetItem(text)
self.table.setItem(row, column, item)
def showEvent(self, e):
super(TableEditUI, self).showEvent(e)
self.refresh_table
def create_layouts(self):
self.main_layout = QVBoxLayout(self.main_widget)
self.main_layout.addWidget(self.table)
self.main_layout.addWidget(self.toggle_dictionary_btn)
def create_connections(self):
self.toggle_dictionary_btn.clicked.connect(self.set_dictionary)
def set_dictionary(self):
self.toggle = not self.toggle
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
if self.toggle:
self.dictionary = dictionary1
else:
self.dictionary = dictionary2
self.refresh_table()
if __name__ == "__main__":
app = QApplication(sys.argv)
TableEditUIWindow = TableEditUI()
TableEditUIWindow.show()
sys.exit(app.exec())
Solution 1
I modify your codes in a way that delete the self.dynamiclayout and rebuild it. Just by using the link you have posted.
You just need to add a call of deleteItemsOfLayout(self.dynamiclayout) and then self.createDynamicForm() in your updateDictionary() function. Another small modification is changing self.main_layout.addLayout(self.dynamiclayout) in your createDynamicForm() function to self.main_layout.insertLayout(0, self.dynamiclayout) in order to keep the order of elements in the self.main_layout.
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
# copy from https://stackoverflow.com/a/45790404/9758790
def deleteItemsOfLayout(layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.setParent(None)
else:
deleteItemsOfLayout(item.layout())
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.insertLayout(0, self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
deleteItemsOfLayout(self.dynamiclayout)
self.createDynamicForm()
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
if __name__ == "__main__":
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary1)
MainWindow.show()
sys.exit(test_app.exec_())
Solution 2
You can also re-build the whole test_app. However,
In this way I think you must make the dictionary a global varibal instead of a property of the class JsonEditor. To be honest, I think this is not elegant.
In addition, the restart of the whole application may be unnecessary, perhaps you just need to close() the window and create a new one as suggested by #musicamante. I am not sure how to create it again. I wonder whether it's possible for a widget to close() itself and rebuild itself again, if not, you need to add the JsonEditor to parent widget and close/rebuild the JsonEditor there. I think it will be troublesome.
Note that the window will "flash"(disappear and appear again) in the solution 2.
import sys
from PyQt5.QtWidgets import QWidget, qApp
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.addLayout(self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
global dictionary
dictionary = dictionary2
qApp.exit(EXIT_CODE_REBOOT)
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
dictionary = dictionary1
EXIT_CODE_REBOOT = -11231351
if __name__ == "__main__":
exitCode = 0
while True:
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary)
MainWindow.show()
exitCode = test_app.exec_()
test_app = None
if exitCode != EXIT_CODE_REBOOT:
break
In fact, this is my first time to try to restart a Qt GUI. I referred to this answer, however, it didn't work directly as I stated in the comment below it. This answer works fine and is adopted in my answer. I also tried this answer, however, the application configuration stayed unchanged if using this implementation.
I need to make a new window for video output. It should have some buttons (which I'll add later), and a video output. I have the main window with audio playback capability, and I can make a video output on the main window, not the second one.
The video check using magic.python executes videopop, which opens a new window (VideoWindow).
Code:
file: main.py
import sys
import magic
# bude obsahovat menu (File, Edit, About,...), Queue, v menu bude historie
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QPalette, QColor, QIcon
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QFileDialog, QWidget, QLabel, QVBoxLayout, QHBoxLayout, \
QPushButton, QStyle, QSlider
from ui import UI
class QueueWin(QMainWindow, UI):
def __init__(self, *args, **kwargs):
super(QueueWin, self).__init__(*args, **kwargs)
self.ui(self)
self.setWindowTitle('MPx') # nastavi title
self.setWindowIcon(QIcon('media-player-5.ico')) # nastavi ikonku
p = self.palette()
p.setColor(QPalette.Window, QColor(52, 51, 51)) # nastavi barvu okna
self.setPalette(p) # aplikuje barvu
self.open_file_act.triggered.connect(self.open_file)
#self.history_act.triggered.connect(self.open_history)
self.history_act.triggered.connect(self.videopop)
#self.open_act.triggered.connect(self.clr_history)
self.mediaPlayer.stateChanged.connect(self.mediastate_changed) #mení ikonku na tlačítku play
self.mediaPlayer.positionChanged.connect(self.update_position)
self.mediaPlayer.durationChanged.connect(self.duration_changed)
self.slider.valueChanged.connect(self.mediaPlayer.setPosition)
self.audioSlider.valueChanged.connect(self.mediaPlayer.setVolume)
def open_history (self):
h = History(self)
h.show()
def videopop(self):
v = VideoWindow(self)
v.show()
def credits(self):
w = Credits(self)
w.show()
def open_file(self):
filename, _ = QFileDialog.getOpenFileName(self, "Open Video")
if filename != '':
self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename)))
self.playBtn.setEnabled(True)
self.mediaPlayer.play()
mime = magic.Magic(mime=True) # check zda je soubor video
videocheck = mime.from_file(filename)
if videocheck.find('video') != -1:
self.videopop()
print('it is video')
def position_changed(self, position):
self.slider.setValue(position)
def duration_changed(self,duration):
self.slider.setRange(0, duration)
def update_position(self, position):
self.slider.blockSignals(True)
self.slider.setValue(position)
self.slider.blockSignals(False)
def play_video(self):
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
self.mediaPlayer.pause()
else:
self.mediaPlayer.play()
def mediastate_changed(self, state):
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
class VideoWindow(QMainWindow, UI):
def __init__(self, *args, **kwargs):
super(VideoWindow, self).__init__(*args, **kwargs)
self.videoui(self)
self.setWindowTitle('Video') #nastavi title
self.setWindowIcon(QIcon('media-player-5.ico')) #nastavi ikonku
# http://www.iconseeker.com/search-icon/isabi/media-player-5.html <-- odkaz na ikonku
self.setGeometry(710, 290, 500, 500) #xMistoOtevreni, yMistoOtevreni, xVelikost, yVelikost
self.setMinimumSize(400, 400) # xMinimalniVelikost, yMinimalniVelikost
p = self.palette()
p.setColor(QPalette.Window, QColor(52, 51, 51)) #nastavi barvu okna
self.setPalette(p) #aplikuje barvu
class History(QMainWindow):
def __init__(self, parent=None):
super(History, self).__init__(parent)
self.setWindowTitle('Historie') # nastavi title
self.setWindowIcon(QIcon('media-player-5.ico')) # nastavi ikonku
# http://www.iconseeker.com/search-icon/isabi/media-player-5.html <-- odkaz na ikonku
self.setGeometry(710, 290, 500, 200) # xMistoOtevreni, yMistoOtevreni, xVelikost, yVelikost
self.setMinimumSize(200, 200) # xMinimalniVelikost, yMinimalniVelikost
p = self.palette()
p.setColor(QPalette.Window, QColor(52, 51, 51)) # nastavi barvu okna
self.setPalette(p) # aplikuje barvu
class Credits(QMainWindow):
def __init__(self, parent=None):
super(Credits, self).__init__(parent)
self.setWindowTitle('O programu') # nastavi title
self.setWindowIcon(QIcon('media-player-5.ico')) # nastavi ikonku
# http://www.iconseeker.com/search-icon/isabi/media-player-5.html <-- odkaz na ikonku
self.setGeometry(710, 290, 500, 200) # xMistoOtevreni, yMistoOtevreni, xVelikost, yVelikost
self.setMinimumSize(200, 200) # xMinimalniVelikost, yMinimalniVelikost
p = self.palette()
p.setColor(QPalette.Window, QColor(52, 51, 51)) # nastavi barvu okna
self.setPalette(p) # aplikuje barvu
self.label = QLabel('Autor: Tomáš Gabriel, 3.B OAUH<br>Napsáno pomocí Pythonu a PyQt5', self)
self.label.setStyleSheet("color: yellow")
self.label.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = QueueWin()
mainWin.show()
sys.exit(app.exec_())
file: UI.py
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtMultimedia import QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import QPushButton, QSlider, QStyle, QHBoxLayout, QVBoxLayout, QMenuBar
class UI(object):
def ui(self, QueueWin):
QueueWin.setObjectName("QueueWin")
QueueWin.resize(600, 500)
_translate = QtCore.QCoreApplication.translate
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.centralWidget = QtWidgets.QWidget(QueueWin)
#sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
#sizePolicy.setHorizontalStretch(0)
#sizePolicy.setVerticalStretch(0)
#sizePolicy.setHeightForWidth(self.centralWidget.sizePolicy().hasHeightForWidth())
#self.centralWidget.setSizePolicy(sizePolicy)
self.openFileBtn = QPushButton("Otevrit soubor...", self.centralWidget)
self.openFileBtn.clicked.connect(self.open_file)
self.playBtn = QPushButton(self.centralWidget) # vytvori tlacitko "play"
self.playBtn.setEnabled(False)
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playBtn.clicked.connect(self.play_video)
self.slider = QSlider(Qt.Horizontal, self.centralWidget) # vytvori slider
self.slider.setRange(0, 0)
self.audioSlider = QSlider(Qt.Horizontal, self.centralWidget)
self.audioSlider.setMaximum(100)
self.audioSlider.setProperty("value", 100)
QueueWin.setCentralWidget(self.centralWidget)
# self.queue = QListView(self)
# self.queue.setAcceptDrops(True)
# self.queue.setAlternatingRowColors(True)
self.hbox = QHBoxLayout(self.centralWidget) # umisti tlacitka, slidery,... do UI
self.hbox.setContentsMargins(11, 11, 11, 11)
self.hbox.addWidget(self.openFileBtn)
self.hbox.addWidget(self.playBtn)
self.hbox.addWidget(self.slider)
self.hbox.addWidget(self.audioSlider)
self.vbox = QVBoxLayout(self.centralWidget)
#self.vbox.addWidget(videowidget)
# vbox.addWidget(self.queue)
self.vbox.addLayout(self.hbox)
#self.mediaPlayer.setVideoOutput(videowidget)
#self.mediaPlayer.positionChanged.connect(self.update_position)
#self.mediaPlayer.durationChanged.connect(self.duration_changed)
self.menuBar = QMenuBar(QueueWin)
QueueWin.setMenuBar(self.menuBar)
self.open = QtWidgets.QMenu(self.menuBar)
self.open_file_act = QtWidgets.QAction(QueueWin)
self.open.addAction(self.open_file_act)
self.open_file_act.setText(_translate("QueueWin", "Otevřít..."))
self.menuBar.addAction(self.open.menuAction())
self.open.setTitle(_translate("QueueWin", "Soubor"))
self.history = QtWidgets.QMenu(self.menuBar)
self.history_act = QtWidgets.QAction(QueueWin)
self.history.addAction(self.history_act)
self.history_act.setText(_translate("QueueWin", "Otevřít historii"))
self.menuBar.addAction(self.history.menuAction())
self.history.setTitle(_translate("QueueWin", "Historie"))
self.historyClr_act = QtWidgets.QAction(QueueWin)
self.history.addAction(self.historyClr_act)
self.historyClr_act.setText(_translate("QueueWin", "Vymazat historii"))
self.historyClr_act.setShortcut('ALT+H')
#about = self.menuBar.addMenu('Autor')
#about_act = QAction('O autorovi...', self)
#about_act.setShortcut('CTRL+A')
#about_act.triggered.connect(lambda: self.credits())
#about.addAction(about_act)
QtCore.QMetaObject.connectSlotsByName(QueueWin)
def videoui(self, VideoWindow):
VideoWindow.setObjectName("QueueWin")
VideoWindow.resize(600, 500)
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.centralWidget = QtWidgets.QWidget(VideoWindow)
self.videowidget = QVideoWidget(self.centralWidget)
VideoWindow.setCentralWidget(self.centralWidget)
self.hbox = QHBoxLayout(self.centralWidget) # umisti tlacitka, slidery,... do UI
self.hbox.setContentsMargins(11, 11, 11, 11)
self.hbox.addWidget(self.videowidget)
self.vbox = QVBoxLayout(self.centralWidget)
self.vbox.addLayout(self.hbox)
QtCore.QMetaObject.connectSlotsByName(VideoWindow)
You're not setting the video output, and the QMediaPlayer in VideoWindow is completely useless (so you can remove that), while you should use the one created for QueueWin and set the video output with the QVideoWidget of the other window.
The "basic" solution would be the following:
if videocheck.find('video') != -1:
videoWindow = VideoWindow(self)
self.mediaPlayer.setVideoOutput(videoWindow.videowidget)
videoWindow.show()
Be aware, though, that your code has some important issues that would require fixing.
First of all, files generated by the pyuic utility should never, ever be manually modified (read how to properly use those files in the official guidelines about using Designer).
Then, while technically working, creating a new QMainWindow with another one as parent is not suggested; you also continuously create a new window everytime the function is called, while you should reuse any existing window. A more proper approach would be to create the child windows when creating the main one, and show them when required, in this way you can already set up the video output for the separate video widget.
Also note that you don't need to use an external module to check if the media actually has video: just use the videoAvailableChanged signal and show the video window if the argument is True (you cannot just check isVideoAvailable() right after setMedia(), as that function is asynchronous).
Finally, if you're not using any of the QMainWindow features (menu, toolbars, dock widgets, status bar), just use a basic QWidget instead.
I'm trying to play an online video using python-vlc library.
First, I was using a simple version of the player to test it out:
import vlc
#example url
url="https://player.vimeo.com/external/363536021.hd.mp4?s=6f6e87d86a49149479ebdab6c8bc421aa89f327c&profile_id=175&oauth2_token_id=57447761"
player = vlc.Instance().media_player_new()
player.set_media(vlc.Instance().media_new(url))
player.play()
while str(player.get_state()) != "State.Ended":
pass
player.stop()
It plays the while video from URL without any problems!
Now I wanted to use the more complex version with pyqt5 GUI (original source-code here):
import sys
import os.path
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QMainWindow, QWidget, QFrame, QSlider, QHBoxLayout, QPushButton, \
QVBoxLayout, QAction, QFileDialog, QApplication
import vlc
class Player(QMainWindow):
def __init__(self, videoUrl, master=None):
QMainWindow.__init__(self, master)
self.setWindowTitle("Media Player")
# creating a basic vlc instance
self.instance = vlc.Instance()
# creating an empty vlc media player
self.mediaplayer = self.instance.media_player_new()
self.playableVideo = videoUrl
self.createUI()
self.isPaused = False
def createUI(self):
"""Set up the user interface, signals & slots
"""
self.widget = QWidget(self)
self.setCentralWidget(self.widget)
# In this widget, the video will be drawn
if sys.platform == "darwin": # for MacOS
from PyQt5.QtWidgets import QMacCocoaViewContainer
self.videoframe = QMacCocoaViewContainer(0)
else:
self.videoframe = QFrame()
self.palette = self.videoframe.palette()
self.palette.setColor (QPalette.Window,
QColor(0,0,0))
self.videoframe.setPalette(self.palette)
self.videoframe.setAutoFillBackground(True)
self.positionslider = QSlider(Qt.Horizontal, self)
self.positionslider.setToolTip("Position")
self.positionslider.setMaximum(5000)
self.positionslider.sliderMoved.connect(self.setPosition)
self.hbuttonbox = QHBoxLayout()
self.playbutton = QPushButton("Play")
self.hbuttonbox.addWidget(self.playbutton)
self.playbutton.clicked.connect(self.PlayPause)
self.stopbutton = QPushButton("Exit")
self.hbuttonbox.addWidget(self.stopbutton)
self.stopbutton.clicked.connect(self.Stop)
self.hbuttonbox.setAlignment(Qt.AlignHCenter)
self.vboxlayout = QVBoxLayout()
self.vboxlayout.addWidget(self.videoframe)
self.vboxlayout.addWidget(self.positionslider)
self.vboxlayout.addLayout(self.hbuttonbox)
self.widget.setLayout(self.vboxlayout)
self.timer = QTimer(self)
self.timer.setInterval(200)
self.timer.timeout.connect(self.updateUI)
def PlayPause(self):
"""Toggle play/pause status
"""
if self.mediaplayer.is_playing():
self.mediaplayer.pause()
self.playbutton.setText("Play")
self.isPaused = True
else:
if self.mediaplayer.play() == -1:
self.OpenFile()
return
self.mediaplayer.play()
self.playbutton.setText("Pause")
self.timer.start()
self.isPaused = False
def Stop(self):
"""Stop player
"""
self.mediaplayer.stop()
self.playbutton.setText("Play")
def OpenFile(self, filename=None):
"""Open a media file in a MediaPlayer
"""
if filename is None:
filename = self.playableVideo
if not filename:
return
# create the media
if sys.version < '3':
filename = unicode(filename)
self.media = self.instance.media_new(filename)
# 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 a video would be displayed in it's own window)
# this is platform specific!
# you have to give the id of the QFrame (or similar object) to
# vlc, different platforms have different functions for this
if sys.platform.startswith('linux'): # for Linux using the X Server
self.mediaplayer.set_xwindow(self.videoframe.winId())
elif sys.platform == "win32": # for Windows
self.mediaplayer.set_hwnd(self.videoframe.winId())
elif sys.platform == "darwin": # for MacOS
self.mediaplayer.set_nsobject(int(self.videoframe.winId()))
self.PlayPause()
def setPosition(self, position):
self.mediaplayer.set_position(position / 1000.0)
def updateUI(self):
self.positionslider.setValue(self.mediaplayer.get_position() * 1000)
if not self.mediaplayer.is_playing():
self.timer.stop()
if not self.isPaused:
self.Stop()
if __name__ == "__main__":
app = QApplication(sys.argv)
player = Player("https://player.vimeo.com/external/363536021.hd.mp4?s=6f6e87d86a49149479ebdab6c8bc421aa89f327c&profile_id=175&oauth2_token_id=57447761")
player.show()
player.resize(640, 480)
if sys.argv[1:]:
player.OpenFile(sys.argv[1])
sys.exit(app.exec_())
This code keeps returning an access stream error: HTTP connection failure.
The problem is that as soon as you start playing the video it is not available since the download is not immediate but with your code you try to update the position. The solution is to update is to get the position after it started playing:
def updateUI(self):
if not self.mediaplayer.is_playing():
return
self.positionslider.setValue(self.mediaplayer.get_position() * 1000)
if not self.mediaplayer.is_playing():
self.timer.stop()
if not self.isPaused:
self.Stop()
Hello Experts!! I hope you are having great day. I am new in GUI programming specially PyQt5. I am practicing on simple GUI invoice application. In this application, I successfully generated the Invoice By QTextDocument. Now i want to add print dialogue and print preview option. I am having trouble in the code. This is saying
AttributeError: 'InvoiceForm' object has no attribute
'printpreviewDialog
As i am new, i am little bit confused in there. Could you please fix the code? That will help me a lot to study. Many Many Thanks.
The code has given below:-
import sys
from PyQt5.QtCore import pyqtSignal, QSize, QSizeF, QDate
from PyQt5.QtGui import QTextDocument, QTextCursor, QFont
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import QWidget, QFormLayout, QLineEdit, QPlainTextEdit, QSpinBox, QDateEdit, QTableWidget, \
QHeaderView, QPushButton, QHBoxLayout, QTextEdit, QApplication, QMainWindow
font= QFont('Arial',16)
class InvoiceForm(QWidget):
submitted = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.setLayout(QFormLayout())
self.inputs = dict()
self.inputs['Customer Name'] = QLineEdit()
self.inputs['Customer Address'] = QPlainTextEdit()
self.inputs['Invoice Date'] = QDateEdit(date=QDate.currentDate(), calendarPopup=True)
self.inputs['Days until Due'] = QSpinBox()
for label, widget in self.inputs.items():
self.layout().addRow(label, widget)
self.line_items = QTableWidget(rowCount=10, columnCount=3)
self.line_items.setHorizontalHeaderLabels(['Job', 'Rate', 'Hours'])
self.line_items.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.layout().addRow(self.line_items)
for row in range(self.line_items.rowCount()):
for col in range(self.line_items.columnCount()):
if col > 0:
w = QSpinBox()
self.line_items.setCellWidget(row, col, w)
submit = QPushButton('Create Invoice', clicked=self.on_submit)
print = QPushButton('Print Invoice', clicked=self.printpreviewDialog)
self.layout().addRow(submit,print)
def on_submit(self):
data = {'c_name': self.inputs['Customer Name'].text(),
'c_addr': self.inputs['Customer Address'].toPlainText(),
'i_date': self.inputs['Invoice Date'].date().toString(),
'i_due': self.inputs['Invoice Date'].date().addDays(self.inputs['Days until Due'].value()).toString(),
'i_terms': '{} days'.format(self.inputs['Days until Due'].value()),
'line_items': list()}
for row in range(self.line_items.rowCount()):
if not self.line_items.item(row, 0):
continue
job = self.line_items.item(row, 0).text()
rate = self.line_items.cellWidget(row, 1).value()
hours = self.line_items.cellWidget(row, 2).value()
total = rate * hours
row_data = [job, rate, hours, total]
if any(row_data):
data['line_items'].append(row_data)
data['total_due'] = sum(x[3] for x in data['line_items'])
self.submitted.emit(data)
# remove everything else in this function below this point
class InvoiceView(QTextEdit):
dpi = 72
doc_width = 8.5 * dpi
doc_height = 6 * dpi
def __init__(self):
super().__init__(readOnly=True)
self.setFixedSize(QSize(self.doc_width, self.doc_height))
def build_invoice(self, data):
document = QTextDocument()
self.setDocument(document)
document.setPageSize(QSizeF(self.doc_width, self.doc_height))
document.setDefaultFont(font)
cursor = QTextCursor(document)
cursor.insertText(f"Customer Name: {data['c_name']}\n")
cursor.insertText(f"Customer Address: {data['c_addr']}\n")
cursor.insertText(f"Date: {data['i_date']}\n")
cursor.insertText(f"Total Due: {data['total_due']}\n")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
self.setCentralWidget(central)
layout = QHBoxLayout(central)
self.invoiceForm = InvoiceForm()
layout.addWidget(self.invoiceForm)
self.invoiceView = InvoiceView()
layout.addWidget(self.invoiceView)
# hide the widget right now...
self.invoiceView.setVisible(False)
self.invoiceForm.submitted.connect(self.showPreview)
def showPreview(self, data):
self.invoiceView.setVisible(True)
self.invoiceView.build_invoice(data)
def printpreviewDialog(self):
printer = QPrinter(QPrinter.HighResolution)
previewDialog = QPrintPreviewDialog(printer, self)
previewDialog.paintRequested.connect(self.printPreview)
previewDialog.exec_()
def printPreview(self, printer):
self.invoiceView.build_invoice.print_(printer)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
if __name__ == '__main__':
main()
The main problem is that self.printpreviewDialog is a member of MainWindow, not of InvoiceForm, so you should connect the clicked signal from the main window instead.
Also note that you tried to use self.invoiceView.build_invoice.print_(), but this wouldn't work as you are not calling build_invoice, and even if you did, that function doesn't return anything.
You should use the self.invoiceView.document() instead, but you must ensure that the data has been built before.
class InvoiceForm(QWidget):
submitted = pyqtSignal(dict)
def __init__(self):
# ...
submit = QPushButton('Create Invoice', clicked=self.on_submit)
# make the button a member of the instance instead of a local variable,
# so that we can connect from the main window instance
self.printButton = QPushButton('Print Invoice')
self.layout().addRow(submit, self.printButton)
# ...
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.invoiceForm.printButton.clicked.connect(self.printpreviewDialog)
# ...
def printPreview(self, printer):
self.invoiceView.document().print_(printer)
Note: never, never use built-in functions and statements for variable names, like print.
Try it:
import sys
from PyQt5.QtCore import pyqtSignal, QSize, QSizeF, QDate
from PyQt5.QtGui import QTextDocument, QTextCursor, QFont
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import (QWidget, QFormLayout, QLineEdit, QPlainTextEdit,
QSpinBox, QDateEdit, QTableWidget, QHeaderView, QPushButton, QHBoxLayout,
QTextEdit, QApplication, QMainWindow)
font = QFont('Arial',16)
class InvoiceForm(QWidget):
submitted = pyqtSignal(dict)
def __init__(self, parent=None): # + parent=None
super().__init__(parent) # + parent
self.setLayout(QFormLayout())
self.inputs = dict()
self.inputs['Customer Name'] = QLineEdit()
self.inputs['Customer Address'] = QPlainTextEdit()
self.inputs['Invoice Date'] = QDateEdit(date=QDate.currentDate(), calendarPopup=True)
self.inputs['Days until Due'] = QSpinBox()
for label, widget in self.inputs.items():
self.layout().addRow(label, widget)
self.line_items = QTableWidget(rowCount=10, columnCount=3)
self.line_items.setHorizontalHeaderLabels(['Job', 'Rate', 'Hours'])
self.line_items.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.layout().addRow(self.line_items)
for row in range(self.line_items.rowCount()):
for col in range(self.line_items.columnCount()):
if col > 0:
w = QSpinBox()
self.line_items.setCellWidget(row, col, w)
submit = QPushButton('Create Invoice', clicked=self.on_submit)
# + vvvvvv vvvvvvvvvvvvv
_print = QPushButton('Print Invoice', clicked=self.window().printpreviewDialog) # + _print, + self.window()
self.layout().addRow(submit, _print) # + _print
def on_submit(self):
data = {'c_name': self.inputs['Customer Name'].text(),
'c_addr': self.inputs['Customer Address'].toPlainText(),
'i_date': self.inputs['Invoice Date'].date().toString(),
'i_due': self.inputs['Invoice Date'].date().addDays(self.inputs['Days until Due'].value()).toString(),
'i_terms': '{} days'.format(self.inputs['Days until Due'].value()),
'line_items': list()}
for row in range(self.line_items.rowCount()):
if not self.line_items.item(row, 0):
continue
job = self.line_items.item(row, 0).text()
rate = self.line_items.cellWidget(row, 1).value()
hours = self.line_items.cellWidget(row, 2).value()
total = rate * hours
row_data = [job, rate, hours, total]
if any(row_data):
data['line_items'].append(row_data)
data['total_due'] = sum(x[3] for x in data['line_items'])
self.submitted.emit(data)
# remove everything else in this function below this point
# +
return data # +++
class InvoiceView(QTextEdit):
dpi = 72
doc_width = 8.5 * dpi
doc_height = 6 * dpi
def __init__(self):
super().__init__(readOnly=True)
self.setFixedSize(QSize(self.doc_width, self.doc_height))
def build_invoice(self, data):
document = QTextDocument()
self.setDocument(document)
document.setPageSize(QSizeF(self.doc_width, self.doc_height))
document.setDefaultFont(font)
cursor = QTextCursor(document)
cursor.insertText(f"Customer Name: {data['c_name']}\n")
cursor.insertText(f"Customer Address: {data['c_addr']}\n")
cursor.insertText(f"Date: {data['i_date']}\n")
cursor.insertText(f"Total Due: {data['total_due']}\n")
# +
return document # +++
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
self.setCentralWidget(central)
layout = QHBoxLayout(central)
# + vvvv
self.invoiceForm = InvoiceForm(self) # + self
layout.addWidget(self.invoiceForm)
self.invoiceView = InvoiceView()
layout.addWidget(self.invoiceView)
# hide the widget right now...
self.invoiceView.setVisible(False)
self.invoiceForm.submitted.connect(self.showPreview)
def showPreview(self, data):
self.invoiceView.setVisible(True)
self.invoiceView.build_invoice(data)
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
def printpreviewDialog(self):
previewDialog = QPrintPreviewDialog()
previewDialog.paintRequested.connect(self.printPreview)
previewDialog.exec_()
def printPreview(self, printer):
# self.invoiceView.build_invoice.print_(printer)
data = self.invoiceForm.on_submit()
document = self.invoiceView.build_invoice(data)
document.print_(printer)
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
if __name__ == '__main__':
main()
I am creating a program to play videos and then process them. I am able to play the videos with QMediaPlayer. How do I access specific frames as images or something similar. My end goal would be to format the video into a 4-d tensor of size [Num of frames, width_of_video, height_of_video, channels].
Here is the code that loads my video.:
self.clear_layout(self.vlayout)
videoItem = QVideoWidget()
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.mediaPlayer.durationChanged.connect(self.update_duration)
self.mediaPlayer.positionChanged.connect(self.update_slider_position)
self.vlayout.addWidget(videoItem)
self.mediaPlayer.setVideoOutput(videoItem)
local = QUrl.fromLocalFile(self.video_paths)
media = QMediaContent(local)
self.mediaPlayer.setMedia(media)
self.play_video()
Here is a working example that I converted to Python from the C++ version available in this question: How to save a frame using QMediaPlayer?
import sys
import uuid
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt, QObject, QUrl, QRect, pyqtSignal, QPoint
from PyQt5.QtGui import QPainter, QImage
from PyQt5.QtWidgets import QWidget, QApplication, QMainWindow, QGridLayout, QToolBar, QAction
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAbstractVideoBuffer, \
QVideoFrame, QVideoSurfaceFormat, QAbstractVideoSurface
from PyQt5.QtMultimediaWidgets import QVideoWidget
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 App(QApplication):
def __init__(self, sys_argv):
super().__init__(sys_argv)
# Show main window
self.view = QMainWindow()
self.centralWidget = QWidget(self.view)
self.gridLayout = QGridLayout(self.centralWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setSpacing(0)
self.video_item = QVideoWidget()
self.gridLayout.addWidget(self.video_item)
self.view.setCentralWidget(self.centralWidget)
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.grabber = VideoFrameGrabber(self.video_item, self)
self.mediaPlayer.setVideoOutput(self.grabber)
self.grabber.frameAvailable.connect(self.process_frame)
self.mediaPlayer.durationChanged.connect(self.update_duration)
self.mediaPlayer.positionChanged.connect(self.update_slider_position)
local = QUrl.fromLocalFile('c:/temp/lorem.mp4')
media = QMediaContent(local)
self.mediaPlayer.setMedia(media)
self.mediaPlayer.play()
self.view.show()
def process_frame(self, image):
# Save image here
image.save('c:/temp/{}.jpg'.format(str(uuid.uuid4())))
def update_duration(self):
pass
def update_slider_position(self):
pass
if __name__ == '__main__':
def except_hook(cls, exception, traceback):
sys.__excepthook__(cls, exception, traceback)
if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
PyQt5.QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
PyQt5.QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
app = App(sys.argv)
app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
sys.excepthook = except_hook
sys.exit(app.exec_())