asyncio delayed StreamReader.read - pygame

I'm kinda new to asyncio and async def/await syntax, so I wanted to ask how exactly should I do something like this:
import asyncio
import pygame
import logging
from pygame import *
log = logging.getLogger('')
class Client:
def __init__(self, host, port):
self.host = host
self.port = port
self.loop = asyncio.get_event_loop()
self.loop.run_until_complete(self.create_client())
async def create_client(self):
self.reader, self.writer = await asyncio.open_connection(self.host,
self.port,
loop=self.loop)
asyncio.ensure_future(self._handle_packets(), loop=self.loop)
async def _handle_packets(self):
while True:
data = await self.reader.read(4096)
if not data:
continue
message = data.decode()
log.debug("(NET) Received "+message)
def send(self, data):
self.loop.run_until_complete(asyncio.ensure_future(self._send(data),
loop=self.loop))
async def _send(self, data):
self.writer.write(data)
await self.writer.drain()
print("_send done")
def disconnect(self):
print("DC")
self.loop.close()
def main():
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Pyond client")
bg = Surface((640, 480))
bg.fill(Color("#004400"))
client = Client('127.0.0.1', 2508)
while True:
pygame.event.pump()
for e in pygame.event.get():
if e.type == QUIT:
raise SystemExit
elif e.type == KEYUP:
if e.key == K_UP:
client.send(b"{'Hello':'World'}")
screen.blit(bg, (0, 0))
pygame.display.update()
client.disconnect()
if __name__ == "__main__":
main()
This code creates 640x480 window with pygame, then reads for incoming K_UP (up arrow) key. Upon pressing, sends json-like string to the server. _handle_packets was supposed to read any incoming data from server and just print it.
I was testing this code and sending works okay, but receiving is quite delayed. I'm sure I need to put handler somewhere else, so where exactly?
And btw, send works only once. Need help on this one too.

