Python, Flask, ffmpeg video streaming: Video does not work in Firefox - python

I am writing a preview portion for video management system, it works like a charm in chrome with standard tag but firefox does not recognize MIME type for some reason and it bugs me a lot.
Here is my stream class:
class Stream:
run = False
FNULL = open(os.devnull, 'w')
overlay = ffmpeg.input("somelogo.png")
def __init__(self, camid):
camUtil = CameraUtil()
self.camid = camid
self.streamurl = camUtil.get_stream_from_id(self.camid)['streamURL']
print(self.streamurl)
self.args = ffmpeg.input(self.streamurl)
# vcodec="libvpx",
# acodec="libvorbis",
self.args = ffmpeg.output(self.args, "-",
f="matroska",
vcodec="copy",
acodec="copy",
blocksize="1024",
# strftime="1",
# segment_time="60",
# segment_format="matroska"
preset="ultrafast",
metadata="title='test'"
)
self.args = ffmpeg.get_args(self.args)
print(self.args)
self.pipe = subprocess.Popen(['ffmpeg'] + self.args,
stdout=subprocess.PIPE,)
#stderr=self.FNULL)
def dep_stream(self):
def gen():
try:
f = self.pipe.stdout
byte = f.read(1024)
while byte:
yield byte
byte = f.read(1024)
finally:
self.pipe.kill()
return Response(gen(), status=200,
mimetype='video/webm',
headers={'Access-Control-Allow-Origin': '*',
"Content-Type": "video/webm",
})
My html playback portion:
<video id="live_page_player" id="video" preload="auto" autoplay width="1280" height="720">
<source src="/stream/{{ camid }}" type='video/webm;codecs="vp8, vorbis"'/>
YOUR BROWSER DOES NOT SUPPORT HTML5, WHAT YEAR ARE YOU FROM?!
</video>
Firefox says "No video with supported format and MIME type found" and in console it says
error: Error Code: NS_ERROR_DOM_MEDIA_METADATA_ERR (0x806e0006)
Did I do something dumb?! Or am I missing something, because it works google chrome like a charm
I need fresh eyes.
Help plez

