Get current frame number of video playing with python-vlc - python

I'm trying to make a python program that plays a video in sync with a light show done via Arduino. To achieve this I need to know what frame of the video is currently playing to send out data to the Arduino.
I modified the cocoavlc.py example of python-vlc to play a video, pause, resume and rewind it, go forward and backwards by 10 seconds.
To keep track of the frame number tried the method get_time of python-vlc but it only updates a few times a second. I had the idea to interpolate its data with a timer but it's not perfect.
Here's the code (without the timer):
import vlc
from pycocoa import __name__ as _pycocoa_
from pycocoa import App, Item, ItemSeparator, MediaWindow, Menu, OpenPanel
from os.path import isfile
from threading import Thread
from time import sleep
import time
_Movies = '.m4v', '.mov', '.mp4'
_Select = 'select a video file from the panel'
class AppVLC(App):
player = None
sized = None
Toggle = None
video = None
window = None
def __init__(self, video = None, title = 'AppVLC'):
super(AppVLC, self).__init__(title = title)
self.media = None
self.Toggle = Item('Play', self.menuToggle_, key='p')
self.video = video
self.player = vlc.MediaPlayer()
def appLaunched_(self, app):
super(AppVLC, self).appLaunched_(app)
self.window = MediaWindow(title=self.video or self.title)
if self.player and self.video and isfile(self.video):
# the VLC player on macOS needs an ObjC NSView
self.media = self.player.set_mrl(self.video)
self.player.set_nsobject(self.window.NSview)
menu = Menu('VLC')
menu.append(
Item('Open...', key='o'),
ItemSeparator(),
self.Toggle,
Item('Rewind', key='r'),
Item('Forward', key='f'),
ItemSeparator(),
Item('Back', key='b'))
ItemSeparator(),
self.append(menu)
self.menuPlay_(None)
self.window.front()
def menuOpen_(self, item):
self.menuPause_(item)
self.badge.label = 'O'
v = OpenPanel(_Select).pick(_Movies)
if v:
self.window.title = self.video = v
self.player.set_mrl(v)
self.sized = None
def menuPause_(self, item, pause = False):
if pause or self.player.is_playing():
self.player.pause()
self.badge.label = 'S'
self.Toggle.title = 'Play'
def menuPlay_(self, item_or_None):
self.player.play()
self._resizer()
self.badge.label = 'P'
self.Toggle.title = 'Pause'
def menuRewind_(self, item):
self.player.set_position(0.0)
self.badge.label = 'R'
self.sized = None
def menuForward_(self, item):
time = self.player.get_time()
lenght = self.player.get_length()
time = time + 10000
if time < lenght:
self.player.set_time(time)
self.sized = None
def menuBack_(self, item):
time = self.player.get_time()
lenght = self.player.get_length()
time = time - 10000
if time > 0:
self.player.set_time(time)
self.sized = None
def menuToggle_(self, item):
if self.player.is_playing():
self.menuPause_(item, pause=True)
else:
self.menuPlay_(item)
def windowClose_(self, window):
if window is self.window:
self.terminate()
super(AppVLC, self).windowClose_(window)
def windowResize_(self, window):
if window is self.window:
self._resizer()
super(AppVLC, self).windowResize_(window)
def _resizer(self):
if self.sized:
self.window.ratio = self.sized
else:
Thread(target=self._sizer).start()
def _sizer(self, secs=0.1):
while True:
w, h = self.player.video_get_size(0)
if h > 0 and w > 0:
self.window.ratio = self.sized = w, h
break
elif secs > 0.001:
sleep(secs)
else:
break
def get_frame(self):
#?
def trackframes():
frame = 0
while True:
if frame != app.get_frame():
frame = app.get_frame()
send(frame) #send data to the Arduino
if __name__ == '__main__':
_video = OpenPanel('Select a video file').pick(_Movies)
app = AppVLC(video = _video)
Thread(target = trackframes).start()
app.run(timeout = None)

The only sensible way I have discovered to approximate the frame number in python vlc, is to take the frames per second player.get_fps() or 25, if not available and calculate the frame number based on the current playing time.
sec = player.get_time() / 1000
frame_no = int(round(sec * fps))

Related

How threads works in python