A couple of problems here.
The first one is pretty fundamental. The asycnio event loop stops running after create_client() finishes, and only runs again while you send() data. So, the only time it is able to run _handle_packets is when you are send()ing. Ideally, you should start the event loop once in a higher scope and close it once you are done with everything.
The second problem is that whenever you client.send(b"{'Hello':'World'}"), you will block the outer pygame while True loop, preventing any other events to be processed until the previous one is sent. You should make use of a asyncio.Queue to queue the events & send them from the Client class.
Here are some changes I would make (sorry, untested; I don't have pygame installed ATM):
# vim: tabstop=4 expandtab
import asyncio
import pygame
import logging
from pygame import *
log = logging.getLogger('')
class Client:
def __init__(self, host, port, loop):
self.host = host
self.port = port
self.loop = loop
self.send_q = asyncio.Queue()
async def connect(self):
self.reader, self.writer = await asyncio.open_connection(self.host,
self.port,
loop=self.loop)
self.loop.create_task(self._handle_packets())
self.loop.create_task(self._send())
async def _handle_packets(self):
while True:
data = await self.reader.read(4096)
if not data:
continue
message = data.decode()
log.debug("(NET) Received "+message)
def send(self, data):
self.send_q.put_nowait(data)
async def _send(self):
while True:
data = await self.send_q.get()
self.writer.write(data)
await self.writer.drain()
def disconnect(self):
print("DC")
self.writer.close()
async def main(loop):
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Pyond client")
bg = Surface((640, 480))
bg.fill(Color("#004400"))
client = Client('127.0.0.1', 2508, loop)
await client.connect()
while True:
pygame.event.pump()
for e in pygame.event.get():
if e.type == QUIT:
raise SystemExit
elif e.type == KEYUP:
if e.key == K_UP:
client.send(b"{'Hello':'World'}")
screen.blit(bg, (0, 0))
pygame.display.update()
client.disconnect()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
loop.close()
Another important thing to keep in mind is that you should never block the asyncio event loop with pygame, otherwise the Client network processing in the background will stall. I've never used pygame, so I'm not familiar with which pygame function might be "blocking", but they should be called using result = await loop.run_in_executor(None, blocking_func, *func_args). This will call blocking functions in another thread.

Related

Failed to execute script error after successful execution exe

I am trying to create an exe file for my pygame game with pyinstaller. The command I am using is pyinstaller --noconsole 2d_minecraft.py. It creates the dist folder successfully.
I am getting this error after a successful execution of the exe file created. This does not happen without the --noconsole option.
code:
import pymc
import pygame
import random
import perlin_noise
pygame.init()
WIN = pygame.display.set_mode((960, 640))
WIN_WIDTH, WIN_HEIGHT = WIN.get_width(), WIN.get_height()
WORLD_WIDTH, WORLD_HEIGHT = 64, 64
FPS = 60
pymc.set_title("2d Minecraft")
class objects():
to_draw = []
run = True
cam_y = (WORLD_HEIGHT/2)*64
cam_x = (WORLD_WIDTH/2)*64
block_textures = {
[...]
}
class tile():
[...]
def handle_keys():
[...]
def generate_world():
[...]
def main():
clock = pygame.time.Clock()
tiles = generate_world()
while objects.run:
clock.tick(FPS)
objects.to_draw = []
for event in pygame.event.get():
if event.type == pygame.QUIT:
objects.run = False
pygame.quit()
exit()
for x in range(0, WORLD_WIDTH):
for y in range(0, WORLD_HEIGHT):
objects.to_draw.append(tiles[x][y])
handle_keys()
pymc.draw_window(WIN, objects.to_draw, fill=(0, 150, 255))
main()
main()
I figured it out!! All I needed to do was change exit() to sys.exit() (and add import sys as well).

Automatic sound echoing in pygame (unintended) [duplicate]

I tried pygame for playing wav file like this:
import pygame
pygame.init()
pygame.mixer.music.load("mysound.wav")
pygame.mixer.music.play()
pygame.event.wait()
but It change the voice and I don't know why!
I read this link solutions and can't solve my problem with playing wave file!
for this solution I dont know what should I import?
s = Sound()
s.read('sound.wav')
s.play()
and for this solution /dev/dsp dosen't exist in new version of linux :
from wave import open as waveOpen
from ossaudiodev import open as ossOpen
s = waveOpen('tada.wav','rb')
(nc,sw,fr,nf,comptype, compname) = s.getparams( )
dsp = ossOpen('/dev/dsp','w')
try:
from ossaudiodev import AFMT_S16_NE
except ImportError:
if byteorder == "little":
AFMT_S16_NE = ossaudiodev.AFMT_S16_LE
else:
AFMT_S16_NE = ossaudiodev.AFMT_S16_BE
dsp.setparameters(AFMT_S16_NE, nc, fr)
data = s.readframes(nf)
s.close()
dsp.write(data)
dsp.close()
and when I tried pyglet It give me this error:
import pyglet
music = pyglet.resource.media('mysound.wav')
music.play()
pyglet.app.run()
--------------------------
nima#ca005 Desktop]$ python play.py
Traceback (most recent call last):
File "play.py", line 4, in <module>
music = pyglet.resource.media('mysound.wav')
File "/usr/lib/python2.7/site-packages/pyglet/resource.py", line 587, in media
return media.load(path, streaming=streaming)
File "/usr/lib/python2.7/site-packages/pyglet/media/__init__.py", line 1386, in load
source = _source_class(filename, file)
File "/usr/lib/python2.7/site-packages/pyglet/media/riff.py", line 194, in __init__
format = wave_form.get_format_chunk()
File "/usr/lib/python2.7/site-packages/pyglet/media/riff.py", line 174, in get_format_chunk
for chunk in self.get_chunks():
File "/usr/lib/python2.7/site-packages/pyglet/media/riff.py", line 110, in get_chunks
chunk = cls(self.file, name, length, offset)
File "/usr/lib/python2.7/site-packages/pyglet/media/riff.py", line 155, in __init__
raise RIFFFormatException('Size of format chunk is incorrect.')
pyglet.media.riff.RIFFFormatException: Size of format chunk is incorrect.
AL lib: ReleaseALC: 1 device not closed
You can use PyAudio. An example here on my Linux it works:
#!usr/bin/env python
#coding=utf-8
import pyaudio
import wave
#define stream chunk
chunk = 1024
#open a wav format music
f = wave.open(r"/usr/share/sounds/alsa/Rear_Center.wav","rb")
#instantiate PyAudio
p = pyaudio.PyAudio()
#open stream
stream = p.open(format = p.get_format_from_width(f.getsampwidth()),
channels = f.getnchannels(),
rate = f.getframerate(),
output = True)
#read data
data = f.readframes(chunk)
#play stream
while data:
stream.write(data)
data = f.readframes(chunk)
#stop stream
stream.stop_stream()
stream.close()
#close PyAudio
p.terminate()
Works for me on Windows:
https://pypi.org/project/playsound/
>>> from playsound import playsound
>>> playsound('/path/to/a/sound/file/you/want/to/play.wav')
NOTE: This has a bug in Windows where it doesn't close the stream.
I've added a PR for a fix here:
https://github.com/TaylorSMarks/playsound/pull/53/commits/53240d970aef483b38fc6d364a0ae0ad6f8bf9a0
The reason pygame changes your audio is mixer defaults to a 22k sample rate:
initialize the mixer module
pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=4096): return None
Your wav is probably 8k. So when pygame plays it, it plays roughly twice as fast. So specify your wav frequency in the init.
Pyglet has some problems correctly reading RIFF headers. If you have a very basic wav file (with exactly a 16 byte fmt block) with no other information in the fmt chunk (like 'fact' data), it works. But it makes no provision for additional data in the chunks, so it's really not adhering to the RIFF interface specification.
PyGame has 2 different modules for playing sound and music, the pygame.mixer module and the pygame.mixer.music module. This module contains classes for loading Sound objects and controlling playback. The difference is explained in the documentation:
The difference between the music playback and regular Sound playback is that the music is streamed, and never actually loaded all at once. The mixer system only supports a single music stream at once.
If you want to play a single wav file, you have to initialize the module and create a pygame.mixer.Sound() object from the file. Invoke play() to start playing the file. Finally, you have to wait for the file to play.
Use get_length() to get the length of the sound in seconds and wait for the sound to finish:
(The argument to pygame.time.wait() is in milliseconds)
import pygame
pygame.mixer.init()
my_sound = pygame.mixer.Sound('mysound.wav')
my_sound.play()
pygame.time.wait(int(my_sound.get_length() * 1000))
Alternatively you can use pygame.mixer.get_busy to test if a sound is being mixed. Query the status of the mixer continuously in a loop:
import pygame
pygame.init()
pygame.mixer.init()
my_sound = pygame.mixer.Sound('mysound.wav')
my_sound.play()
while pygame.mixer.get_busy():
pygame.time.delay(10)
pygame.event.poll()
Windows
winsound
If you are a Windows user,the easiest way is to use winsound.You don't even need to install it.
Not recommended, too few functions
import winsound
winsound.PlaySound("Wet Hands.wav", winsound.SND_FILENAME)
# add winsound.SND_ASYNC flag if you want to wait for it.
# like winsound.PlaySound("Wet Hands.wav", winsound.SND_FILENAME | winsound.SND_ASYNC)
mp3play
If you are looking for more advanced functions, you can try mp3play.
Unluckily,mp3play is only available in Python2 and Windows.
If you want to use it on other platforms,use playsound despite its poor functions.If you want to use it in Python3,I will give you the modified version which is available on Python 3.(at the bottom of the answer)
Also,mp3play is really good at playing wave files, and it gives you more choices.
import time
import mp3play
music = mp3play.load("Wet Hands.wav")
music.play()
time.sleep(music.seconds())
Cross-platform
playsound
Playsound is very easy to use,but it is not recommended because you can't pause or get some infomation of the music, and errors often occurs.Unless other ways doesn't work at all, you may try this.
import playsound
playsound.playsound("Wet Hands.wav", block=True)
pygame
I'm using this code and it works on Ubuntu 22.04 after my test.
If it doesn't work on your machine, consider updating your pygame lib.
import pygame
pygame.mixer.init()
pygame.mixer.music.load("Wet Hands.wav")
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pass
pyglet
This works on Windows but it doesn't work on my Ubuntu, so I can do nothing.
import pyglet
import time
sound = pyglet.media.load("Wet Hands.wav", "Wet Hands.wav")
sound.play()
time.sleep(sound.duration)
Conclusion
It seems that you are using Linux,so playsound may be your choice.My code maybe cannot solve your problem by using pygame and pyglet,because I always use Windows.If none of the solutions work on your machine,I suggest you run the program on Windows...
To other users seeing my answer, I have done many tests among many libraries,so if you are using Windows,you may try mp3play which can play both mp3 and wave files, and mp3play is the most pythonic, easy, light-weight and functional library.
mp3play in Python3
just copy the code below and create a file named mp3play.py in your working directory and paste the content.
import random
from ctypes import windll, c_buffer
class _mci:
def __init__(self):
self.w32mci = windll.winmm.mciSendStringA
self.w32mcierror = windll.winmm.mciGetErrorStringA
def send(self, command):
buffer = c_buffer(255)
command = command.encode(encoding="utf-8")
errorcode = self.w32mci(command, buffer, 254, 0)
if errorcode:
return errorcode, self.get_error(errorcode)
else:
return errorcode, buffer.value
def get_error(self, error):
error = int(error)
buffer = c_buffer(255)
self.w32mcierror(error, buffer, 254)
return buffer.value
def directsend(self, txt):
(err, buf) = self.send(txt)
# if err != 0:
# print('Error %s for "%s": %s' % (str(err), txt, buf))
return err, buf
class _AudioClip(object):
def __init__(self, filename):
filename = filename.replace('/', '\\')
self.filename = filename
self._alias = 'mp3_%s' % str(random.random())
self._mci = _mci()
self._mci.directsend(r'open "%s" alias %s' % (filename, self._alias))
self._mci.directsend('set %s time format milliseconds' % self._alias)
err, buf = self._mci.directsend('status %s length' % self._alias)
self._length_ms = int(buf)
def volume(self, level):
"""Sets the volume between 0 and 100."""
self._mci.directsend('setaudio %s volume to %d' %
(self._alias, level * 10))
def play(self, start_ms=None, end_ms=None):
start_ms = 0 if not start_ms else start_ms
end_ms = self.milliseconds() if not end_ms else end_ms
err, buf = self._mci.directsend('play %s from %d to %d'
% (self._alias, start_ms, end_ms))
def isplaying(self):
return self._mode() == 'playing'
def _mode(self):
err, buf = self._mci.directsend('status %s mode' % self._alias)
return buf
def pause(self):
self._mci.directsend('pause %s' % self._alias)
def unpause(self):
self._mci.directsend('resume %s' % self._alias)
def ispaused(self):
return self._mode() == 'paused'
def stop(self):
self._mci.directsend('stop %s' % self._alias)
self._mci.directsend('seek %s to start' % self._alias)
def milliseconds(self):
return self._length_ms
def __del__(self):
self._mci.directsend('close %s' % self._alias)
_PlatformSpecificAudioClip = _AudioClip
class AudioClip(object):
__slots__ = ['_clip']
def __init__(self, filename):
self._clip = _PlatformSpecificAudioClip(filename)
def play(self, start_ms=None, end_ms=None):
if end_ms is not None and end_ms < start_ms:
return
else:
return self._clip.play(start_ms, end_ms)
def volume(self, level):
assert 0 <= level <= 100
return self._clip.volume(level)
def isplaying(self):
return self._clip.isplaying()
def pause(self):
return self._clip.pause()
def unpause(self):
return self._clip.unpause()
def ispaused(self):
return self._clip.ispaused()
def stop(self):
return self._clip.stop()
def seconds(self):
return int(round(float(self.milliseconds()) / 1000))
def milliseconds(self):
return self._clip.milliseconds()
def load(filename):
"""Return an AudioClip for the given filename."""
return AudioClip(filename)

How can I play an mp3 as a background process with Python3?

I'm trying to play an mp3 as a background process, but I'm not being able to do it. Is it even possible?
import pygame
from daemonize import Daemonize
from time import sleep
pid = '/tmp/mpee3.pid'
def startSong():
pygame.mixer.init()
pygame.mixer.music.load('test.mp3')
pygame.mixer.music.play(0)
def main():
while pygame.mixer.music.get_busy():
sleep(0.1)
if __name__ == '__main__':
startSong()
daemon = Daemonize(app='mpee3', pid=pid, action=main)
daemon.start()
I'm not getting error messages, but the song doesn't play.
If I do in the end
if __name__ == '__main__':
startSong()
main()
The song plays, but if I try to use daemonize, it doesn't play.
You need to call pygame.init() to initialize everything else. Also, use pygame.mixer.play(-1) to loop the song indefinitely.
I don't know Daemonize, but based on the name, it probably spawns a detached thread. You're immediately exiting the main thread, forcing your song to stop. Try adding while 1: pass after daemon.start()
Try:
import pygame
pygame.init()
from daemonize import Daemonize
from time import sleep
pid = '/tmp/mpee3.pid'
def startSong():
pygame.mixer.init()
pygame.mixer.music.load('test.mp3')
pygame.mixer.music.play(-1)
def main():
while pygame.mixer.music.get_busy():
sleep(0.1)
if __name__ == '__main__':
startSong()
daemon = Daemonize(app='mpee3', pid=pid, action=main)
daemon.start()
while 1:
pass

