-
-
Save Axel-Erfurt/af8bc3ff7dc11809b5ed3710af915b13 to your computer and use it in GitHub Desktop.
#!/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_()) |
hi axel, i'm having trouble with pyqt5.multimedia, it's not in the instalation and can't find any information on how to make it work, could you help me? what i need to do is really simple, play a video file from my computer.
i'm working in latest ubuntu
sudo apt-get install qtmultimedia5-dev
Thanks! Interesting concept. I'll try importing youtube-dl ... or have you posted that ?
nice
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!
added YouTube (needs youtube-dl)