I'm trying to create app for controlling djitello basing on some tutorials and i want to create possibility to click on some gui tabs and during that control drone by keyboard.
So i thought about using threads but i can't write it. Right now when i use my app it freezes after clicking button which activates method for keyboard control. Can someone explain what am i doing wrong?
This is class for keyboardControl:
from Modules.KeyPressModule import GetKeyPressed as keyPressed
from time import sleep
import logging
class KeyboardControlService():
def __init__(self,passedTello):
self.tello = passedTello
self.startControl = False
def Initialize(self,ui):
self.ui = ui
def GetKeyboardInput(self):
lr,fb,ud,yv = 0,0,0,0
speed = 30
if(keyPressed("left")): lr = speed
elif (keyPressed("right")): lr = -speed
if(keyPressed("up")): fb = speed
elif (keyPressed("down")): fb = -speed
if(keyPressed("w")): ud = speed
elif (keyPressed("s")): ud = -speed
if(keyPressed("a")): yv = speed
elif (keyPressed("d")): yv = -speed
if(keyPressed("q")): self.tello.land()
elif (keyPressed("e")): self.tello.takeoff()
return [lr,fb,ud,yv]
def StartControl(self):
self.startControl = True
while self.startControl:
values = self.GetKeyboardInput()
print("test")
#self.tello.send_rc_control(values[0],values[1],values[2],values[3])
sleep(0.05)
def EndControl(self):
self.startControl = False
This class is for button clicking response:
from djitellopy import Tello
from threading import Thread
from Services.KeyboardControlService import KeyboardControlService as keyboardControlService
class TelloService():
def __init__(self):
self.tello = Tello()
self.keyboardControlService = keyboardControlService(self.tello)
def UseKeyboardControl(self,UseIt,UI):
if(UseIt == True):
self.keyboardControlService.Initialize(UI)
self.keyboardThread = Thread(target=self.keyboardControlService.StartControl(), daemon=True)
self.keyboardThread.start()
else:
self.keyboardControlService.EndControl()
self.keyboardThread.join()
Okey i resolved it
The trouble was here :
self.keyboardThread = Thread(target=self.keyboardControlService.StartControl(), daemon=True)
I passed function not the object

Pygame Latency Issue [duplicate]

This question already has answers here:
How can I play an mp3 with pygame?
(7 answers)
how to play wav file in python?
(5 answers)
Closed 1 year ago.
I made a midiPlayer class for an experiment I'm running. It looks like this:
class midiPlayer(object):
status = NOT_STARTED
setVolume = 1
def __init__(self, Sound):
self.Sound = Sound
def play(self):
freq = 44100
bitsize = -16
channels = 2
buffer = 1024
pygame.mixer.init(freq, bitsize, channels, buffer)
pygame.mixer.music.set_volume(self.setVolume)
pygame.mixer.music.load(self.Sound)
pygame.mixer.music.play()
self.status = STARTED
def stop(self):
pygame.mixer.music.stop()
self.status = FINISHED
def setSound(self, music_file):
self.Sound = music_file
def busy(self):
return pygame.mixer.music.get_busy()
On most people's computers, this is running absolutely fine, but on one participant's computer (using Windows 10), there is about 0.6s latency. I don't have the problem on my own machine (MacOS Sierra).
Is there anything he can do to lose the latency? He's tried reducing the buffer size, but this doesn't seem to have any effect.
Here is an example of the class being used in a minimal reproducible example.:
import pygame
from psychopy import core
class midiPlayer(object):
setVolume = 1
def __init__(self, Sound):
self.Sound = Sound
def play(self):
freq = 44100
bitsize = -16
channels = 2
buffer = 1024
pygame.mixer.init(freq, bitsize, channels, buffer)
pygame.mixer.music.set_volume(self.setVolume)
pygame.mixer.music.load(self.Sound)
pygame.mixer.music.play()
def stop(self):
pygame.mixer.music.stop()
def setSound(self, music_file):
self.Sound = music_file
def busy(self):
return pygame.mixer.music.get_busy()
# You can download the MIDI file at https://wetransfer.com/downloads/868799aa1aacf7361de9a47d3218d2ee20200818095737/c3ccee ; obviously you'll need to update the file location below to wherever the file is saved on your own computer:
MIDIFILE = midiPlayer("/Users/sam/Downloads/sounds/A4_version_2_gentlemen_normal.mid")
trialClock = core.Clock()
started_playing = 0
text_printed = 0
continueRoutine = True
trialClock.reset()
while continueRoutine:
t = trialClock.getTime()
if started_playing == 0:
MIDIFILE.play()
started_playing = 1
if t >= 4.2 and text_printed ==0:
print 'now'
text_printed = 1
# this should appear on the 8th note of the tune
if t >= 6:
MIDIFILE.stop()
continueRoutine = False

How can I properly call a method from its callback method within a class?

I Have a code with two functions. Function 'send_thread' and Function 'receive_thread' which is the callback of 'send_thread'. What I want to do is to run 'send_thread', this activates 'receive_thread' and once it's over repeat it all again. To do so, I have come up with the code below. This is not giving the desired results, since the 'send_thread' gets called again but doesn't activate the callback anymore. Thank you in advance for your help.
I have noticed, that the function gets called at the end of the receive_thread and runs for the amount of time that I wait in the send_thread (rospy.sleep()). I does never activate the callback again after the first try though.
import rospy
import pepper_2d_simulator
import threading
class TROS(object):
def __init__(self):
self.cmd_vel_pub = rospy.Publisher('cmd_vel',Twist)
self.event = threading.Event()
def send_thread(self):
#send commmand
self.event.set()
sequence = [[1,0,0.05],[0,0,0],[0,0,0.1292]]
for cmd in sequence:
rospy.Rate(0.5).sleep()
msg = Twist()
msg.linear.x = cmd[0]
msg.linear.y = cmd[1]
msg.angular.z = cmd[2]
t = rospy.get_rostime()
self.cmd_vel_pub.publish(msg)
self.event.clear()
rospy.sleep(1)
def receive_thread(self,msg):
#if something is being send, listen to this
if self.event.isSet():
frame_id = msg.header.frame_id
self.x_odom = msg.pose.pose.position.x
self.y_odom = msg.pose.pose.position.y
self.z_odom = msg.pose.pose.position.z
self.pos_odom = [self.x_odom,self.y_odom,self.z_odom,1]
self.ang_odom = msg.pose.pose.orientation.z
self.time = msg.header.stamp.secs + msg.header.stamp.nsecs
#some transformations here to get self.trans...
else:
#after self.event() is cleared, rename and run again
self.x_odom = self.trans_br_x
self.y_odom = self.trans_br_y
self.ang_odom = self.rot_br_ang
self.send_thread()
def init_node(self):
rospy.init_node('pepper_cmd_evaluator',anonymous = True)
rospy.Subscriber('odom',Odometry,self.receive_thread)
if __name__ == '__main__':
thinking = Thinking()
thinking.init_node()
thinking.send_thread()
The expected result is that I am able to loop this two function so that I call send_thread, this activates receive thread. Then send_thread stops, receive_thread stops and activates the send_thread again. I want to do this 10 times.
I have now figured out how to this. I will post my solution in case anyone else runs into a similar problem. The working solution I came up with is pretty simple. I created a self.flag variable and alternatively set it to True and False in the send_thread and callback respectively. The code:
import rospy
import pepper_2d_simulator
import threading
class TROS(object):
def __init__(self):
self.cmd_vel_pub = rospy.Publisher('cmd_vel',Twist)
self.event = threading.Event()
self.count = 0
self.flag = True
def send_thread(self):
while self.count < 10:
if self.flag:
self.count = self.count + 1
#send commmand
self.event.set()
sequence = [[1,0,0.05],[0,0,0],[0,0,0.1292]]
for cmd in sequence:
rospy.Rate(0.5).sleep()
msg = Twist()
msg.linear.x = cmd[0]
msg.linear.y = cmd[1]
msg.angular.z = cmd[2]
t = rospy.get_rostime()
self.cmd_vel_pub.publish(msg)
self.event.clear()
rospy.sleep(0.3)
self.flag = False
rospy.signal_shutdown('Command finished')
def receive_thread(self,msg):
#if something is being send, listen to this
if self.event.isSet():
frame_id = msg.header.frame_id
self.x_odom = msg.pose.pose.position.x
self.y_odom = msg.pose.pose.position.y
self.z_odom = msg.pose.pose.position.z
self.pos_odom = [self.x_odom,self.y_odom,self.z_odom,1]
self.ang_odom = msg.pose.pose.orientation.z
self.time = msg.header.stamp.secs + msg.header.stamp.nsecs
#some transformations here to get self.trans...
else:
#after self.event() is cleared, rename and run again
self.x_odom = self.trans_br_x
self.y_odom = self.trans_br_y
self.ang_odom = self.rot_br_ang
self.flag = True
def init_node(self):
rospy.init_node('pepper_cmd_evaluator',anonymous = True)
rospy.Subscriber('odom',Odometry,self.receive_thread)
if __name__ == '__main__':
thinking = Thinking()
thinking.init_node()
thinking.send_thread()

