Saving video file content from fetch formdata

What I’m trying to do:

Save a video created with Javascript media_recorder API and sent with fetch, to an Anvil HTTP endpoint function.

What I’ve tried and what’s not working:

The file is transferred properly and saved to the database, but with the following header:

------WebKitFormBoundaryrxtZSOYF0fR5jDD4
Content-Disposition: form-data; name=“file”; filename=“recording.webm”
Content-Type: video/webm

If I manually remove that header from the raw data, the video plays properly.

I’m not sure if I need to send the data differently using the JS fetch operation, or save the received bytes differently in the server API function, or just manually parse out the header from the saved data. I’ve read the following posts:

Code Sample:
I have this http endpoint function running at https://recordvideo.anvil.app/_/api/savevideo:

@anvil.server.http_endpoint('/savevideo')
def save_video():
  app_tables.videos.add_row(video=anvil.server.request.body)
  return 'success'

Recorded video data is sent to that endpoint from this HTML/CSS/JS running on my local PC:

<!DOCTYPE html>
<html>
<head>
<title>Demo - Record Photo From Webcam Using Javascript</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style type="text/css">

button {
    width: 150px;
    padding: 10px;
    display: block;
    margin: 20px auto;
    border: 2px solid #111111;
    cursor: pointer;
    background-color: white;
}

#start-camera {
    margin-top: 50px;
}

#video {
    display: none;
    margin: 50px auto 0 auto;
}

#start-record, #stop-record, #download-video {
    display: none;
}

#download-video {
    text-align: center;
    margin: 20px 0 0 0;
}

</style>
</head>

<body>

<button id="start-camera">Start Camera</button>
<video id="video" width="320" height="240" autoplay></video>
<button id="start-record">Start Recording</button>
<button id="stop-record">Stop Recording</button>
<a id="download-video" download="test.webm">Download Video</a>

<script>

let camera_button = document.querySelector("#start-camera");
let video = document.querySelector("#video");
let start_button = document.querySelector("#start-record");
let stop_button = document.querySelector("#stop-record");
let download_link = document.querySelector("#download-video");

let camera_stream = null;
let media_recorder = null;
let blobs_recorded = [];

camera_button.addEventListener('click', async function() {
   	try {
    	camera_stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    }
    catch(error) {
    	alert(error.message);
    	return;
    }

    video.srcObject = camera_stream;
    camera_button.style.display = 'none';
    video.style.display = 'block';
    start_button.style.display = 'block';
});

start_button.addEventListener('click', function() {
    media_recorder = new MediaRecorder(camera_stream, { mimeType: 'video/webm' });

    media_recorder.addEventListener('dataavailable', function(e) {
    	blobs_recorded.push(e.data);
    });

    media_recorder.addEventListener('stop', function() {
      let recording = new File(blobs_recorded, 'recording.webm', { type: 'video/webm' });
      let data = new FormData();
      data.append('file', recording);
      let response = fetch('https://recordvideo.anvil.app/_/api/savevideo', {
          method: 'POST',
          <!-- credentials: 'same-origin', -->
          body: data
      });

    	let video_local = URL.createObjectURL(new Blob(blobs_recorded, { type: 'video/webm' }));
    	download_link.href = video_local;

        stop_button.style.display = 'none';
        download_link.style.display = 'block';
    });

    media_recorder.start(1000);

    start_button.style.display = 'none';
    stop_button.style.display = 'block';
});

stop_button.addEventListener('click', function() {
	media_recorder.stop(); 
});

</script>

</body>
</html>

Clone link:

Coming from my esoteric background using dinosaur tools, I would just parse out the header and save the data, but it seems like using requests_toolbelt.multipart.decoder or multipart is more Pythonic, or maybe I’m just missing the preferred way to use anvil.server.request.body.get_bytes(). I’ll just brute force parse it if needed, but it seems there must be nicer expected pattern for accomplishing this.

This works:

@anvil.server.http_endpoint('/savevideo')
def save_video():
  video_bytes=anvil.server.request.body.get_bytes()
  video_file=anvil.BlobMedia('video/webm', video_bytes[142:], name='video.webm')
  app_tables.videos.add_row(video=video_file)
  return 'success'
1 Like

But any other solutions are appreciated!