|
| 1 | +import logging |
| 2 | +import json |
| 3 | +import copy |
| 4 | + |
| 5 | +import azure.functions as func |
| 6 | + |
| 7 | +# JSON schema to create container instances with the Logic App connector |
| 8 | +# We'll fill in the values here using this azure function and define all the container instances to be created programattically |
| 9 | +aciStringSchema = """ |
| 10 | + { |
| 11 | + "name": "1", |
| 12 | + "properties": { |
| 13 | + "image": "", |
| 14 | + "resources": { |
| 15 | + "requests": { |
| 16 | + "memoryInGB": 1, |
| 17 | + "cpu": 4 |
| 18 | + } |
| 19 | + }, |
| 20 | + "command": [ |
| 21 | + "" |
| 22 | + ], |
| 23 | + "volumeMounts": [ |
| 24 | + { |
| 25 | + "name": "", |
| 26 | + "mountPath": "", |
| 27 | + "readOnly": false |
| 28 | + }, |
| 29 | + { |
| 30 | + "name": "", |
| 31 | + "mountPath": "", |
| 32 | + "readOnly": false |
| 33 | + } |
| 34 | + ] |
| 35 | + } |
| 36 | + } |
| 37 | +""" |
| 38 | + |
| 39 | +def main(req: func.HttpRequest) -> func.HttpResponse: |
| 40 | + logging.info('Python HTTP trigger function processed a request.') |
| 41 | + body = req.get_body() |
| 42 | + |
| 43 | + # Fail if no request body passed |
| 44 | + if not body: |
| 45 | + return func.HttpResponse( |
| 46 | + "Please pass in the request body", |
| 47 | + status_code=400 |
| 48 | + ) |
| 49 | + |
| 50 | + shots = json.loads(body) |
| 51 | + aciJsonSchema = json.loads(aciStringSchema) |
| 52 | + |
| 53 | + # Array of conatiner instances JSON definition objects that we'll return to the ACI Logic App connector |
| 54 | + returnArray = [] |
| 55 | + |
| 56 | + # Get header parameters that are passed in from Logic App ACI connector |
| 57 | + videoName = req.headers.get('videoName') |
| 58 | + ffmpegBinaryPath = req.headers.get('ffmpegBinaryPath') |
| 59 | + |
| 60 | + aciSourceVideoMountName = req.headers.get('aciSourceVideoMountName') |
| 61 | + aciSourceVideoPath = req.headers.get('aciSourceVideoPath') |
| 62 | + |
| 63 | + aciDestinationVideoMountName = req.headers.get('aciDestinationVideoMountName') |
| 64 | + aciDestinationVideoPath = req.headers.get('aciDestinationVideoPath') |
| 65 | + |
| 66 | + dockerImage = req.headers.get('dockerImage') |
| 67 | + requestedMemoryInGB = float(req.headers.get('requestedMemoryInGB')) |
| 68 | + requestedCPUCores = int(req.headers.get('requestedCPUCores')) |
| 69 | + |
| 70 | + if not (videoName or ffmpegBinaryPath or aciSourceVideoMountName or aciSourceVideoPath or aciDestinationVideoMountName or aciDestinationVideoPath or dockerImage or requestedMemoryInGB or requestedCPUCores): |
| 71 | + # Fail if any of the parameters are missing |
| 72 | + return func.HttpResponse( |
| 73 | + "Please pass required parameters in the request body", |
| 74 | + status_code=400 |
| 75 | + ) |
| 76 | + |
| 77 | + for shot in shots: |
| 78 | + containerJson = copy.deepcopy(aciJsonSchema) |
| 79 | + |
| 80 | + # ffmpeg command that needs to be run by the container instance to encode the video |
| 81 | + # Using ffmpeg -ss and -t parameters, we'll only encode a specific time chunk |
| 82 | + # The time chunks that we need to encode correspond to the different scene timestamps that we'll get from the video index |
| 83 | + # To fix the ffmpeg error - "Too many packets buffered for output stream 0:1" |
| 84 | + # Added -max_muxing_queue_size 1024 |
| 85 | + ffmpegCommand = "{}/ffmpeg -i {}{} -v debug -c:v libaom-av1 -crf 22 -b:v 0 -strict experimental -cpu-used 4 -row-mt 1 -tiles 2x1 -c:a copy -max_muxing_queue_size 102400 -ss {} -t {} -y {}{}.mkv" |
| 86 | + formattedFFmpeg = ffmpegCommand.format(ffmpegBinaryPath, aciSourceVideoPath, videoName, shot['instances'][0]['start'], shot['instances'][0]['duration'], aciDestinationVideoPath, shot['id']) |
| 87 | + aciCommand = formattedFFmpeg.split(' ') |
| 88 | + |
| 89 | + containerJson['name'] = 'shot-' + "{}".format(shot['id']) |
| 90 | + containerJson['properties']['image'] = dockerImage |
| 91 | + containerJson['properties']['resources']['requests']['memoryInGB'] = requestedMemoryInGB |
| 92 | + containerJson['properties']['resources']['requests']['cpu'] = requestedCPUCores |
| 93 | + containerJson['properties']['command'] = aciCommand |
| 94 | + containerJson['properties']['volumeMounts'][0]['name'] = aciSourceVideoMountName |
| 95 | + containerJson['properties']['volumeMounts'][0]['mountPath'] = aciSourceVideoPath |
| 96 | + containerJson['properties']['volumeMounts'][1]['name'] = aciDestinationVideoMountName |
| 97 | + containerJson['properties']['volumeMounts'][1]['mountPath'] = aciDestinationVideoPath |
| 98 | + returnArray.append(containerJson) |
| 99 | + |
| 100 | + # For testing the returnArray |
| 101 | + #print(returnArray) |
| 102 | + |
| 103 | + return func.HttpResponse( |
| 104 | + json.dumps(returnArray), |
| 105 | + status_code=200 |
| 106 | + ) |
0 commit comments