PyQt how to send/recieve signals between threads

Hi all and thanks for the help.
I am using a PyQt GUI to read and write voltages to mass flow controls and a heat flux gage. I am using QThreads to have a thread running in the background continuously updating the measurements displayed in the GUI. I am running into trouble having the background thread actually update the GUI as it does not have access to the GUI thread's functions. From what I have read online there is something about "moving" on thread into the other but am not sure what is meant by this. Can someone clarify this/point me in the right direction?
Here is my code:
from PyQt4 import QtGui, QtCore
import sys
import design4
import piplates.DAQC2plate as DAQC2
import time
import threading
air = 0.0
propane = 0
tolerance = .1
n = 1 #number of air controllers
class App4(QtGui.QMainWindow, design4.Ui_MainWindow):
def __init__(self, parent =None):
super(self.__class__, self).__init__()
self.setupUi(self)
self.sendFlow.clicked.connect(self.inputClicked)
self.displayFlow.clicked.connect(self.outputClicked)
self.hfButton.clicked.connect(self.heatFluxRead)
self.myThread = Worker()
self.myThread.start()
def inputClicked(self):
air = float(self.AirFlowInput.text())
propane = self.PropaneFlowInput.text()
if air <= 160 and air > 0:
acv = air / 20.0 / n *2 +.05#compute air control voltage for each controller
DAQC2.setDAC(0,0,acv) #changing voltage signal for channel 0
DAQC2.setDAC(0,1,acv)
else:
DAQC2.setDAC(0,0,0)
print DAQC2.getDAC(0,1)
print DAQC2.getDAC(0,0)
def outputClicked(self):
airRead = float(DAQC2.getADC(0,0)-.01) * 40 / n #converting 0-4Volts to flow
self.airFlowMeasured.display(airRead)
#print DAQC2.getADC(0,0)
#propRead = float(DAQC2.getADC(0,2)-.01) * 20
#self.propaneFlowMeasured.display(propRead)
#print DAQC2.getADC(0,1)
#palette = self.airFlowMeasured.palette()
#role = self.airFlowMeasured.backgroundRole()
#if float(DAQC2.getADC(0,0)) < (air*(1-tolerance):
# palette.setColor(role, QColor('yellow'))
#else if
def heatFluxRead(self):
heatFlux = float(DAQC2.getADC(0,7)) * 5800.0 #converts volts to kW/m^2
self.hfNumber.display(heatFlux)
print heatFlux
print self.getADC2(0,7)
def getADC2(self,addr,channel):
DAQC2.VerifyADDR(addr)
DAQC2.VerifyAINchannel(channel)
resp= DAQC2.ppCMD(addr,0x30,channel,0,2)
value=(256*resp[0]+resp[1])
if (channel==8):
value=value*5.0*2.4/65536
else:
value=(value*24.0/65536)-12.0
value=round(value*DAQC2.calScale[addr][channel]+DAQC2.calOffset[addr][channel],5)
return value
def main():
app = QtGui.QApplication(sys.argv)
form = App4()
form.show()
app.exec_()
class Update(QtCore.QObject):
sendUpdate = QtCore.pyqtSignal()
class Worker(QtCore.QThread):
def __init__(self, parent = None):
QtCore.QThread.__init__(self, parent)
self.initSignal()
def initSignal(self):
self.u = Update()
self.u.sendUpdate.connect(self.outputClicked)
def run(self):
while True:
self.u.sendUpdate.emit()
print 1
time.sleep(.25)
if __name__ == '__main__':
main()
Thanks a ton for any help!
I think that reading through DAQC2.getADC() function does not consume much time so it is not necessary to use threads, what you want to do is a periodic task, so the most appropriate option is to use QTimer:
class App4(QtGui.QMainWindow, design4.Ui_MainWindow):
def __init__(self, parent =None):
super(self.__class__, self).__init__()
self.setupUi(self)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.outputClicked)
self.timer.setInterval(250)
self.sendFlow.clicked.connect(self.inputClicked)
self.displayFlow.clicked.connect(self.timer.start)
self.hfButton.clicked.connect(self.heatFluxRead)
def inputClicked(self):
air = float(self.AirFlowInput.text())
propane = self.PropaneFlowInput.text()
if air <= 160 and air > 0:
acv = air / 20.0 / n *2 +.05#compute air control voltage for each controller
DAQC2.setDAC(0,0,acv) #changing voltage signal for channel 0
DAQC2.setDAC(0,1,acv)
else:
DAQC2.setDAC(0,0,0)
print DAQC2.getDAC(0,1)
print DAQC2.getDAC(0,0)
def outputClicked(self):
airRead = float(DAQC2.getADC(0,0)-.01) * 40 / n #converting 0-4Volts to flow
self.airFlowMeasured.display(airRead)
#print DAQC2.getADC(0,0)
#propRead = float(DAQC2.getADC(0,2)-.01) * 20
#self.propaneFlowMeasured.display(propRead)
#print DAQC2.getADC(0,1)
#palette = self.airFlowMeasured.palette()
#role = self.airFlowMeasured.backgroundRole()
#if float(DAQC2.getADC(0,0)) < (air*(1-tolerance):
# palette.setColor(role, QColor('yellow'))
#else if
def heatFluxRead(self):
heatFlux = float(DAQC2.getADC(0,7)) * 5800.0 #converts volts to kW/m^2
self.hfNumber.display(heatFlux)
print heatFlux
print self.getADC2(0,7)
def getADC2(self,addr,channel):
DAQC2.VerifyADDR(addr)
DAQC2.VerifyAINchannel(channel)
resp= DAQC2.ppCMD(addr,0x30,channel,0,2)
value=(256*resp[0]+resp[1])
if (channel==8):
value=value*5.0*2.4/65536
else:
value=(value*24.0/65536)-12.0
value=round(value*DAQC2.calScale[addr][channel]+DAQC2.calOffset[addr][channel],5)
return value

