skip to Main Content

I’m attempting to record a video and download it with a border. I’m using the RGB color values 0, 0, 255 for the border. While I can easily download the recorded video, the border does not appear on it. When I click on the ‘Start Recording’ button, recording is initiated. Upon clicking the ‘Stop Recording’ button, a download button appears containing the link to download the video. However, the downloaded video lacks the border.

APP.PY

from flask import Flask, render_template, send_file
import cv2
import numpy as np

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

def add_border_to_video(video_path, output_path, border_thickness=20, border_color=(0, 0, 255)):
    cap = cv2.VideoCapture(video_path)
    frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(output_path, fourcc, 20.0, (frame_width, frame_height))

    while True:
        ret, frame = cap.read()
        if not ret:
            break
        # Add border to the frame
        bordered_frame = cv2.copyMakeBorder(frame, border_thickness, border_thickness, border_thickness, border_thickness, cv2.BORDER_CONSTANT, value=border_color)
        out.write(bordered_frame)

    cap.release()
    out.release()

@app.route('/record_video')
def record_video():
    # Record video with OpenCV
    # Make sure to save it to a file
    # For simplicity, let's assume the file is named 'recorded_video.mp4'
    recorded_video_path = 'recorded_video.mp4'

    # Add border to the recorded video
    bordered_video_path = 'output_with_border.mp4'
    add_border_to_video(recorded_video_path, bordered_video_path)

    return 'Video recorded and bordered successfully!'

@app.route('/download_video')
def download_video():
    # Serve the bordered video file for download
    bordered_video_path = 'output_with_border.mp4'
    return send_file(bordered_video_path, as_attachment=True)

if __name__ == '__main__':
    app.run(debug=True)

INDEX.HTML (HTML AND JS)


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Record and Download Video</title>
    <style>
        /* CSS styles for the video container */
        #video-container {
            width: 640px;
            height: 480px;
            margin: 0 auto;
            border: 2px solid black;
        }
    </style>
</head>
<body>
    <h1>Record and Download Video</h1>

    <!-- Video container -->
    <div id="video-container">
        <video id="video" width="640" height="480" autoplay></video>
    </div>

    <!-- Buttons for recording and downloading -->
    <button id="startRecord">Start Recording</button>
    <button id="stopRecord" disabled>Stop Recording</button>
    <a id="downloadVideo" style="display: none;">Download Video</a>

    <!-- JavaScript code -->
    <script>
        const video = document.getElementById('video');
        const startRecordBtn = document.getElementById('startRecord');
        const stopRecordBtn = document.getElementById('stopRecord');
        const downloadVideoBtn = document.getElementById('downloadVideo');
        let mediaRecorder;
        let chunks = [];

        startRecordBtn.addEventListener('click', async () => {
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ video: true });
                video.srcObject = stream;
                startRecordBtn.disabled = true;
                stopRecordBtn.disabled = false;
                downloadVideoBtn.style.display = 'none';
                mediaRecorder = new MediaRecorder(stream);
                mediaRecorder.ondataavailable = handleDataAvailable;
                mediaRecorder.start();
            } catch (err) {
                console.error('Error accessing camera:', err);
            }
        });

        stopRecordBtn.addEventListener('click', () => {
            mediaRecorder.stop();
            startRecordBtn.disabled = false;
            stopRecordBtn.disabled = true;
        });

        function handleDataAvailable(event) {
            chunks.push(event.data);
            if (mediaRecorder.state === 'inactive') {
                const blob = new Blob(chunks, { type: 'video/mp4' });
                const url = URL.createObjectURL(blob);
                downloadVideoBtn.href = url;
                downloadVideoBtn.download = 'recorded_video.mp4';
                downloadVideoBtn.style.display = 'inline';
            }
        }
    </script>
</body>
</html>

2