So after bashing my head around, I decided to check out the console(yes I know)
I found that firefox throws NS_ERROR_DOM_MEDIA_METADATA_ERR, after vigorous googling I have found that raw matroska is not supported by firefox(or rather they don't let you play it for some reason) while google chrome does support it.
The solution was actually pretty simple, you gotta re-encode the stream with vcodec libvpx-vp9 or vp8 and acoded libopus or libvorbis
The ffmpeg for python syntax would look like this:
self.args = ffmpeg.output(self.args, "-",
f="webm",
vcodec="libvpx-vp9",
acodec="loboupus",
blocksize="1024",
# strftime="1",
# segment_time="60",
# segment_format="matroska"
preset="ultrafast",
metadata="title='test'"
)
Note that this eats the CPU quite heavily, Im still playing around with it but this is the solution!

Related

How to get sound with pyaudio front end? [duplicate]

I'm sending my servers microphone's audio to the browser (mostly like this post but with some modified options).
All works fine, until you head over to a mobile or safari, where it doesn't work at all. I've tried using something like howler to take care of the frontend but with not success (still works in chrome and on the computer but not on the phones Safari/Chrome/etc). <audio> ... </audio> works fine in chrome but only on the computer.
function play_audio() {
var sound = new Howl({
src: ['audio_feed'],
format: ['wav'],
html5: true,
autoplay: true
});
sound.play();
}
How does one send a wav-generated audio feed which is 'live' that works in any browser?
EDIT 230203:
I have narrowed the error down to headers (at least what I think is causing the errors).
What headers should one use to make the sound available in all browsers?
Take this simple app.py for example:
from flask import Flask, Response, render_template
import pyaudio
import time
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html', headers={'Content-Type': 'text/html'})
def generate_wav_header(sampleRate, bitsPerSample, channels):
datasize = 2000*10**6
o = bytes("RIFF",'ascii')
o += (datasize + 36).to_bytes(4,'little')
o += bytes("WAVE",'ascii')
o += bytes("fmt ",'ascii')
o += (16).to_bytes(4,'little')
o += (1).to_bytes(2,'little')
o += (channels).to_bytes(2,'little')
o += (sampleRate).to_bytes(4,'little')
o += (sampleRate * channels * bitsPerSample // 8).to_bytes(4,'little')
o += (channels * bitsPerSample // 8).to_bytes(2,'little')
o += (bitsPerSample).to_bytes(2,'little')
o += bytes("data",'ascii')
o += (datasize).to_bytes(4,'little')
return o
def get_sound(InputAudio):
FORMAT = pyaudio.paInt16
CHANNELS = 2
CHUNK = 1024
SAMPLE_RATE = 44100
BITS_PER_SAMPLE = 16
wav_header = generate_wav_header(SAMPLE_RATE, BITS_PER_SAMPLE, CHANNELS)
stream = InputAudio.open(
format=FORMAT,
channels=CHANNELS,
rate=SAMPLE_RATE,
input=True,
input_device_index=1,
frames_per_buffer=CHUNK
)
first_run = True
while True:
if first_run:
data = wav_header + stream.read(CHUNK)
first_run = False
else:
data = stream.read(CHUNK)
yield(data)
#app.route('/audio_feed')
def audio_feed():
return Response(
get_sound(pyaudio.PyAudio()),
content_type = 'audio/wav',
)
if __name__ == '__main__':
app.run(debug=True)
With a index.html looking like this:
<html>
<head>
<title>Test audio</title>
</head>
<body>
<button onclick="play_audio()">
Play audio
</button>
<div id="audio-feed"></div>
</body>
<script>
function play_audio() {
var audio_div = document.getElementById('audio-feed');
const audio_url = "{{ url_for('audio_feed') }}"
audio_div.innerHTML = "<audio controls><source src="+audio_url+" type='audio/x-wav;codec=pcm'></audio>";
}
</script>
</html>
Fire upp the flask development server python app.py and test with chrome, if you have a microphone you will hear the input sound (headphones preferably, otherwise you'll get a sound loop). Firefox works fine too.
But If you try the same app with any browser on an iPhone you'll get no sound, and the same goes for safari on MacOS.
There's no errors and you can see that the byte stream of the audio is getting downloaded in safari, but still no sound.
What is causing this? I think I should use some kind of headers in the audio_feed response but with hours of debugging I cannot seem to find anything for this.
I guess apple demands the implementation of RFC7233 HTTP Range Request for data without given content length. It seems otherwise the download stops after the first chunk.
For this you can add response header information like:
Accept-Ranges: bytes
Content-Range: bytes <start>-<end>/*
and return HTTP code 206 PARTIAL CONTENT. This way the browser is able to request the chunks of your media stream as soon as it needs them. Also you should evaluate the Range-attribute of each request, because the audio controls can be used to navigate in the stream. For this it will be necessary that you keep a defined amount of audio data in your server to satisfy this requirement. Also, your app should allow multi-threading.
There are discussions about streaming video content on SO like here or here. You can try to implement the same for your audio stream.
I know that this isn't the whole solution but I hope it points you in the right direction.

Python BaseHTTPRequestHandler throws Lookup error on anything but utf-8

I Need to write a server program in Python serving webpages and handling other GET and POST requests to and from client. I'm new to servers in Python, so I looked up some examples and after a while I had a basic Requesthandler running with some routing to my pages as a start. Routing worked in browser and pages were displayed there but I only got text, no styles, no pictures. Then I looked a bit further and realised that I also needed to handle GET requests for these .css, .js,.jpg files. So I did that, and ended up with smth like this:
class Serv(BaseHTTPRequestHandler):
def do_GET(self):
#route incoming path to correct page
if self.path in("","/"):
self.path = "/my_site/index.html"
#TODO do same for every page in the site
if self.path == "/foo":
self.path = "/my_site/fooandstuff.html"
if self.path == "/bar":
self.path = "/my_site/subdir/barfly.html"
try:
sendReply = False
if self.path.endswith(".html"):
mimetype = "text/html"
sendReply = True
if self.path.endswith(".jpg"):
mimetype = "image/jpg"
sendReply = True
if self.path.endswith(".js"):
mimetype = "application/javascript"
sendReply = True
if self.path.endswith(".css"):
mimetype = "text/css"
sendReply = True
if sendReply == True:
f = open(self.path[1:]).read()
self.send_response(200)
self.send_header('Content-type',mimetype)
self.end_headers()
self.wfile.write(f.encode(mimetype))
return
except IOError:
self.send_error(404, "File not found %s" % self.path)
When I run this and request a page, I get the following LookupError:
File "d:/somedir/myfile.py", line 47, in do_GET
self.wfile.write(f.encode(mimetype))
LookupError: unknown encoding: text/html
if I change text/html to utf-8, that seems te "solve" the problem, but then I run into the same Lookuperror but this time for image/jpg, and so on. It seems like wfile.write only accepts utf-8, although , when I look around, I see people passing file.read() just like that to wfile.write
wfile.write(file.read())
and for them it seems to work. Yet, when I do that, what I get is
File "C:\Users\myuser\AppData\Local\Programs\Python\Python37\lib\socketserver.py", line 799, in write
self._sock.sendall(b)
TypeError: a bytes-like object is required, not 'str'
What could cause this to happen?
for server handling with python better lookup flask
sample code will look like
from flask import Flask, render_template, url_for, request, redirect
import csv
#app.route('/')
def my_home():
return render_template('index.html')
#app.route('/<string:page_name>')
def html_page(page_name):
return render_template(page_name)
put all HTML in the same folder as your server.py in a folder called [template]
and all CSS and java in folder called [static] assets and all include. dont forget to change paths in css, java and html
The answer was in the opening of an image file, that needed an extra argument "rb" , like this:
if mimetype != "image/jpg":
f = open(self.path[1:])
else:
f = open(self.path[1:], "rb")
and then also:
if mimetype == "image/jpg":
self.wfile.write(f.read())
else:
self.wfile.write(f.read().encode("utf-8"))

NFQueue/Scapy Man in the Middle

I'm trying to construct a man in the middle attack on a webpage (i.e. HTTP traffic). I'm doing this by using a Linux machine attached to Ethernet and a client attached to the Linux box via its WiFi hotspot.
What I've done so far is use NFQueue from within the IPTables Linux firewall to route all TCP packets on the FORWARD chain to the NFQueue queue, which a Python script is picking up and then processing those rules. I'm able to read the data off of the HTTP response packets, but whenever I try to modify them and pass them back (accept the packets), I'm getting an error regarding the strings:
Exception AttributeError: "'str' object has no attribute 'build_padding'" in 'netfilterqueue.global_callback' ignored
My code is here, which includes things that I've tried that didn't work. Notably, I'm using a third-party extension for scapy called scapy_http that may be interfering with things, and I'm using a webpage that is not being compressed by gzip because that was messing with things as well. The test webpage that I'm using is here.
#scapy
from scapy.all import *
#nfqueue import
from netfilterqueue import NetfilterQueue
#scapy http extension, not really needed
import scapy_http.http
#failed gzip decoding, also tried some other stuff
#import gzip
def print_and_accept(packet):
#convert nfqueue datatype to scapy-compatible
pkt = IP(packet.get_payload())
#is this an HTTP response?
if pkt[TCP].sport == 80:
#legacy trial that doesn't work
#data = packet.get_data()
print('HTTP Packet Found')
#check what's in the payload
stringLoad = str(pkt[TCP].payload)
#deleted because printing stuff out clogs output
#print(stringLoad)
#we only want to modify a specific packet:
if "<title>Acids and Bases: Use of the pKa Table</title>" in stringLoad:
print('Target Found')
#strings kind of don't work, I think this is a me problem
#stringLoad.replace('>Acids and Bases: Use of the pK<sub>a</sub>', 'This page has been modified: a random ')
#pkt[TCP].payload = stringLoad
#https://stackoverflow.com/questions/27293924/change-tcp-payload-with-nfqueue-scapy
payload_before = len(pkt[TCP].payload)
# I suspect this line is a problem: the string assigns,
# but maybe under the hood scapy doesn't like that very much
pkt[TCP].payload = str(pkt[TCP].payload).replace("Discussion", "This page has been modified")
#recalculate length
payload_after = len(pkt[TCP].payload)
payload_dif = payload_after - payload_before
pkt[IP].len = pkt[IP].len + payload_dif
#recalculate checksum
del pkt[TCP].chksum
del pkt[IP].chksum
del pkt.chksum
print('Packet Modified')
#redudant
#print(stringLoad)
#this throws an error (I think)
print(str(pkt[TCP].payload))
#no clue if this works or not yet
#goal here is to reassign modified packet to original parameter
packet.set_payload(str(pkt))
#this was also throwing the error, so tried to move away from it
#print(pkt.show2())
#bunch of legacy code that didn't work
#print(GET_print(pkt))
#print(pkt.show())
#decompressed_data = zlib.decompress(str(pkt[TCP].payload), 16 + zlib.MAX_WBITS)
#print(decompressed_data)
#print(str(gzip.decompress(pkt[TCP].payload)))
# print(pkt.getlayer(Raw).load)
#print('HTTP Contents Shown')
packet.accept()
def GET_print(packet1):
ret = "***************************************GET PACKET****************************************************\n"
ret += "\n".join(packet1.sprintf("{Raw:%Raw.load%}\n").split(r"\r\n"))
ret += "*****************************************************************************************************\n"
return ret
print('Test: Modify a very specific target')
print('Program Starting')
nfqueue = NetfilterQueue()
nfqueue.bind(1, print_and_accept)
try:
print('Packet Interface Starting')
nfqueue.run()
except KeyboardInterrupt:
print('\nProgram Ending')
nfqueue.unbind()
Apologies in advance if this is hard to read or badly formatted code; Python isn't a language that I write in often. Any help is greatly appreciated!

Stream audio from pyaudio with Flask to HTML5

I want to stream the audio of my microphone (that is being recorded via pyaudio) via Flask to any client that connects.
This is where the audio comes from:
def getSound(self):
# Current chunk of audio data
data = self.stream.read(self.CHUNK)
self.frames.append(data)
wave = self.save(list(self.frames))
return data
Here's my flask-code:
#app.route('/audiofeed')
def audiofeed():
def gen(microphone):
while True:
sound = microphone.getSound()
#with open('tmp.wav', 'rb') as myfile:
# yield myfile.read()
yield sound
return Response(stream_with_context(gen(Microphone())))
And this is the client:
<audio controls>
<source src="{{ url_for('audiofeed') }}" type="audio/x-wav;codec=pcm">
Your browser does not support the audio element.
</audio>
It does work sometimes, but most of the times I'm getting "[Errno 32] Broken pipe"
When uncommenting that with open("tmp.wav")-part (the self.save() optionally takes all previous frames and saves them in tmp.wav), I kind of get a stream, but all that comes out of the speakers is a "clicking"-noise.
I'm open for any suggestions. How do I get the input of my microphone live-streamed (no pre-recording!) to a webbrowser?
Thanks!
Try This its worked for me. shell cmd "cat" is working perfect see the code
iam using FLASK
import subprocess
import os
import inspect
from flask import Flask
from flask import Response
#app.route('/playaudio')
def playaudio():
sendFileName=""
def generate():
# get_list_all_files_name this function gives all internal files inside the folder
filesAudios=get_list_all_files_name(currentDir+"/streamingAudios/1")
# audioPath is audio file path in system
for audioPath in filesAudios:
data=subprocess.check_output(['cat',audioPath])
yield data
return Response(generate(), mimetype='audio/mp3')
This question was asked long time ago, but since I spent entire day to figure out how to implement the same, I want to give the answer. Maybe it will be helpful for somebody.
"[Errno 32] Broken pipe" error comes from the fact that client can not play audio and closes this stream.
Audio can not be played due to absence of the header in the data stream. You can easily create the header using genHeader(sampleRate, bitsPerSample, channels, samples) function from the code here . This header has to be attached at least to the first chunck of sent data ( chunck=header+data ). Pay attention, that audio can be played ONLY untill client reaches file size in download that you have to specify in the header. So, workaround would be to set in the header some big files size, e.g. 2Gb.
Instead of datasize = len(samples) * channels * bitsPerSample in the header function write datasize = 2000*10**6.
def gen_audio():
CHUNK = 512
sampleRate = 44100
bitsPerSample = 16
channels = 2
wav_header = genHeader(sampleRate, bitsPerSample, channels)
audio = AudioRead()
data = audio.get_audio_chunck()
chunck = wav_header + data
while True:
yield (chunck)
data = audio.get_audio_chunck()
chunck = data
After lots research and tinkering I finally found the solution.
Basically it came down to serving pyaudio.paFloat32 audio data through WebSockets using Flask's SocketIO implementation and receiving/playing the data in JavaScript using HTML5's AudioContext.
As this is requires quite some code, I think it would not be a good idea to post it all here. Instead, feel free to check out the project I'm using it in: simpleCam
The relevant code is in:
- noise_detector.py (recording)
- server.py (WebSocket transfer)
- static/js/player.js (receiving/playing)
Thanks everyone for the support!

Bottle running command after file download

I have a bottle "server" serving me block device images' in .raw format. Unfortunately, after download finishes (successfully or not, doesn't matter) I have to run one more method to unmount exported block device.
Below I provide the code which is used to export the image but only provides me with errors (cannot unmap device as it's used by different process) and isn't exporting images as intended:
class ExportResponse(HTTPResponse):
def __init__(self, devpath, volume_name):
self.devpath = devpath
self.volume_name = volume_name
output_filename = "%s.raw" % (volume_name)
fp = open(devpath)
content_length = os.lseek(fp.fileno(), 0, os.SEEK_END)
os.lseek(fp.fileno(), 0, os.SEEK_SET)
headers = {
'Content-Length': str(content_length),
'Content-Type': "application/octet-stream",
'Content-Disposition': 'attachment; filename="%s"' % output_filename
}
log.info("%s, %s" % (os.SEEK_CUR, content_length))
super(ExportResponse, self).__init__(fp, **headers)
#get('/retrieve/<volume_name>/')
def export_volume(volume_name):
fmt = request.query['format']
dev = find_mapping(volume_name)
if dev:
log.debug("export, name=%s, already mapped dev=%s" % (repr(volume_name), str(dev)))
dev_owner = dev is None
try:
if not dev:
map_volume(volume_name)
dev = find_mapping(volume_name)
log.debug("export, name=%s, mapped to dev=%s" % (repr(volume_name), str(dev)))
return(ExportResponse(dev, volume_name))
except:
log.exception("export, name=%s" % repr(volume_name))
if dev and dev_owner:
unmap_volume(volume_name)
raise
finally:
unmap_volume(volume_name)
Is there any good way of doing this?
There are two ways of doing this.
1) Read the entire image into memory using a BytesIO object. Use that as the stream handle you pass to __init__, instead of the handle to the actual file.
2) Obtain from bottle the raw handle to the response stream and use the write() method on the stream to write chunks of the file yourself, closing it when you are done. I am not sure how to get that stream from bottle, but I know it exists because the WSGI protocol specifies the response is passed as a stream.

Categories