I was looking for a solution to play mp3 files in python and many stackoverflow answers (to other questions) seemed to recommend pyglet. I am writing a program that takes a piece of text, breaks it into individual words and then downloads mp3s of those words (if they aren't already downloaded) using gTTs and plays them.
from pyglet import media, app, clock
from gtts import gTTS
import os
import time
from num2words import num2words
cwd = os.getcwd()
beep = media.load('beep.mp3', streaming = False)
def get_mp3(text):
player = media.Player()
lowertext = text.lower()
words = lowertext.split()
player.queue(beep)
for word in words:
save_path = cwd + '\\tts_downloads\\{}.mp3'.format(word)
if os.path.isfile(save_path) == False:
tts = gTTS(word, 'en-us')
tts.save(save_path)
mp3 = media.load(save_path)
player.queue(mp3)
player.queue(beep)
player.play()
app.run()
However I find that after playing the files pyglet won't let my program progress. How can I exit the pyglet app after playback has finished, so that my code can progress?
Alternatively is there some other way that I can play mp3 files in python?
This is souly because app.run() is a never-ending loop in order to keep the GL context alive. There's only one way around this and that is to create your own class that inherits the Pyglet properties.
I'll give you a sample code and if you have any questions feel free to ask away.
import pyglet
from pyglet.gl import *
# Optional audio outputs (Linux examples):
# pyglet.options['audio'] = ('alsa', 'openal', 'silent')
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 800, fullscreen = False)
self.x, self.y = 0, 0
self.bg = pyglet.sprite.Sprite(pyglet.image.load('background.jpg'))
self.sprites = {}
self.player = pyglet.media.Player()
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
# Do something when a key is pressed?
# Pause the audio for instance?
# use `if symbol == key.SPACE: ...`
# This is just an example of how you could load the audio.
# You could also do a standard input() call and enter a string
# on the command line.
if symbol == key.ENTER:
self.player.queue(media.load('beep.mp3', streaming = False))
if nog self.player.playing:
self.player.play()
if symbol == key.ESC: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.bg.draw()
# self.sprites is a dictionary where you store sprites
# to be rendered, if you have any.
for sprite_name, sprite in self.sprites.items():
sprite.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
self.player.delete() # Free resources. (Not really needed but as an example)
x = main()
x.run()
Now this is just the most basic example of how to load audio sources and play them. You press Enter and that triggers beep.mp3.
Normally, you'd also want to hook a function to self.player.eos() that does something when you run out of sources.
Also note that it only calls play() once if it's not already playing. These are attributes you want to honor.
Ah and Escape exits the application.
Related
I am trying to design a gui which is related to my computer vision project. In that, the video I want to stop the web camera feed and I want to resume it by pressing a button. I managed to stop the feed, but I cannot resume it. The camera gets turned on but it is not working. This is the code for the program.
from PyQt5 import uic
from PyQt5 import QtCore, QtWidgets, QtGui
import cv2
import sys
class opencv_feed(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = uic.loadUi('../designs/design5_flexible_opencv_window2.ui', self) #change this whenever u want... keep the ui file with you
self.resize(900,600)
self.worker1 = worker1() #creating an instance
self.worker1.start()
self.worker1.ImgUpdate.connect(self.ImageUpdateSlot)
self.but_stop.clicked.connect(self.cancel_feed)
self.but_resume.clicked.connect(self.resume_feed)
def ImageUpdateSlot(self, Image):
self.label.setPixmap(QtGui.QPixmap.fromImage(Image))
def cancel_feed(self):
self.worker1.stop()
def resume_feed(self):
self.__init__()
#self.worker1.ImgUpdate.connect(self.ImageUpdateSlot)
class worker1(QtCore.QThread):
ImgUpdate = QtCore.pyqtSignal(QtGui.QImage)
#QtCore.pyqtSlot()
def run(self): #put self in every variable to stop crashing the gui, when we interact with gui
self.ThreadActive = True
self.feed = cv2.VideoCapture(0)
while self.ThreadActive:
self.ret, self.frm = self.feed.read()
if self.ret:
self.img = cv2.cvtColor(self.frm, cv2.COLOR_BGR2RGB)
#print(img1.shape)
self.img = cv2.flip(self.img,1)
self.qtformat_conv_img = QtGui.QImage(self.img.data, self.img.shape[1], self.img.shape[0], QtGui.QImage.Format_RGB888)
#print(self.img.shape)
self.pic = self.qtformat_conv_img.scaled(self.img.shape[1],self.img.shape[0],QtCore.Qt.KeepAspectRatio) #keep this as an attribute, else when resizing the app stops
self.ImgUpdate.emit(self.pic)
def stop(self):
self.ThreadActive = False
self.feed.release()
self.quit()
#os._exit(0)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
wind = opencv_feed()
wind.show()
sys.exit(app.exec_())
Can someone explain me what am I doing wrong.
Link to the UI file..
https://drive.google.com/file/d/1UP8RjQML1GzFA75eGURgWt4Y0o_Ip3sU/view?usp=sharing
You can only start a thread once. Once it finishes you need to create another thread object to actually run. I would add another flag after self.ThreadActive called something like "pause" to keep the thread alive, just without doing anything.
#QtCore.pyqtSlot()
def run(self): #put self in every variable to stop crashing the gui, when we interact with gui
self.ThreadActive = True
self.paused = False
self.feed = cv2.VideoCapture(0)
while self.ThreadActive:
if not self.paused:
self.ret, self.frm = self.feed.read()
if self.ret:
self.img = cv2.cvtColor(self.frm, cv2.COLOR_BGR2RGB)
#print(img1.shape)
self.img = cv2.flip(self.img,1)
self.qtformat_conv_img = QtGui.QImage(self.img.data,
self.img.shape[1],
self.img.shape[0],
QtGui.QImage.Format_RGB888)
#print(self.img.shape)
self.pic = self.qtformat_conv_img.scaled(self.img.shape[1],self.img.shape[0],QtCore.Qt.KeepAspectRatio) #keep this as an attribute, else when resizing the app stops
self.ImgUpdate.emit(self.pic)
this way when you want to pause the thread you can pause and unpause using that flag
Either that or you need to always create another instance of the worker. Does it work if you create the instance outside of the init ? I'm unsure what happens to the GUI if the init is called twice.
EDIT:
You'll also have to change the way you pause and start again
def cancel_feed(self):
self.worker1.paused = True
def resume_feed(self):
self.worker1.paused = False
I'm making a program with pafy, vlc, PySimpleGUI that takes a youtube url and plays it as mp3
the problem I got when I first tried the console mode is that the mp3 stops after a while and I fixed it with time.sleep(seconds) and now everything works fine in the console version.
The problem gets when I tried to make it a GUI with PySimpleGUI when I used time.sleep(seconds) the GUI freezes until it the mp3 ends, I searched and found that window.read() may fixes the problem and it did but I couldn't then resume the mp3 after pausing it (like the console mode), when I press play it plays and when I press pause it pauses and when I press play again it starts from the beginning but I want it to start from when it paused is it because of the window.read() ?
sorry if I couldn't explain it clearly.
The console mode:
import pafy
import vlc
player = vlc.Instance()
media_player = player.media_player_new()
def readurl():
url=input("URL : ")
vid=pafy.new(url)
l=vid.length
aud=vid.getbestaudio()
media = player.media_new(aud.url)
media.get_mrl()
media_player.set_media(media)
def ans(a):
if(a.upper()=="S"):
media_player.pause()
elif(a.upper()=="P"):
media_player.play()
elif(a.upper()=="R"):
media_player.pause()
readurl()
ans("P")
else:
exit()
readurl()
while True:
a=input("Choose from options P to play, S to stop(pause), Q to quit, R to get another video. ")
ans(a)
The GUI mode:
import PySimpleGUI as sg
import vlc
import pafy
import time
player = vlc.Instance()
media_player = player.media_player_new()
sg.theme('DarkBlue')
def btn(name):
return sg.Button(name, size=(6, 1), pad=(1, 1))
layout = [[sg.Button(button_text='Get video from youtube url : ', size=(20,1), key='geturl'),sg.Input(default_text='', size=(50, 10), key='-VIDEO_LOCATION-')],
[btn('play'), btn('pause')]]
window = sg.Window('Youtube Radio Mode', layout, element_justification='center', finalize=True, resizable=True)
#------------ Media Player Setup ---------#
"""
"""
def ans(a,q):
if(a.upper()=="S"):
media_player.pause()
#window.read(q)
elif(a.upper()=="P"):# or (a.upper()=="R")):
media_player.play()
window.read(q)
else:
exit()
#------------ The Event Loop ------------#
#def vi()
while True:
event, values = window.read(timeout=1000)
url=values['-VIDEO_LOCATION-']
z=''
if event == sg.WIN_CLOSED:
break
if (url!='' or event == 'geturl'):
vid=pafy.new(url)
#l=vid.length
aud=vid.getbestaudio()
media = player.media_new(aud.url)
media.get_mrl()
media_player.set_media(media)
z='l'
q=vid.length*1000
if event == 'play':
if(z!='l'):
sg.popup_error('PLEASE GET A URL FIRST')
continue
ans("P",q)
#window.refresh()
z=''
if event == 'pause':
ans("S",q)
window.close()
Define your GUI, then go your event loop and make sure what event will happen in you GUI, then what's the next step to go for which event.
Here, revised code for your reference, no test for the code.
import PySimpleGUI as sg
import vlc
import pafy
import time
def vlc_player():
player = vlc.Instance()
media_player = player.media_player_new()
return media_player
def Button(button_text):
return sg.Button(button_text, size=(6, 1), pad=(1, 1))
media_player = vlc_player()
sg.theme('DarkBlue')
sg.set_options(font=("Courier New", 16))
layout = [
[sg.Button(button_text='Get video from youtube url : ', size=(20,1), key='-GET_URL-'),
sg.Input(default_text='', size=(50, 10), key='URL')],
[Button('PLAY'), BUTTON('PAUSE'), Button('QUIT')],
[sg.Text("", size=(0, 1), key='-STATUS-')],
]
window = sg.Window('Youtube Radio Mode', layout, element_justification='center',
finalize=True, resizable=True)
status = window['-STATUS-']
mp3_load = False
#------------ The Event Loop ------------#
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'QUIT'):
break
status.update(value='')
if event == '-GET_URL-':
"""
if player.is_playing():
player.stop()
"""
url = values['-URL-'].strip()
try:
vid = pafy.new(url)
aud = vid.getbestaudio()
media = player.media_new(aud.url)
media.get_mrl()
media_player.set_media(media)
mp3_load = True
continue
except:
pass
status.update('URL load failed !')
mp3_load = False
elif event == 'PLAY' and mp3_load:
media_player.play()
elif event == 'PAUSE' and mp3_load:
media_player.pause()
if player.is_playing():
media_player.stop()
window.close()
You may use multi-thread to load mp3 from Youtube if it will take long time, and set some flags to confirm if in downloading or download complete, or disable button '-GET_URL-' or else buttons.
Don't update GUI directly in another thread, you can call window.write_event_value to generate new event, then update GUI in event loop.
Refer https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Media_Player_VLC_Based.py
Ive tried looking for an answer to this without much luck.
Im trying to build an application in kivy that starts and stops a sound as the button is toggled. The sound does what I want the first time the button is toggled, but the second time the button only starts the sound, but does not stop the sound.
Here is my code so far.
'''code'''
class MainApp(App):
def build(self):
layout = BoxLayout(padding=10)
self.oceanButton = ToggleButton(text='Ocean',
background_normal='C:/Users/micha/Desktop/Code/Soothing Sounds/picture/oceanpic.jpg')
self.oceanButton.bind(on_press=self.on_press_button)
layout.add_widget(self.oceanButton)
return layout
def on_press_button(self, *args):
waveSound = SoundLoader.load(
'C:/Users/micha/Desktop/Code/Soothing Sounds/sounds/ocean.wav'
)
if self.oceanButton.state == 'down':
waveSound.play()
waveSound.loop = True
print('On')
else:
waveSound.stop()
print('Off')
The problem is that your on_press_button() method always creates a new instance of Sound (using SoundLoader). So, when the ToggleButton state is not down, it calls the stop() method on that new instance, and the Sound that was created on the previous call continues to play.
You can fix that by keeping a reference to the created Sound instance, and using that instance to call stop():
def on_press_button(self, *args):
if self.oceanButton.state == 'down':
self.waveSound = SoundLoader.load(
'C:/Users/micha/Desktop/Code/Soothing Sounds/sounds/ocean.wav'
)
self.waveSound.play()
self.waveSound.loop = True
print('On')
else:
self.waveSound.stop()
print('Off')
I have a pretty strange use-case here, I am trying to put together a handful of simple programs for my students that will help them learn python. To get it working I have a PyGame window embedded in a TKinter frame, and I need to redirect stdout to change something in the PyGame window. I have the redirecting working, if I redirect it to a file it works fine, but if I try to change the text it doesn't work. I hard-coded a string into the PyGame text changing code and that works, but it won't work with the redirected text for some reason.
The redirecting class:
class PrintTest:
def __init__(self, file, game):
self.f = file
self.game = game
def write(self, t):
f.write(t)
self.game.text = game.font.render(t, True, self.game.text_colors[1], self.game.text_colors[2])
self.game.textRect = self.game.text.get_rect()
self.game.textRect.center = (300, 300)
def flush(self):
pass
the game class:
class Game:
def __init__(self, root):
self.root = root
embed = tk.Frame(root, width=600, height=600)
embed.pack(side=tk.LEFT)
os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
if platform.system == "Windows":
os.environ['SDL_VIDEODRIVER'] = 'windib'
self.text_colors = [(255,255,255),
(0,255,0),
(0,0,128)]
# initialize a pygame display
pygame.init()
self.screen = pygame.display.set_mode((600, 600))
self.screen.fill(pygame.Color('red'))
self.clock = pygame.time.Clock()
self.font = pygame.font.Font('freesansbold.ttf', 32)
self.text = self.font.render('Hello, world!', True, self.text_colors[1], self.text_colors[2])
self.textRect = self.text.get_rect()
self.textRect.center = (300, 300)
# TK Creation
helv = font.Font(family='Helvetica', size=18, weight='bold')
self.button = tk.Button(root,
text="Change text",
font=helv,
command=self.change_text).pack()
self.user_input = tk.StringVar()
self.textbox = ttk.Entry(root, width=15, textvariable=self.user_input)
self.textbox.pack()
# ---------------------------------------------------------------
def change_text(self):
print(self.user_input.get())
def run(self):
# Pygame loop
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
self.screen.fill(pygame.Color('red'))
self.screen.blit(self.text, self.textRect)
pygame.display.update()
try:
self.root.update()
except tk.TclError:
running = False
pygame.quit()
and I set up the stdout like this:
try:
root = tk.Tk()
root.geometry('1000x600')
game = Game(root)
f = open('text.txt', 'w')
sl = PrintTest(f, game)
sys.stdout = sl
game.run()
except Exception:
traceback.print_exc()
pygame.quit()
When I run this as it is, if I type hello in the box, hello is printed to the file, but a null character is put into the pygame box. I don't really know PyGame well enough to know if it is an issue on that end or a redirecting issue. Any help would be greatly appreciated!
(In case you are wondering what the use-case is here, I am going to have them 'finish' some programs to make something happen in PyGame. So if they type print('Hello Friend!') into the given field, it will redirect that to be dialog for someone in the PyGame box. It may not work in the long run, but I gotta get past this to really figure that out)
Edit:
So the problem is that the write function is being called twice for some reason when I click the button, it is calling print on the typed in string then again on an empty string. Still not sure how to fix it, but at least I found the problem
Okay, so I found the problem.
It looks as though print sends two things to stdout, the string to print and the ending character. So it was overwriting the string I wanted printed with a newline character. I changed my PrintTest class to accommodate for this:
class PrintTest:
def __init__(self, file, game):
self.f = file
self.game = game
def write(self, t):
if t != '\n':
self.f.write(t)
self.game.text, self.game.textRect = game.font.render(t, self.game.text_colors[2])
self.game.textRect.center = (300, 300)
def flush(self):
pass
I am relatively new to Python and Pyglet, I am trying to create an app that dynamically displays photos accordingly to the ID of a command sent via serial.
Here is my code:
import pyglet
from pyglet import clock
import serial
import json
import os
base_dir = 'data'
data = []
currentDatum = ''
def initialiseData():
global data
#load the json
with open('dataset.json') as f:
data = json.load(f)
#for every file in the json load the image
for d in data:
d['media'] = pyglet.image.load(os.path.join(base_dir, d['name']))
print("Scan a tag")
def readSerial(dt):
global currentDatum
tag = ser.readline()
tag = tag.strip()
for d in data:
if d['id'] == tag:
currentDatum = d
print(currentDatum)
ser = serial.Serial('/dev/cu.usbmodem1421', 9600)
initialiseData()
window = pyglet.window.Window(1000, 800, resizable = True)
#window.event
def on_draw():
window.clear()
currentDatum['media'].anchor_x = currentDatum['media'].width/2 - window.width/2
currentDatum['media'].anchor_y = currentDatum['media'].height/2 - window.height/2
currentDatum['media'].blit(0, 0)
clock.schedule(readSerial)
pyglet.app.run()
The application works fine, in the sense that it loads the data from the son and when I send the serial ID it gets read instantly, but every mouse interaction freezes the app: I cannot close the window, cannot resize it, it just gets stuck. Any advice?
I have no idea what the program actually does, or how to use it.
And since I can't replicate your environment (you've got a usbmodem for instance), I'll try my best approach at a generic solution that should work and plan ahead for future development on your project:
import pyglet
from pyglet.gl import *
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.x, self.y = 0, 0
self.batch = pyglet.graphics.Batch()
self.data = []
self.currentDatum = ''
self.ser = serial.Serial('/dev/cu.usbmodem1421', 9600)
self.initialiseData()
pyglet.clock.schedule(self.readSerial)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
pass
def on_mouse_release(self, x, y, button, modifiers):
pass
def on_mouse_press(self, x, y, button, modifiers):
pass
def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
pass
def on_key_release(self, symbol, modifiers):
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
# For future reference, use batches:
self.batch.draw()
# But this is how it's implemented atm:
self.currentDatum['media'].anchor_x = self.currentDatum['media'].width/2 - self.width/2
self.currentDatum['media'].anchor_y = self.currentDatum['media'].height/2 - self.height/2
self.currentDatum['media'].blit(0, 0)
self.flip()
def initialiseData(self):
#load the json
with open('dataset.json') as f:
self.data = json.load(f)
#for every file in the json load the image
for d in self.data:
d['media'] = pyglet.image.load(os.path.join(base_dir, d['name']))
print("Scan a tag")
def readSerial(self, dt):
tag = self.ser.readline()
tag = tag.strip()
for d in self.data:
if d['id'] == tag:
self.currentDatum = d
print(self.currentDatum)
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
It's bound to be a few run time errors in this code, syntax that I've might have messed up. But I've ported as much of your code as possible into a object oriented way of thinking. Aka a class that inherits the Window class in order for you to modify elements within and the window itself.
There's also some placeholder-functions that you can use to handle mouse and keyboard events. These are inherited/overlapping the pyglet.window.Window class. So any function supported by Window can be put into this class.
There's also a custom pyglet.app.run() that handles the event polling, making sure you don't get stuck on keyboard, mouse or window (resize, move etc) events.
Hope this works.