Answers


  1. This aim to be a minimal example with focus on how and where pre– and post-process a video. No JavaScript is needed.

    Due to the huge amount of online tutorial copying each other out I tried to give an "improved" version and also use coroutines to better separate the tasks of pre and post processing.

    Once opening the root page, for ex localhost:5000/, the the camera will be turned on and automatically the video will be recorder. For stop the recording close the window (or partially reload the page).

    import time
    from flask import Flask, render_template, Response, render_template_string, stream_with_context
    import cv2
    
    
    def coroutine(func):
        def start(*args, **kwargs):
            cr = func(*args, **kwargs)
            next(cr)
            return cr
        return start
    
    
    @stream_with_context
    def stream_main(target, path_video):
        cap = cv2.VideoCapture(0)
    
        if not cap.isOpened():
            print("Cannot open camera")
            # do routing stuffs
            #cap.release() # don't forget it:)
            return
    
        # type of the file extension that defines the output format
        frame_ext = '.jpg'
        if not frame_ext.startswith('.'):
            frame_ext = '.' + frame_ext
    
        # frame info
        frame_size = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    
        # "initialize" coroutine
        target = target(path_video, *frame_size)
    
        # wrap binary image to html content
        def video_frame_content(frame:bytes) -> bytes:
            return b''.join((b'--framernContent-Type: image/jpegrnrn', frame, b'rn'))
    
        while True:
            # capturing data (frame) from the camera
            ret, frame = cap.read()
            if not ret:
                print('Stream ended/interrupted?')
                break
    
            # save the frame
            target.send(frame)
    
            # pre-process the "live"-frame    <--------------
            # example: flip along the y-axis
            frame = cv2.flip(frame, flipCode=1)
    
            # prepare for streaming
            #
            # encodes an image into a memory buffer
            ret, buffer = cv2.imencode(frame_ext, frame)
            if not ret:
                print('At Frame XY a problem occurred')
    
            yield video_frame_content(buffer.tobytes())
            
            # FPS
            time.sleep(0.04) # not mandatory
    
        cap.release()
        target.close()
    
    
    @coroutine
    def video_writer(dst_video, frame_width, frame_height):
        # post processing the frames
        
        # or declare them as parameters
        border_thickness = 20
        border_color = 0, 0, 255
    
        # video recording
        out = cv2.VideoWriter(
            dst_video, 
            cv2.VideoWriter_fourcc(*'MJPG'.lower()),
            10, 
            (frame_width+2*border_thickness,frame_height+2*border_thickness)
        )
    
        try:
            while True:
                # read the frame
                frame = (yield)
    
                # post-processing  <----------------
                # example: add border
                bordered_frame = cv2.copyMakeBorder(
                    frame,
                    border_thickness, border_thickness, border_thickness, border_thickness,
                    cv2.BORDER_CONSTANT,
                    value=border_color)
    
                out.write(bordered_frame)
        except Exception:
            # Done
            pass
        finally:
            out.release()
    
    
    # routing
    #########
    
    
    # to add
    path_video_border = # path of the new .mp4 file
    
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def index():
        return render_template_string('Testing camera:<br><img src="{{ url_for("video_feed") }}">')
    
    
    @app.route('/video_feed')
    def video_feed():
        # returns the streaming response
        return Response(stream_main(video_writer, path_video_border), mimetype='multipart/x-mixed-replace; boundary=frame')
    
    
    if __name__ == "__main__":
        app.run(debug=True)
    

    Notice further that in the OP there is a mistake when the writer is initialised: by adding a border to the frames their size change as well out = cv2.VideoWriter( [...], (frame_width + 2*border_thickness, frame_height+2*border_thickness)).

    Login or Signup to reply.
  2. Your code for adding a border doesn’t take into account that the border makes the individual frame of the video larger. For this reason, the edited video cannot be played.

    You also added the URL of the unedited video to the download button. The original is never sent to the server. Because of this, the code to add the border is not executed and the border is also not visible in the downloaded video.

    The following example shows you how to record a video on the client side and send it to the server where it is edited. The edited video is then offered for download.

    from flask import (
        Flask, 
        abort, 
        render_template, 
        request, 
        send_from_directory
    )
    from mimetypes import guess_extension
    from werkzeug.utils import secure_filename
    import cv2
    import os 
    import tempfile
    
    app = Flask(__name__)
    app.config.from_mapping(
        UPLOAD_FOLDER=os.path.join(app.instance_path, 'uploads')
    )
    os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
    
    @app.route('/')
    def index():
        return render_template('index.html')
    
    @app.post('/upload')
    def upload():
        file = request.files.get('video')
        if file and file.filename != '':
            extname = guess_extension(file.mimetype)
            if not extname:
                abort(400)
    
            # Check the allowed file types here.
    
            with tempfile.TemporaryDirectory(dir=app.config['UPLOAD_FOLDER']) as tmpdirname:
                srcfile = secure_filename(f'{file.filename}{extname}')
                srcpath = os.path.join(tmpdirname, srcfile) 
                dstfile = f'output.mp4'
                dstpath = os.path.join(tmpdirname, dstfile)
                file.save(srcpath)
    
                convert(srcpath, dstpath) 
    
                return send_from_directory(tmpdirname, dstfile)
    
        abort(400)
    
    def convert(src, dst, border=10, clr=(0,0,255)):
        cap = cv2.VideoCapture(src)
        frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        out = cv2.VideoWriter(
            dst, 
            cv2.VideoWriter_fourcc(*'mp4v'), 
            20.0, 
            (frame_width + 2*border, frame_height + 2*border)
        )
    
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            frame = cv2.copyMakeBorder(
                frame, 
                border, border, border, border, 
                cv2.BORDER_CONSTANT, 
                value=clr
            )
            out.write(frame)
    
        out.release()
        cap.release()
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Index</title>
        <style>
            a#dwl {
                display: None;
            }
            a#dwl.active {
                display: inline-block;
            }
        </style>
    </head>
    <body>
        <div id="video-container">
            <video id="video" width="640" height="480" autoplay></video>
        </div>
    
        <button id="start">Start Recording</button>
        <button id="stop" disabled>Stop Recording</button>
        <a id="dwl" href="#">Download</a>
    
        <script>
            (function() {
                const video = document.getElementById('video');
                const startBtn = document.getElementById('start');
                const stopBtn = document.getElementById('stop');
                const dwlBtn = document.getElementById('dwl');
    
                navigator.mediaDevices.getUserMedia({ video: true })
                    .then(stream => {
                        video.srcObject = stream;
    
                        let chunks = [];
                        let mediaRecorder = new MediaRecorder(stream);
    
                        mediaRecorder.ondataavailable = event => {
                            chunks.push(event.data);
                        };
    
                        mediaRecorder.onstop = () => {
                            let blob = new Blob(chunks, { type: mediaRecorder.mimeType });
                            chunks = [];
    
                            startBtn.disabled = false;
                            stopBtn.disabled = true;
    
                            const formData = new FormData();
                            formData.append('video', blob);
    
                            fetch('/upload', { method: 'post', body: formData })
                                .then(resp => resp.ok && resp.blob())
                                .then(blob => {
                                    if (blob) {
                                        dwlBtn.href = URL.createObjectURL(blob);
                                        dwlBtn.download = 'output.mp4';
                                        dwlBtn.classList.add('active');
                                    }
                                });
                        };
    
                        mediaRecorder.onstart = event => {
                            startBtn.disabled = true;
                            stopBtn.disabled = false;
                        };
                
                        stopBtn.addEventListener('click', () => mediaRecorder.stop());
                        startBtn.addEventListener('click', () => mediaRecorder.start());
                    });
    
            })();
        </script>
    
    </body>
    </html>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search