Working with coroutines in Python Tornado Web Server

I am working on an autonomous car implementation for a web browser game with Python 2x. I use Tornado Web Server to run game on localhost and I post and receive data from game with JSON data format in the function called "FrameHandler" and also I determine what the act of car should be in "to_dict_faster()" function.
Here, my problem is that I can write data to text file which is hold in speed_data variable in specific time interval with help of a coroutine. However, I can't dump JSON data to function in this specific time interval because "FrameHandler" acts like While True and it always requests data to dump. What I am trying to do is sending desired acts as writing text file in specific time interval while not changing flow frame handler because it affects FPS of the game.
I am trying to figure out How can I do that for a long time any help would be great here:
#gen.coroutine
def sampler():
io_loop = tornado.ioloop.IOLoop.current()
start = time.time()
while True:
with open("Sampled_Speed.txt", "a") as text_file:
text_file.write("%d,%.2f\n" % (speed_data, ((time.time() - start))))
yield gen.Task(io_loop.add_timeout, io_loop.time() + period)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.redirect("/static/v2.curves.html")
class FrameHandler(tornado.web.RequestHandler):
def post(self):
global speed_data
data = json.loads(self.get_arguments("telemetry")[0])
ar = np.fromstring(base64.decodestring(self.request.body), dtype=np.uint8)
image = ar.reshape(hp.INPUT_SIZE, hp.INPUT_SIZE, hp.NUM_CHANNELS)
left, right, faster, slower = data["action"]
terminal, action, all_data, was_start = (
data["terminal"],
Action(left=left, right=right, faster=faster, slower=slower),
data["all_data"],
data["was_start"]
)
for i in range(len(all_data)):
data_dict=all_data[i]
speed_data = data_dict[u'speed']
position_data=data_dict[u'position']
result_action = agent.steps(image, 0.1, terminal, was_start, action, all_data)
if speed_data < 4000:
self.write(json.dumps(result_action.to_dict_faster()))
else:
self.write(json.dumps(result_action.to_dict_constant()))
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/frame", FrameHandler),
(r"/static/(.*)", tornado.web.StaticFileHandler, {"path": static_path})
], debug=True)
if __name__ == "__main__":
app = make_app()
if "SERVER_PORT" in os.environ:
port = int(os.environ["SERVER_PORT"])
else:
port = 8880
print "LISTENING ON PORT: %d" % port
app.listen(port)
tornado.ioloop.IOLoop.current().run_sync(sampler)
tornado.ioloop.IOLoop.current().start()
You can move file writing to a different thread (using tornado's run_on_executor for example), so python interpreter will automatically switch from Sampler to main thread with FrameHandler on write. But you have to use thread-safe speed_data variable, I've used stdlib Queue.Queue as an example:
class Handler(tornado.web.RequestHandler):
#gen.coroutine
def get(self):
global speed_data
speed_data.put("REALLY BIG TEST DATA\n")
self.finish("OK")
class Sampler():
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
def __init__(self, queue):
self._q = queue
#run_on_executor
def write_sample(self):
with open("foobar.txt", "w") as f:
while True:
data = self._q.get()
f.write(data)
if __name__ == '__main__':
application = Application(
[("/status", Handler)]
)
server = HTTPServer(application)
server.listen(8888)
speed_data = Queue.Queue()
smp = Sampler(speed_data)
IOLoop.current().add_callback(smp.write_sample)
IOLoop.current().start()

Pygame plays mp3 too fast (regardless of what frequency i define)

I´m playing an mp3 file I get after a request to google translate. When I play it through pygame it plays at twice the speed, regardless of what frequency I set in pygame.mixer.init()
Here's the code:
import tweepy, urllib, os, pygame, pycurl, difflib, time
pygame.init()
pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=4096)
consumer_key = 'xxxxxxxxx'
consumer_secret = 'xxxxxxxx'
access_token = 'xxxxxx'
access_token_secret = 'xxxxxx'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
# function to download a file from a url, used for testing
def downloadFile(url, fileName):
fp = open(fileName, "wb")
curl = pycurl.Curl()
curl.setopt(pycurl.URL, url)
curl.setopt(pycurl.WRITEDATA, fp)
curl.perform()
curl.close()
fp.close()
# returns the appropriate google speech url for a particular phrase
def getGoogleSpeechURL(phrase):
googleTranslateURL = "http://translate.google.com/translate_tts?tl=en&"
parameters = {'q': phrase}
data = urllib.urlencode(parameters)
googleTranslateURL = "%s%s" % (googleTranslateURL,data)
return googleTranslateURL
def speakSpeechFromText(phrase):
googleSpeechURL = getGoogleSpeechURL(phrase.encode('utf-8').strip())
downloadFile(googleSpeechURL,"tts.mp3")
pygame.mixer.music.load("tts.mp3")
pygame.mixer.music.play(0,0.0)
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
#os.system("mplayer tts.mp3 -af extrastereo=0 &")
def diffindex(string1, string2):
for i, (char1, char2) in enumerate(zip(string1, string2)):
if char1 != char2:
return 1
return 2
A = "a"
B = "b"
api = tweepy.API(auth)
while(true)
results = api.search(q="Hackney", count=1, result_type="recent")
for result in results:
A = result.text
if diffindex(A, B) != 2:
speakSpeechFromText(A)
B = A
else:
print ("jaha")
time.sleep(20)
Sorry if there´s a lot of irrelevant stuff there as well but I thought it might be of use.
I got it:
pygame.mixer.pre_init(16000, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.mixer.init()
pygame.mixer.music.load("mymp3file.mp3")
pygame.mixer.music.play()
# I adjusted the 16000 - it was 44100, tryied 48000, 22000, 110000 and themn 16000
You should call the mixer.init() before pygame.init() because pygame.init() also initializing the mixer.
If you have to call mixer.init() after pygame.init() first call mixer.quit().
It's all in the docs :).