Last active
May 20, 2022 09:23
-
-
Save danshev/072b305050e6d7aaa819968eea40b9fd to your computer and use it in GitHub Desktop.
TeslaCam-Merge-Videos (for AWS Lambda)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Function: TeslaCam-Merge-Videos | |
Runtime: Python 3.7 | |
Environment: AWS Lambda | |
Description: | |
This function is meant to be invoked by another Lambda (TeslaCam-Identify-Sets-and-Kickoff). | |
Upon execution, it will use the 'event' key of the event dictionary (passed at runtime), and: | |
1. Download camera angle video files from S3 | |
2. Use ffmpeg to determine clip duration | |
3. Use ffmpeg to create a "fullscreen" video clip in the format | |
[ camera-1 ][ camera-2 ][ camera-3 ] ... camera order determined by ENV variable | |
** Major credit to: https://github.com/ehendrix23/tesla_dashcam ** | |
-- This is simply one instantiation of the many possibilities offered by the above | |
Environment Variables: | |
- S3_BUCKET = the.name.of.your.bucket | |
- CAMERA_NAMES = left_repeater, front, right_repeater (or as desired) | |
- FFMPEG = /path/to/ffmpeg (e.g., /opt/ffmpeg-git-20190514-amd64-static/ffmpeg ) | |
- OUTPUT_FILEPATH = /tmp/output.mp4 (recommended) | |
Assumptions: | |
1. You have a Lambda Layer connected to the Lambda with ffmpeg library, etc. | |
2. This Lambda function has a permissions Role enabling it to: | |
a. Read from and Write to S3 | |
b. (Recommended) Write to CloudWatch Logs | |
''' | |
import boto3 | |
import json | |
import subprocess | |
import os | |
from re import search | |
s3 = boto3.resource('s3') | |
def lambda_handler(event, context): | |
inputs = [] | |
for camera_name in os.environ['CAMERA_NAMES'].split(","): | |
camera_name = camera_name.strip() | |
filename = "{}{}.mp4".format(event["event"], camera_name) | |
local_filename = '/tmp/{}.mp4'.format(camera_name) | |
# Get the video files from S3 | |
s3.meta.client.download_file( | |
os.environ['S3_BUCKET'], | |
filename, | |
local_filename | |
) | |
inputs += ['-i', local_filename] | |
# Use the last file to determine the appropriate duration | |
command_result = subprocess.run([ | |
os.environ['FFMPEG'], | |
'-i', | |
local_filename, | |
'-hide_banner'], | |
capture_output=True, | |
text=True | |
) | |
for line in command_result.stderr.splitlines(): | |
if search("^ *Duration: ", line) is not None: | |
line_split = line.split(',') | |
line_split = line_split[0].split(':', 1) | |
duration_list = line_split[1].split(':') | |
duration = int(duration_list[0]) * 60 * 60 + \ | |
int(duration_list[1]) * 60 + \ | |
int(duration_list[2].split('.')[0]) + \ | |
(float(duration_list[2].split('.')[1]) / 100) | |
# Build the ffmpeg filter argument | |
# Note: this will need revision if TeslaCam outputs more camera feeds | |
ffmpeg_filter = 'color=duration={}:s=1920x480:c=black [base];'.format(duration) + \ | |
'[0:v] setpts=PTS-STARTPTS, scale=640x480, hflip [left];' + \ | |
'[1:v] setpts=PTS-STARTPTS, scale=640x480 [front];' + \ | |
'[2:v] setpts=PTS-STARTPTS, scale=640x480, hflip [right];' + \ | |
'[base][left] overlay=eof_action=pass:repeatlast=0:x=0:y=0 [left1];' \ | |
'[left1][front] overlay=eof_action=pass:repeatlast=0:x=640:y=0 [front1];' \ | |
'[front1][right] overlay=eof_action=pass:repeatlast=0:x=1280:y=0' | |
# Build the final ffmpeg command | |
ffmpeg_command = [os.environ['FFMPEG'],] + inputs + \ | |
['-filter_complex', ffmpeg_filter] + \ | |
['-preset', 'medium', '-crf', '28'] + \ | |
['-y', os.environ['OUTPUT_FILEPATH']] | |
# Execute ffmpeg | |
try: | |
subprocess.run(ffmpeg_command) | |
except subprocess.CalledProcessError as e: | |
print(e.output) | |
return { 'statusCode': 500, 'body': json.dumps('Poop.') } | |
# Upload the result to the S3 with the fullscreen/ prefix | |
s3.meta.client.upload_file( | |
os.environ['OUTPUT_FILEPATH'], | |
os.environ['S3_BUCKET'], | |
"fullscreen/{}.mp4".format(event["event"].split("/")[1][:-1]), | |
ExtraArgs={ 'ContentType': 'video/mp4', 'ACL':'public-read' } | |
) | |
return { 'statusCode': 200, 'body': json.dumps('It worked!') } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment