AWSTemplateFormatVersion: 2010-09-09

Description: Complicance Content Alert - Video Analysis

Metadata:

  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: Media Storage
      Parameters:
      - S3MediaBucket
    - Label:
        default: Metadata Database
      Parameters:
      - DynamoDBMetadataTableName
    - Label:
        default: VideoAnalysis Job Info Database
      Parameters:
      - DynamoDBVideoAnalysisTableName
    - Label:
        default: Provide an email address to subscribe to alerts
      Parameters:
      - EmailAddr

Parameters:
  S3MediaBucket:
    Type: String
    Description: S3 Media and Asset Storage Bucket Name
  DynamoDBMetadataTableName:
    Type: String
    Description: DynamoDB Metadata Table Name
  DynamoDBVideoAnalysisTableName:
    Type: String
    Description: VideoAnalysis Job Info Database
  EmailAddr:
    Type: String
    Description: Email address to subscribe to alerts

Resources:
  PolicytoAccessS3:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: AccesstoS3
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
                - 's3:GetObject'
                - 's3:GetBucketLocation'
            Resource:              
              - !GetAtt
                - S3MediaBucketWithEvents
                - Arn
              - !Join
                - ''
                - - !GetAtt
                    - S3MediaBucketWithEvents
                    - Arn
                  - '/*'
      Roles:
        - !Ref VideoProcessorRole
    DependsOn:
      - S3MediaBucketWithEvents

  PolicytoAccessDDB:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: AccesstoDynamoDB
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: 
                - 'dynamodb:PutItem'
                - 'dynamodb:GetItem'
            Resource:
              - !GetAtt CreateVideoMetadataDynamoDBTable.Arn
              - !GetAtt CreateVideoFileDynamoDBTable.Arn
      Roles:
        - !Ref VideoProcessorRole
        - !Ref ContentAlertRole

  PolicytoAccessRekognition:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: AccesstoRekognition
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: 
                - 'rekognition:GetContentModeration'
                - 'rekognition:StartContentModeration'
            Resource: '*'
      Roles:
        - !Ref VideoProcessorRole
        - !Ref ContentAlertRole
  
  PolicytoAccessSNS:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: AccesstoSNS
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: 
                - 'sns:Publish'
                - 'sns:Subscribe'
            Resource: '*'
      Roles:
        - !Ref VideoProcessorRole
        - !Ref ContentAlertRole
        - !Ref RekogntionVideoCompletionSNSPublishRole
 
  PolicytoAccessCWLogs:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: AccesstoCWLogs
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: 
                - 'logs:CreateLogStream'
                - 'logs:PutLogEvents'
            Resource: 
                - !Join
                    - ''
                    -   - 'arn:aws:logs:'
                        - !Ref AWS::Region
                        - ':'
                        - !Ref AWS::AccountId
                        - ':*'
          - Effect: Allow
            Action: 'logs:CreateLogGroup'
            Resource: 'arn:aws:logs:*:*:*'
      Roles:
        - !Ref VideoProcessorRole
        - !Ref ContentAlertRole

  PolicytoPassRole:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: AbiltytoPassRole
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: 
                - 'iam:PassRole'
            Resource: !GetAtt 
              - RekogntionVideoCompletionSNSPublishRole
              - Arn
      Roles:
        - !Ref VideoProcessorRole
              
  ContentAlertRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: 
                - !Ref AWS::AccountId
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
  
  VideoProcessorRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: 
                - !Ref AWS::AccountId
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
  
  RekogntionVideoCompletionSNSPublishRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: 
                - rekognition.amazonaws.com
              AWS: !Ref AWS::AccountId
            Action:
              - 'sts:AssumeRole'
              
  SNSTopicRekognitionVideo:
    Type: 'AWS::SNS::Topic'
    Properties:
      DisplayName: Track Rekognition Video Progress
      Subscription: 
        - Endpoint: 
            Fn::GetAtt: 
                - 'ContentAlertFunction'
                - 'Arn'
          Protocol: 'lambda'
      TopicName: TrackVideoProgress
    DependsOn:
      - ContentAlertFunction
  
  SNSTopicVideoAlert:
    Type: 'AWS::SNS::Topic'
    Properties:
      DisplayName: Alert Subscribers about Inappropriate content
      Subscription: 
        - Endpoint: !Ref EmailAddr
          Protocol: email
      TopicName: AlertVideoContent
      
  ContentAlertFunctionSNSPermissions:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName:
        Fn::GetAtt: 
            - 'ContentAlertFunction'
            - 'Arn'
      Action: 'lambda:InvokeFunction'
      Principal: sns.amazonaws.com
      SourceArn: !Ref SNSTopicRekognitionVideo
    DependsOn:
      - ContentAlertFunction
      
  VideoProcessorFunctionS3Permissions:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName:
        Fn::GetAtt: 
            - 'VideoProcessorFunction'
            - 'Arn'
      Action: 'lambda:InvokeFunction'
      Principal: s3.amazonaws.com
      SourceArn: !Sub arn:aws:s3:::${S3MediaBucket}
    DependsOn:
      - VideoProcessorFunction
      
  CreateVideoFileDynamoDBTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      AttributeDefinitions:
        - AttributeName: JobId
          AttributeType: S
      KeySchema:
        - AttributeName: JobId
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: '5'
        WriteCapacityUnits: '5'
      TableName: !Ref DynamoDBVideoAnalysisTableName

  CreateVideoMetadataDynamoDBTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      AttributeDefinitions:
        - AttributeName: VideoName
          AttributeType: S
        - AttributeName: Timestamp
          AttributeType: N
      KeySchema:
        - AttributeName: VideoName
          KeyType: HASH
        - AttributeName: Timestamp
          KeyType: RANGE
      ProvisionedThroughput:
        ReadCapacityUnits: '5'
        WriteCapacityUnits: '5'
      TableName: !Ref DynamoDBMetadataTableName
  
  LambdaToPublishTopicPolicy:
    Type: 'AWS::SNS::TopicPolicy'
    Properties:
      Topics:
        - !Ref SNSTopicRekognitionVideo
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action: 'sns:Publish'
            Resource: !Ref SNSTopicRekognitionVideo
            Principal:
              AWS: !Ref AWS::AccountId

  
  S3MediaBucketWithEvents:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref S3MediaBucket
      AccessControl: Private
      LifecycleConfiguration:
        Rules:
        - Id: GlacierRule
          Status: Enabled
          ExpirationInDays: '365'
          Transitions:
            - TransitionInDays: '30'
              StorageClass: STANDARD_IA      
            - TransitionInDays: '90'
              StorageClass: GLACIER          
      NotificationConfiguration:
        LambdaConfigurations:
          - Function: !GetAtt VideoProcessorFunction.Arn
            Event: 's3:ObjectCreated:*'
            Filter:
              S3Key:
                Rules:
                  - Name: suffix
                    Value: '.mp4'
    DependsOn:
      - VideoProcessorFunctionS3Permissions
      - LambdaToPublishTopicPolicy
      
  S3MediaBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3MediaBucketWithEvents
      PolicyDocument:
        Statement:
        - Sid: PublicReadForGetBucketObjects
          Effect: Allow
          Principal:
            AWS: 
              - !Ref AWS::AccountId
          Action: s3:GetObject
          Resource: !Join ['', ['arn:aws:s3:::', !Ref 'S3MediaBucket', /*]]
    DependsOn:
      - S3MediaBucketWithEvents
      
  ContentAlertFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
            from __future__ import print_function
            from __future__ import print_function
            import boto3
            import json
            import os
            # --------------- Main handler ------------------   
            # SNS trigger initiates this lambda funtion to call
            # Rekognition GetContentModerationVideo API 
            # and save the metadata information in DynamoDB
            #------------------------------------------------  
            def lambda_handler(event, context):
                # Loading Environment variables
                topicArn =os.environ['SNSTopic']
                tName = os.environ['MetadataTableName']
                jobInfoTable = os.environ['VideoTableName']
                
                # Loading Boto clients
                rekognition = boto3.client('rekognition')
                sns = boto3.client('sns')
                table = boto3.resource('dynamodb').Table(tName)
                client = boto3.resource('dynamodb').Table(jobInfoTable)
                
                # Counter variables defined
                explicitNudity=0
                suggestive=0
                
                # Analyzing the SNS event trigger
                message = json.loads(event['Records'][0]['Sns']['Message'])
                try:
                    # Checks to see if Rekogntion Video posted the SNS message with SUCCEEDED status
                    if message['Status']=="SUCCEEDED":
                        job= str(message['JobId'])
                        responseGet = rekognition.get_content_moderation(JobId=job, MaxResults=123, SortBy="TIMESTAMP")
                        moderationLabels=responseGet['ModerationLabels']
                    
                        # Check the Rekogntion Job Information table for the video file name
                        response = client.get_item(Key={'JobId':job })
                        vfile=response['Item']['VideoName']
                        # Store the Content moderation metadata
                        for label in moderationLabels:
                            # Metadata of the inappropriate content
                            tstamp = int(label['Timestamp'])
                            confidence = str(label['ModerationLabel']['Confidence'])
                            parentName = str(label['ModerationLabel']['ParentName'])
                            name = str(label['ModerationLabel']['Name'])
                            print(vfile+'|'+str(tstamp)+'|'+confidence+'|'+parentName+'|'+name)
                            # If parent name is notapplicable to replicate as the name
                            if parentName =='':
                                parentName=name
                            #Inster metadata into the database
                            table.put_item(Item={'VideoName': vfile, 'Timestamp': tstamp, 'Confidence':confidence, 'ModerationLabel':name, 'ModerationCategory':parentName})    
                            if parentName=='Explicit Nudity':
                                explicitNudity+=1
                            else:
                                suggestive+=1
                        # If there is inappropriate content in the video,  notify the end user via SNS
                        if explicitNudity or suggestive > 0: 
                            i = "This video file {} consists of {} explicitNudity scenes and {} suggestive scenes"
                            message=i.format(vfile,explicitNudity,suggestive)
                            print(message)
                            sns.publish(TopicArn=topicArn,Message=message,Subject='Alert Notification')
                    else:
                        print(message['Status'])
                        print("Received Response:Awaiting results")
                except Exception as e:
                    print(e)
      Description: Initiates Video Content moderation Analysis using Rekognition Video
      FunctionName: ContentAlertFunction
      Handler: index.lambda_handler
      MemorySize: 128
      Environment:
        Variables:
          MetadataTableName: !Ref DynamoDBMetadataTableName
          VideoTableName: !Ref DynamoDBVideoAnalysisTableName
          SNSTopic: !Ref SNSTopicVideoAlert
      Role: !GetAtt 
        - ContentAlertRole
        - Arn
      Runtime: python2.7
      Timeout: 60
    DependsOn:
      - ContentAlertRole

  VideoProcessorFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
            from __future__ import print_function
            import boto3
            from decimal import Decimal
            import json
            import urllib
            import random
            import os

            # --------------- Main handler ------------------
            # S3 trigger initiates this lambda funtion to call
            # Rekognition StartContentModerationVideo API 
            # and save the videofile information in DynamoDB
            #------------------------------------------------ 

            def lambda_handler(event, context):
                
                # Loading Environment variables
                ddbtable = os.environ['TableName']
                ddbtable = os.environ['TableName']
                snstopic =os.environ['SNSTopic']
                rolearn = os.environ['RoleARN']
                
                # Loading Boto clients
                table = boto3.resource('dynamodb').Table(ddbtable)
                rekognition = boto3.client('rekognition')
                
                # Analyze the S3 event notification
                bucket = event['Records'][0]['s3']['bucket']['name']
                key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key'].encode('utf8'))
                print (bucket)
                print (key)
                i="Initiated"
                
                # Initiate Rekogntion StartContentModeration API
                try:
                    response = rekognition.start_content_moderation(Video={"S3Object": {"Bucket": bucket,"Name": key}}, MinConfidence=50,NotificationChannel={"SNSTopicArn": snstopic,"RoleArn": rolearn})
                    print(response)
                    jobId=response['JobId']
                    
                    # Place video processing metadata into the database
                    table.put_item(Item={'VideoName': key, 'JobId': jobId, 'ContentModeration': i})
                except Exception as e:
                    print(e)
                    print("Error processing object {} from bucket {}. ".format(key, bucket) +
                          "Make sure your object and bucket exist and your bucket is in the same region as this function.")
                    raise e
      Description: Intiates Video Content Moderation output in DynamoDB
      FunctionName: VideoProcessorFunction
      Handler: index.lambda_handler
      MemorySize: 128
      Environment:
        Variables:
          TableName: !Ref DynamoDBVideoAnalysisTableName
          SNSTopic: !Ref SNSTopicRekognitionVideo
          RoleARN: !GetAtt 
            - RekogntionVideoCompletionSNSPublishRole
            - Arn
      Role: !GetAtt 
        - VideoProcessorRole
        - Arn
      Runtime: python2.7
      Timeout: 60
    DependsOn:
      - SNSTopicRekognitionVideo
