4

I have a video, from which I am getting some clips by time interval, and I want to do the following:

  • Make the clips play one after another - so first clip plays, then after it is done, then second plays, and so on... But currently, it's good, but it just plays the part in between the time intervals of the two clips, which I don't want it to do.

How would I go about doing that?

I tried using e.g. pause, stop, etc... simply everything, but none of them worked.

My version of python: 3.6.0

And PyQt: 5.6

Video player file (a link to repl since it is too long):

https://repl.it/repls/SuperBrownSoftware

(just copy it into your IDE)

Here is my code that you should run:

from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia
from PyQt5.QtWidgets import *
import sys
from pyqtvideo2_copy import *
app = QtWidgets.QApplication(sys.argv)

video = VideoWidget()
w = QtWidgets.QWidget()
w.fr = QtWidgets.QGridLayout(w)
w.bt = QtWidgets.QPushButton()
w.fr.addWidget(w.bt)
w.player_=Player(sys.argv[1:])
w.fr.addWidget(w.player_)
print(w)
video.activateWindow()

def clicked():
    l=[[2000,4000],[10000,15000]]
    for i in l:
        w.player_.setPosition(i[0])
        w.player_.player.pause()
        w.player_.player.play()
        w._end=i[1]
        w.player_.player.positionChanged.connect(on_positionChanged)

def on_positionChanged(position):
    if w.player_.player.state() == QtMultimedia.QMediaPlayer.PlayingState:
        if position > w._end:
            w.player_.player.stop()

def except_hook(cls, exception, traceback):
    sys.__excepthook__(cls, exception, traceback)

w.bt.clicked.connect(clicked)
w.show()
sys.excepthook=except_hook
sys.exit(app.exec_())

Update:

I now got the answer by ekhumoro, so i am using this setup of the structure:

class Ui_MainWindow(QMainWindow):
    def setupUi(self, MainWindow):
        ...
    def retranslateUi(self, MainWindow):
        ...
        self.player.positionChanged.connect(self.handlePositionChanged)
        self.player.mediaStatusChanged.connect(self.handleMediaStateChanged)

    ...
    def videoclips(self):
        self.w=QWidget()
        g=QGridLayout(self.w)
        g.setContentsMargins(0,0,0,0)
        d=TableWidget(self.df2,self.clicked)
        g.addWidget(d)
        self.w.show()
    def clicked(self,item):
        self.w.close()
        self.addMedia(ast.literal_eval(item.text()))
    def addMedia(self, clips):
        self._index = -1
        self._clips = clips
    def playNext(self):
        self.player.player.pause()
        self._index += 1
        if 0 <= self._index < len(self._clips):
            self.player.player.setPosition(self._clips[self._index][0])
            self.player.player.play()
    def handlePositionChanged(self, pos):
        if (0 <= self._index < len(self._clips) and
            pos > self._clips[self._index][1] and
            self.player.player.state() == QtMultimedia.QMediaPlayer.PlayingState):
            self.playNext()

    def handleMediaStateChanged(self, state):
        if state == QtMultimedia.QMediaPlayer.LoadedMedia:
            self.playNext()

I copied ekhumoro's answer's functions to my code, then added two lines of: self.player.positionChanged.connect(self.handlePositionChanged) and self.player.mediaStatusChanged.connect(self.handleMediaStateChanged) in retranslateUi

But that didn't seem to work, i felt like i did some mistake.

It's giving me an error:

AttributeError: 'Ui_MainWindow' object has no attribute '_index'
U13-Forward
  • 69,221
  • 14
  • 89
  • 114

1 Answers1

4

One approach is to create a simple queue and then wait for each clip to finish before moving to the next item in the queue. Below is a simple demo showing how to achieve that. Hopefully it should be easy to see how to adapt this to your own code.

import sys
from PyQt5 import QtCore, QtWidgets, QtMultimedia, QtMultimediaWidgets

class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.video = QtMultimediaWidgets.QVideoWidget()
        self.player = QtMultimedia.QMediaPlayer()
        self.player.setVideoOutput(self.video)
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.video)
        self.player.positionChanged.connect(self.handlePositionChanged)
        self.player.mediaStatusChanged.connect(self.handleMediaStateChanged)

    def addMedia(self, path, clips):
        self._index = -1
        self._clips = clips
        self.player.setMedia(
            QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(path)))

    def playNext(self):
        self.player.pause()
        self._index += 1
        if 0 <= self._index < len(self._clips):
            self.player.setPosition(self._clips[self._index][0])
            self.player.play()

    def handlePositionChanged(self, pos):
        if (0 <= self._index < len(self._clips) and
            pos > self._clips[self._index][1] and
            self.player.state() == QtMultimedia.QMediaPlayer.PlayingState):
            self.playNext()

    def handleMediaStateChanged(self, state):
        if state == QtMultimedia.QMediaPlayer.LoadedMedia:
            self.playNext()

app = QtWidgets.QApplication(sys.argv)
window = Window()
window.addMedia(sys.argv[1], [[2000, 4000], [10000, 15000]])
# window.addMedia(sys.argv[1], [[10000, 20000], [40000, 50000]])
window.show()
sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Do you have any idea how to embed this into another code? – U13-Forward Feb 18 '19 at 02:46
  • I thought I'd already made it as simple as I could. The only parts that really matter are creating some kind of queue for the clip parameters, and then using the `positionChanged` signal to grab the next one. All the other stuff in my example is just there to make a working demo, and can be ignored. The key concept is to avoid using an explicit loop structure (like you have in your question), and use signals to wait until the current clip has finished before getting the next one from the queue. If you can't get that working, there must be something wrong with some other part of your code. – ekhumoro Feb 18 '19 at 04:32
  • Thanks for your reply, so simply i copy your `addMedia`, `playNext`, `handlePositionChanged`, and `handleMediaStateChanged` functions into my whole code, also add these two lines: `self.player.positionChanged.connect(self.handlePositionChanged)` `self.player.mediaStatusChanged.connect(self.handleMediaStateChanged)` – U13-Forward Feb 18 '19 at 09:16
  • No, you obviously cannot just copy and paste the code directly. And that is not what you asked for. You asked for **how** to do it, which is what my answer does. You don't need the media state changed part. You just need a function to initialize the queue (like `addMedia`); a function to start the next clip (like `playNext`); and a function to handle the position change signal (like `handlePositionChange`). I think it's quite easy to see how my functions work if you read them carefully, so it it should also be quite easy to see how to use something similar in your own code. – ekhumoro Feb 18 '19 at 13:45
  • gave you bounty. – U13-Forward Feb 19 '19 at 00:38
  • @U9-Forward Thanks. Have made any progress yet? I don't think I can help any further unless you're more specific about what you're having difficulty with. Maybe you should update your question with a different example. – ekhumoro Feb 19 '19 at 00:45
  • Sorry, i was busy for a while, gonna answer you – U13-Forward Feb 28 '19 at 02:10
  • My structure is: https://repl.it/repls/ArcticUnluckyAccounting So currently it is still doing the same – U13-Forward Feb 28 '19 at 02:20
  • @U9-Forward As I asked before: please be more specific about what you're having difficulty with, and put the relevant code in your question. – ekhumoro Feb 28 '19 at 02:48
  • Oh sorry, also, i won't post my whole code, it is way to long, i will edit – U13-Forward Feb 28 '19 at 02:55
  • @U9-Forward No. The code there is mostly what I gave in my answer. I cannot guess what is in the code that you haven't shown, so I don't see how I can help. – ekhumoro Feb 28 '19 at 03:57