Skip to content

Instantly share code, notes, and snippets.

@Axel-Erfurt
Last active November 1, 2023 22:27
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Axel-Erfurt/af8bc3ff7dc11809b5ed3710af915b13 to your computer and use it in GitHub Desktop.
Save Axel-Erfurt/af8bc3ff7dc11809b5ed3710af915b13 to your computer and use it in GitHub Desktop.
PyQt5 VideoPlayer
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5.QtGui import QPalette, QKeySequence, QIcon
from PyQt5.QtCore import QDir, Qt, QUrl, QSize, QPoint, QTime, QMimeData, QProcess, QEvent
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer, QMediaMetaData
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLineEdit,
QPushButton, QSizePolicy, QSlider, QMessageBox, QStyle, QVBoxLayout,
QWidget, QShortcut, QMenu)
import sys
import os
import subprocess
#QT_DEBUG_PLUGINS
class VideoPlayer(QWidget):
def __init__(self, aPath, parent=None):
super(VideoPlayer, self).__init__(parent)
self.setAttribute( Qt.WA_NoSystemBackground, True )
self.setAcceptDrops(True)
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.StreamPlayback)
self.mediaPlayer.mediaStatusChanged.connect(self.printMediaData)
self.mediaPlayer.setVolume(80)
self.videoWidget = QVideoWidget(self)
self.lbl = QLineEdit('00:00:00')
self.lbl.setReadOnly(True)
self.lbl.setFixedWidth(70)
self.lbl.setUpdatesEnabled(True)
self.lbl.setStyleSheet(stylesheet(self))
self.lbl.selectionChanged.connect(lambda: self.lbl.setSelection(0, 0))
self.elbl = QLineEdit('00:00:00')
self.elbl.setReadOnly(True)
self.elbl.setFixedWidth(70)
self.elbl.setUpdatesEnabled(True)
self.elbl.setStyleSheet(stylesheet(self))
self.elbl.selectionChanged.connect(lambda: self.elbl.setSelection(0, 0))
self.playButton = QPushButton()
self.playButton.setEnabled(False)
self.playButton.setFixedWidth(32)
self.playButton.setStyleSheet("background-color: black")
self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playButton.clicked.connect(self.play)
self.positionSlider = QSlider(Qt.Horizontal, self)
self.positionSlider.setStyleSheet (stylesheet(self))
self.positionSlider.setRange(0, 100)
self.positionSlider.sliderMoved.connect(self.setPosition)
self.positionSlider.setSingleStep(2)
self.positionSlider.setPageStep(20)
self.positionSlider.setAttribute(Qt.WA_TranslucentBackground, True)
self.clip = QApplication.clipboard()
self.process = QProcess(self)
self.process.readyRead.connect(self.dataReady)
self.process.finished.connect(self.playFromURL)
self.myurl = ""
controlLayout = QHBoxLayout()
controlLayout.setContentsMargins(5, 0, 5, 0)
controlLayout.addWidget(self.playButton)
controlLayout.addWidget(self.lbl)
controlLayout.addWidget(self.positionSlider)
controlLayout.addWidget(self.elbl)
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.videoWidget)
layout.addLayout(controlLayout)
self.setLayout(layout)
self.myinfo = "©2016\nAxel Schneider\n\nMouse Wheel = Zoom\nUP = Volume Up\nDOWN = Volume Down\n" + \
"LEFT = < 1 Minute\nRIGHT = > 1 Minute\n" + \
"SHIFT+LEFT = < 10 Minutes\nSHIFT+RIGHT = > 10 Minutes"
self.widescreen = True
#### shortcuts ####
self.shortcut = QShortcut(QKeySequence("q"), self)
self.shortcut.activated.connect(self.handleQuit)
self.shortcut = QShortcut(QKeySequence("u"), self)
self.shortcut.activated.connect(self.playFromURL)
self.shortcut = QShortcut(QKeySequence("y"), self)
self.shortcut.activated.connect(self.getYTUrl)
self.shortcut = QShortcut(QKeySequence("o"), self)
self.shortcut.activated.connect(self.openFile)
self.shortcut = QShortcut(QKeySequence(" "), self)
self.shortcut.activated.connect(self.play)
self.shortcut = QShortcut(QKeySequence("f"), self)
self.shortcut.activated.connect(self.handleFullscreen)
self.shortcut = QShortcut(QKeySequence("i"), self)
self.shortcut.activated.connect(self.handleInfo)
self.shortcut = QShortcut(QKeySequence("s"), self)
self.shortcut.activated.connect(self.toggleSlider)
self.shortcut = QShortcut(QKeySequence(Qt.Key_Right), self)
self.shortcut.activated.connect(self.forwardSlider)
self.shortcut = QShortcut(QKeySequence(Qt.Key_Left), self)
self.shortcut.activated.connect(self.backSlider)
self.shortcut = QShortcut(QKeySequence(Qt.Key_Up), self)
self.shortcut.activated.connect(self.volumeUp)
self.shortcut = QShortcut(QKeySequence(Qt.Key_Down), self)
self.shortcut.activated.connect(self.volumeDown)
self.shortcut = QShortcut(QKeySequence(Qt.ShiftModifier + Qt.Key_Right) , self)
self.shortcut.activated.connect(self.forwardSlider10)
self.shortcut = QShortcut(QKeySequence(Qt.ShiftModifier + Qt.Key_Left) , self)
self.shortcut.activated.connect(self.backSlider10)
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)
print("QT5 Player started")
print("press 'o' to open file (see context menu for more)")
self.suspend_screensaver()
def mouseDoubleClickEvent(self, event):
self.handleFullscreen()
def playFromURL(self):
self.mediaPlayer.pause()
self.myurl = self.clip.text()
self.mediaPlayer.setMedia(QMediaContent(QUrl(self.myurl)))
self.playButton.setEnabled(True)
self.mediaPlayer.play()
self.hideSlider()
print(self.myurl)
def getYTUrl(self):
cmd = "youtube-dl -g -f best " + self.clip.text()
print("grabbing YouTube URL")
self.process.start(cmd)
def dataReady(self):
self.myurl = str(self.process.readAll(), encoding = 'utf8').rstrip() ###
self.myurl = self.myurl.partition("\n")[0]
print(self.myurl)
self.clip.setText(self.myurl)
self.playFromURL()
def suspend_screensaver(self):
'suspend linux screensaver'
proc = subprocess.Popen('gsettings set org.gnome.desktop.screensaver idle-activation-enabled false', shell=True)
proc.wait()
def resume_screensaver(self):
'resume linux screensaver'
proc = subprocess.Popen('gsettings set org.gnome.desktop.screensaver idle-activation-enabled true', shell=True)
proc.wait()
def openFile(self):
fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie",
QDir.homePath() + "/Videos", "Media (*.webm *.mp4 *.ts *.avi *.mpeg *.mpg *.mkv *.VOB *.m4v *.3gp *.mp3 *.m4a *.wav *.ogg *.flac *.m3u *.m3u8)")
if fileName != '':
self.loadFilm(fileName)
print("File loaded")
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)
mtime = QTime(0,0,0,0)
mtime = mtime.addMSecs(self.mediaPlayer.position())
self.lbl.setText(mtime.toString())
def durationChanged(self, duration):
self.positionSlider.setRange(0, duration)
mtime = QTime(0,0,0,0)
mtime = mtime.addMSecs(self.mediaPlayer.duration())
self.elbl.setText(mtime.toString())
def setPosition(self, position):
self.mediaPlayer.setPosition(position)
def handleError(self):
self.playButton.setEnabled(False)
print("Error: ", self.mediaPlayer.errorString())
def handleQuit(self):
self.mediaPlayer.stop()
self.resume_screensaver()
print("Goodbye ...")
app.quit()
def contextMenuRequested(self,point):
menu = QMenu()
actionFile = menu.addAction(QIcon.fromTheme("video-x-generic"),"open File (o)")
actionclipboard = menu.addSeparator()
actionURL = menu.addAction(QIcon.fromTheme("browser"),"URL from Clipboard (u)")
actionclipboard = menu.addSeparator()
actionYTurl = menu.addAction(QIcon.fromTheme("youtube"), "URL from YouTube (y)")
actionclipboard = menu.addSeparator()
actionToggle = menu.addAction(QIcon.fromTheme("next"),"show / hide Slider (s)")
actionFull = menu.addAction(QIcon.fromTheme("view-fullscreen"),"Fullscreen (f)")
action169 = menu.addAction(QIcon.fromTheme("tv-symbolic"),"16 : 9")
action43 = menu.addAction(QIcon.fromTheme("tv-symbolic"),"4 : 3")
actionSep = menu.addSeparator()
actionInfo = menu.addAction(QIcon.fromTheme("help-about"),"Info (i)")
action5 = menu.addSeparator()
actionQuit = menu.addAction(QIcon.fromTheme("application-exit"),"Exit (q)")
actionFile.triggered.connect(self.openFile)
actionQuit.triggered.connect(self.handleQuit)
actionFull.triggered.connect(self.handleFullscreen)
actionInfo.triggered.connect(self.handleInfo)
actionToggle.triggered.connect(self.toggleSlider)
actionURL.triggered.connect(self.playFromURL)
actionYTurl.triggered.connect(self.getYTUrl)
action169.triggered.connect(self.screen169)
action43.triggered.connect(self.screen43)
menu.exec_(self.mapToGlobal(point))
def wheelEvent(self,event):
mwidth = self.frameGeometry().width()
mheight = self.frameGeometry().height()
mleft = self.frameGeometry().left()
mtop = self.frameGeometry().top()
mscale = event.angleDelta().y() / 5
if self.widescreen == True:
self.setGeometry(mleft, mtop, mwidth + mscale, round((mwidth + mscale) / 1.778))
else:
self.setGeometry(mleft, mtop, mwidth + mscale, round((mwidth + mscale) / 1.33))
#elif self.positionSlider.hasFocus():
# self.positionSlider.value = self.positionSlider.value + 5
def screen169(self):
self.widescreen = True
mwidth = self.frameGeometry().width()
mheight = self.frameGeometry().height()
mleft = self.frameGeometry().left()
mtop = self.frameGeometry().top()
mratio = 1.778
self.setGeometry(mleft, mtop, mwidth, round(mwidth / mratio))
def screen43(self):
self.widescreen = False
mwidth = self.frameGeometry().width()
mheight = self.frameGeometry().height()
mleft = self.frameGeometry().left()
mtop = self.frameGeometry().top()
mratio = 1.33
self.setGeometry(mleft, mtop, mwidth, round(mwidth / mratio))
def handleFullscreen(self):
if self.windowState() & Qt.WindowFullScreen:
QApplication.setOverrideCursor(Qt.ArrowCursor)
self.showNormal()
print("no Fullscreen")
else:
self.showFullScreen()
QApplication.setOverrideCursor(Qt.BlankCursor)
print("Fullscreen entered")
def handleInfo(self):
msg = QMessageBox.about(self, "QT5 Player", self.myinfo)
def toggleSlider(self):
if self.positionSlider.isVisible():
self.hideSlider()
else:
self.showSlider()
def hideSlider(self):
self.playButton.hide()
self.lbl.hide()
self.positionSlider.hide()
self.elbl.hide()
mwidth = self.frameGeometry().width()
mheight = self.frameGeometry().height()
mleft = self.frameGeometry().left()
mtop = self.frameGeometry().top()
if self.widescreen == True:
self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.778))
else:
self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.33))
def showSlider(self):
self.playButton.show()
self.lbl.show()
self.positionSlider.show()
self.elbl.show()
mwidth = self.frameGeometry().width()
mheight = self.frameGeometry().height()
mleft = self.frameGeometry().left()
mtop = self.frameGeometry().top()
if self.widescreen == True:
self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.55))
else:
self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.33))
def forwardSlider(self):
self.mediaPlayer.setPosition(self.mediaPlayer.position() + 1000*60)
def forwardSlider10(self):
self.mediaPlayer.setPosition(self.mediaPlayer.position() + 10000*60)
def backSlider(self):
self.mediaPlayer.setPosition(self.mediaPlayer.position() - 1000*60)
def backSlider10(self):
self.mediaPlayer.setPosition(self.mediaPlayer.position() - 10000*60)
def volumeUp(self):
self.mediaPlayer.setVolume(self.mediaPlayer.volume() + 10)
print("Volume: " + str(self.mediaPlayer.volume()))
def volumeDown(self):
self.mediaPlayer.setVolume(self.mediaPlayer.volume() - 10)
print("Volume: " + str(self.mediaPlayer.volume()))
def mousePressEvent(self, evt):
self.oldPos = evt.globalPos()
def mouseMoveEvent(self, evt):
delta = QPoint(evt.globalPos() - self.oldPos)
self.move(self.x() + delta.x(), self.y() + delta.y())
self.oldPos = evt.globalPos()
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
elif event.mimeData().hasText():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
print("drop")
if event.mimeData().hasUrls():
url = event.mimeData().urls()[0].toString()
print("url = ", url)
self.mediaPlayer.stop()
self.mediaPlayer.setMedia(QMediaContent(QUrl(url)))
self.playButton.setEnabled(True)
self.mediaPlayer.play()
elif event.mimeData().hasText():
mydrop = event.mimeData().text()
### YouTube url
if "youtube" in mydrop:
print("is YouTube", mydrop)
self.clip.setText(mydrop)
self.getYTUrl()
else:
### normal url
print("generic url = ", mydrop)
self.mediaPlayer.setMedia(QMediaContent(QUrl(mydrop)))
self.playButton.setEnabled(True)
self.mediaPlayer.play()
self.hideSlider()
def loadFilm(self, f):
self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(f)))
self.playButton.setEnabled(True)
self.mediaPlayer.play()
def printMediaData(self):
if self.mediaPlayer.mediaStatus() == 6:
if self.mediaPlayer.isMetaDataAvailable():
res = str(self.mediaPlayer.metaData("Resolution")).partition("PyQt5.QtCore.QSize(")[2].replace(", ", "x").replace(")", "")
print("%s%s" % ("Video Resolution = ",res))
if int(res.partition("x")[0]) / int(res.partition("x")[2]) < 1.5:
self.screen43()
else:
self.screen169()
else:
print("no metaData available")
def openFileAtStart(self, filelist):
matching = [s for s in filelist if ".myformat" in s]
if len(matching) > 0:
self.loadFilm(matching)
##################### end ##################################
def stylesheet(self):
return """
QSlider::handle:horizontal
{
background: transparent;
width: 8px;
}
QSlider::groove:horizontal {
border: 1px solid #444444;
height: 8px;
background: qlineargradient(y1: 0, y2: 1,
stop: 0 #2e3436, stop: 1.0 #000000);
}
QSlider::sub-page:horizontal {
background: qlineargradient( y1: 0, y2: 1,
stop: 0 #729fcf, stop: 1 #2a82da);
border: 1px solid #777;
height: 8px;
}
QSlider::handle:horizontal:hover {
background: #2a82da;
height: 8px;
width: 18px;
border: 1px solid #2e3436;
}
QSlider::sub-page:horizontal:disabled {
background: #bbbbbb;
border-color: #999999;
}
QSlider::add-page:horizontal:disabled {
background: #2a82da;
border-color: #999999;
}
QSlider::handle:horizontal:disabled {
background: #2a82da;
}
QLineEdit
{
background: black;
color: #585858;
border: 0px solid #076100;
font-size: 8pt;
font-weight: bold;
}
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
player = VideoPlayer('')
player.setAcceptDrops(True)
player.setWindowTitle("QT5 Player")
player.setWindowIcon(QIcon.fromTheme("multimedia-video-player"))
player.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
player.setGeometry(100, 300, 600, 380)
player.setContextMenuPolicy(Qt.CustomContextMenu);
player.customContextMenuRequested[QPoint].connect(player.contextMenuRequested)
player.hideSlider()
player.show()
player.widescreen = True
if len(sys.argv) > 1:
print(sys.argv[1])
if sys.argv[1].startswith("http"):
player.myurl = sys.argv[1]
player.playFromURL()
else:
player.loadFilm(sys.argv[1])
sys.exit(app.exec_())
@soyuznik
Copy link

nice

@nguyentrongnhat4869
Copy link

Hi i'm trying to play a m3u8 link (rtsp to hls conversion) to run a stream, i got it running and the m3u8 link updates continuously. But the problem is that the position of the stream changes but the duration is always 0, so I can't rewind the stream. I tried another public m3u8 link and found that if it had the #EXT-X-ENDLIST tag, the stream would still be rewindable, and the link my m3u8 created would not have the #EXT-X-ENDLIST tag because it changed change continuously until rtsp is always interrupted. Is there any way I can rewind in this case as hls.js has implemented. Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment