Skip to content

Instantly share code, notes, and snippets.

@danshev
Last active May 20, 2022 09:23
Show Gist options
  • Save danshev/072b305050e6d7aaa819968eea40b9fd to your computer and use it in GitHub Desktop.
Save danshev/072b305050e6d7aaa819968eea40b9fd to your computer and use it in GitHub Desktop.
TeslaCam-Merge-Videos (for AWS Lambda)
'''
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