Why doesn't SIGVTALRM trigger inside time.sleep()?

I'm trying to use SIGVTALRM to snapshot profile my Python code, but it doesn't seem to be firing inside blocking operations like time.sleep() and socket operations.
Why is that? And is there any way to address that, so I can collect samples while I'm inside blocking operations?
I've also tried using ITIMER_PROF/SIGPROF and ITIMER_REAL/SIGALRM and both seem to produce similar results.
The code I'm testing with follows, and the output is something like:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:53): 1
<module>(__main__:1);test_sampling_profiler(__main__:53);busyloop(__main__:48): 1509
Note that the timesleep function isn't shown at all.
Test code:
import time
import signal
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001, logger=None):
self.interval = interval
self.running = False
self.counter = collections.Counter()
def _sample(self, signum, frame):
if not self.running:
return
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" %(
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
def start(self):
if self.running:
return
signal.signal(signal.SIGVTALRM, self._sample)
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
self.running = True
def stop(self):
if not self.running:
return
self.running = False
signal.signal(signal.SIGVTALRM, signal.SIG_IGN)
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
Not sure about why time.sleep works that way (could it be using SIGALRM for itself to know when to resume?) but Popen.wait does not block signals so worst case you can call out to OS sleep.
Another approach is to use a separate thread to trigger the sampling:
import sys
import threading
import time
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001):
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.thread = threading.Thread(target=self._sample)
def _sample(self):
while self.running:
next_wakeup_time = time.time() + self.interval
for thread_id, frame in sys._current_frames().items():
if thread_id == self.thread.ident:
continue
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" % (
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
sleep_time = next_wakeup_time - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
def start(self):
if self.running:
return
self.running = True
self.thread.start()
def stop(self):
if not self.running:
return
self.running = False
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
When doing it this way the result is:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 2875
<module>(__main__:1);test_sampling_profiler(__main__:62);start(__main__:37);start(threading:717);wait(threading:597);wait(threading:309): 1
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 4280
Still not totally fair, but better than no samples at all during sleep.
The absence of SIGVTALRM during a sleep() doesn't surprise me, since ITIMER_VIRTUAL "runs only when the process is executing."
(As an aside, CPython on non-Windows platforms implements time.sleep() in terms of select().)
With a plain SIGALRM, however, I expect a signal interruption and indeed I observe one:
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 4914
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 1
I changed the code somewhat, but you get the idea:
class SamplingProfiler(object):
TimerSigs = {
signal.ITIMER_PROF : signal.SIGPROF,
signal.ITIMER_REAL : signal.SIGALRM,
signal.ITIMER_VIRTUAL : signal.SIGVTALRM,
}
def __init__(self, interval=0.001, timer = signal.ITIMER_REAL): # CHANGE
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.timer = timer # CHANGE
self.signal = self.TimerSigs[timer] # CHANGE
....

Categories

Resources