Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

response.stream no more works in Sanic v22.9.0 #2

Closed
goldbingo opened this issue Oct 8, 2022 · 6 comments
Closed

response.stream no more works in Sanic v22.9.0 #2

goldbingo opened this issue Oct 8, 2022 · 6 comments

Comments

@goldbingo
Copy link

Error message:
return response.stream(stream_mjpeg, content_type='multipart/x-mixed-replace; boundary=frame')
AttributeError: module 'sanic.response' has no attribute 'stream'

@bootrino
Copy link
Owner

bootrino commented Oct 8, 2022

I rewrote it later on, maybe try this?

import math
import os
import sys
import time
import traceback

from sanic import response, Sanic
import asyncio
import timeit
from PIL import Image
import io
from pathlib import Path

# MaryJane is an mjpeg server - it works by fetching *the same* jpeg image over and over from a ram drive
# MIT license
# copyright 2021 Andrew Stuart andrew.stuart@supercoders.com.au
format = 'jpg'
mime_types = {
    'jpg': 'jpg',
    'jpeg': 'jpg',
}
mime_type = mime_types[format]

directory_latest_frame = os.getenv('DIRECTORY_LATEST_FRAME')
if not directory_latest_frame:
    print('env var DIRECTORY_LATEST_FRAME is not valid, exiting.')
    sys.exit()
frame_absolute_path = f'{directory_latest_frame}frame.jpg'

port = os.getenv('PORT_NUMBER_PREVIEW_SERVER')
if not port:
    print('env var PORT_NUMBER_PREVIEW_SERVER is not valid, exiting.')
    sys.exit()
port = int(port)

app = Sanic(__name__)


def package_mjpeg(img_bytes):
    if img_bytes:
        if mime_type == 'jpg':
            return (b'--frame\r\n'
                    b'Content-Type: image/jpg\r\n\r\n' + img_bytes + b'\r\n')


async def run():
    # if the system has not yet started generating preview images, then make our own blank image
    if not os.path.isfile(frame_absolute_path):
        source_frame_image = Image.new('RGB', (1, 1), color=(0, 0, 0))
        img_byte_arr = io.BytesIO()
        source_frame_image.save(img_byte_arr, format=format, quality=20)
        return img_byte_arr.getvalue()

    if format == 'jpg':
        # 40K to 160K bandwidth / second
        with open(frame_absolute_path, 'rb') as file:
            return file.read()

    # print(f'{frame_absolute_path}: {Path(frame_absolute_path).stat().st_size}')


@app.route('/')
@app.route('/<path:path>')  # catchall
async def mjpeg_server(request, path=''):
    # 15fps = frame_milliseconds_budget of 66.66
    # 20fps = frame_milliseconds_budget of 50
    # 30fps = frame_milliseconds_budget of 33.33
    # 60fps = frame_milliseconds_budget of 16.66
    show_stats = True
    fps = 15  # frames per second
    frame_milliseconds_budget = 1000 / fps

    bytes_sent_this_second = 0
    current_second = math.floor(time.time())
    frames_sent_this_second = 0
    remaining_time = 0

    async def stream_mjpeg(response):
        bytes_sent_this_second = 0
        current_second = math.floor(time.time())
        frames_sent_this_second = 0
        remaining_time = 0
        while True:
            # if this frame was completed MORE QUICKLY than needed to maintain FPS
            # sleep the the remaining time budget
            await asyncio.sleep(remaining_time / 1000)
            start = timeit.default_timer()
            try:
                image_bytes: bytes = await run()
                await response.send(package_mjpeg(image_bytes))
            except Exception as e:
                print(repr(e))
            if current_second == math.floor(time.time()):
                bytes_sent_this_second += len(image_bytes)
                frames_sent_this_second += 1
            else:
                if show_stats:
                    print(f'{frames_sent_this_second} frames {bytes_sent_this_second} bytes sent last second')
                frames_sent_this_second = 0
                bytes_sent_this_second = 0
                current_second = math.floor(time.time())
            milliseconds_taken_to_send_frame = (timeit.default_timer() - start) * 1000
            remaining_time = frame_milliseconds_budget - milliseconds_taken_to_send_frame
            remaining_time = max(remaining_time, 0)  # make zero if negative
            if remaining_time > 0:
                print('+', end='')

    response = await request.respond(content_type='multipart/x-mixed-replace; boundary=frame')
    await stream_mjpeg(response)


if __name__ == '__main__':
    try:
        app.run(host="0.0.0.0", port=port)
    except KeyboardInterrupt:
        print("Received KeyboardInterrupt, exiting")
    except Exception as e:
        print(traceback.format_exc())
        print(f'EXCEPTION in get_instance_info: {repr(e)}')

@goldbingo
Copy link
Author

It fails at the the very beginning:

format = 'webp'
mime_types = {
'jpg': 'jpg',
'jpeg': 'jpg',
}
mime_type = mime_types[format]

Error message:

Traceback (most recent call last):
File "/usr/home/test/mjpeg-server.py", line 23, in
mime_type = mime_types[format]
KeyError: 'webp'

@bootrino
Copy link
Owner

bootrino commented Oct 9, 2022

Give it another try. I have updated it.

@bootrino
Copy link
Owner

bootrino commented Oct 9, 2022

You will need to:

replace the 5000 with whatever port you want to run on:

export PORT_NUMBER_PREVIEW_SERVER=5000
export DIRECTORY_LATEST_FRAME=/directorywhereyoustoreyourframe

@goldbingo
Copy link
Author

Now it works great in browser.

However, this new version has one limitation. It won't play in VLC. I used to use VLC to record the stream. And I will try find another way to record the stream.

Thanks for your help. This piece of art do save me a lot of time.

@bootrino
Copy link
Owner

bootrino commented Oct 9, 2022

I can't see why it wouldn't record in VLC if it was doing so before. Have you tried using ffmpeg to save it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants