├── .github └── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── RuleBasedEncoding ├── .gitignore ├── README.md ├── assets │ ├── TheTemplateForM2TS.json │ ├── TheTemplateForMP4.json │ ├── rulesWorkflowM2TS.json │ ├── rulesWorkflowMXF.json │ └── rulesWorkflowQuickTime.json ├── deploy │ ├── build-s3-dist.sh │ ├── chalice-fix-inputs.py │ └── sam-translate.py ├── keys.yaml ├── ruleapi │ ├── .chalice │ │ ├── config.json │ │ └── dev-app-policy.json │ ├── .gitignore │ ├── app.py │ └── requirements.txt ├── ruleweb.yaml ├── test │ ├── rulesonawsJobs │ │ ├── containervariables.json │ │ ├── starlight.json │ │ └── test.json │ ├── testMediainfoRulesEngine.json │ ├── testMediainfoRulesEngineProfiler.json │ └── vodonawsJobs │ │ ├── beach.json │ │ ├── caminades.json │ │ ├── coffee.json │ │ ├── donuts.json │ │ ├── silksmxf.json │ │ ├── starlight.json │ │ ├── testMetadata.json │ │ ├── van_life_scte35.json │ │ └── wine.json ├── website │ ├── deploy │ │ └── config.js │ ├── images │ │ ├── RulesBasedEncodingArchitcture.png │ │ ├── RulesBasedEncodingWorkflow.png │ │ ├── RulesBasedEncodingWorkflow1.png │ │ └── RulesBasedEncodingWorkflowSimple.png │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── lib │ │ │ └── jquery.js │ │ └── ruleapp │ │ │ ├── main.js │ │ │ └── ui │ │ │ ├── querybuilder │ │ │ ├── filters.js │ │ │ ├── qb-view-rule.js │ │ │ ├── qb.js │ │ │ └── query-builder-business-rules.js │ │ │ └── settings_menu.js │ ├── package-lock.json │ ├── qb.html │ ├── qb_view_rule.html │ ├── rules_list.html │ └── style.css └── workshop.yaml └── images ├── RulesServerlessApp.png ├── ServerlessWebApp-new.png ├── ServerlessWebApp.png ├── TestRules.png ├── WorkflowServerlessApp.png ├── api-RestAPI-resource.png ├── api-gateway-key.png ├── api-gateway-resources.png ├── api-gateway-test-rules.png ├── api-gateway-test-vodonaws.png ├── api-hello.png ├── api-key.png ├── api-rules-forbidden.png ├── api-usage-plan-stage.png ├── api-vodonaws-forbidden.png ├── app-rule-execution.png ├── app-sample-rule.png ├── cfn-changesets.png ├── cfn-ruleapi-outputs.png ├── cfn-rules-outputs.png ├── cfn-ruleweb-outputs.png ├── create-new-rule.png ├── forbidden.png ├── lambda-find-mediainfoRulesProfiler.png ├── mediaconvert-create-template.png ├── mediainfoBusinessRulesLambda.png ├── metadata-test.png ├── metadata-watchfolder-new.png ├── metadata-watchfolder.png ├── process-state-machine-definition.png ├── process-step-function.png ├── profiler-step-inputs.png ├── profiler-step-output.png ├── profiler-step-outputs.png ├── ruleContainerEqMxf.png ├── s3-events-new.png ├── s3-events.png ├── s3-json-event-trigger-new.png ├── s3-json-event-trigger.png ├── step-hello.png ├── step-replace-profiler-new.png ├── step-replace-profiler.png ├── success.png ├── vodonaws-changes-new.png ├── vodonaws-changes.png ├── web-setup.png ├── web-workflow-details.png └── web-workflow.png /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/aws-mediaservices-vod-starter-lab/issues), or [recently closed](https://github.com/aws-samples/aws-mediaservices-vod-starter-lab/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/aws-mediaservices-vod-starter-lab/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/aws-mediaservices-vod-starter-lab/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Mediaservices Vod Starter Lab 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Media Services VOD Workflow Composer 2 | 3 | ## Overview 4 | 5 | Create flexible, intelligent file-based video workflows on AWS using dynamic rules. 6 | 7 | * API for creating rules that can be run against videos to automate encoding decsions and perform quality control for file-based video delivery workflows. 8 | * A rule execution engine for evaluating dynamic rules within video workflows. 9 | * A serverless application for creating rules 10 | * A sample workflow using dynamic rules. 11 | * A tutorial for how to build the sample application. 12 | 13 | ### Rule Builder 14 | 15 | * Create named rule expressions using a form based user interface. 16 | * The sample uses [Mediainfo](https://mediaarea.net/en/MediaInfo) video analysis as the domain for creating expressions. 17 | * The form is implemented using the [JQuery QueryBuilder](https://querybuilder.js.org/index.html) JavaScript project. 18 | 19 | ![sample rule screenshot](images/app-sample-rule.png) 20 | 21 | ### Rule Execution 22 | 23 | * Rules are executed as a part of a file-based video processing workflow using the [business-rules](https://github.com/venmo/business-rules) Python package in an AWS Lambda. 24 | 25 | ### Sample VOD workflow using the Video on Demand on AWS Solution 26 | 27 | * A sample workflow using the [Video on Demand on AWS](https://aws.amazon.com/answers/media-entertainment/video-on-demand-on-aws/) solution is provided. The workflow takes a list of _ruleMappings_ as input to the VOD on AWS solution metadata trigger and uses the rule execution lambda function to decide which MediaConvert template (encoding settings) should be used to process a video input as part of an unattended workflow. ruleMappings have the following format: 28 | 29 | ```json 30 | ruleMappings [ 31 | { 32 | "ruleName" : "RuleBuilderRuleName", 33 | "template" : "MediaConvertJobTemplateName" 34 | } 35 | ] 36 | ``` 37 | 38 | * A webpage is included to monitor the sample workflow 39 | 40 | ![Rule execution screenshot](images/app-rule-execution.png) 41 | 42 | 43 | 44 | ### REST API 45 | 46 | All browser actions are performed through an authenticated and SSL encrypted REST API hosted in the cloud. The API can be used by other tools to manage rules, execute rules and monitor the sample workflow. 47 | 48 | ## Navigate 49 | 50 | Navigate to [README](README.md) | [Workshop](RuleBasedEncoding/README.md) -------------------------------------------------------------------------------- /RuleBasedEncoding/.gitignore: -------------------------------------------------------------------------------- 1 | **.zip 2 | sam.* 3 | deploy/dist 4 | **dist 5 | **.pyc 6 | deploy/build-s3-dist-local.sh -------------------------------------------------------------------------------- /RuleBasedEncoding/README.md: -------------------------------------------------------------------------------- 1 | # Customizing the Video On Demand on AWS solution with dynamic encoding rules 2 | 3 | ## Introduction 4 | 5 | If you prefer, skip this introduction and [jump to the start of the step-by-step instructions.](#Deploy-the-Lab-Toolkit) 6 | 7 | In this tutorial, you will build additional intelligence into the [Video on Demand on AWS](https://aws.amazon.com/answers/media-entertainment/video-on-demand-on-aws/) (_VOD_) solution by adding dynamic, user-defined rules to make automatic encoding decisions during execution of the workflow. This change will enable end users who may have access to MediaConvert and the solution S3 bucket, but not the internals of the solution, to develop new video processing workflows without modifying the underlying code for the solution. 8 | 9 | The Video on Demand on AWS solution leverages AWS Step Functions, which breaks the workflow into individual steps, making it easier to customize or extend the architecture for your specific video-on-demand needs. For example, you can modify or replace the encoding steps to produce different content sets. You can also add steps to extend support for more complex workflows, including, for example, image processing for poster artwork or additional custom data to the metadata file that will then be stored in Amazon DynamoDB. 10 | 11 | The solution already implements some hard-coded rules that we will replace with dynamic rules. The existing rules checks the Mediainfo analysis results of the input video and decides on a job template that will avoid producing _up-converted_ video outputs - that is videos with a higher resolution than the input video. 12 | 13 | ## Changes to the Video on Demand solution end-user workflow 14 | 15 | When this module is complete the end user experience for using the Video on Demand solution will be as follows: 16 | 17 | 1. Create MediaConvert templates (e.g. using the AWS Elemental MediaConvert console). In our workshop, we will be using existing templates that were either generated from the lab toolkit or system templates provided by MediaConvert. However, the new workflow supports any MediaConvert template. 18 | 19 | ![create template](../images/mediaconvert-create-template.png) 20 | 21 | 2. Create Mediainfo Rules using form input on a webpage. Rules are stored in DynamoDB and can be retrieved by name via an API or AWS Lambda. 22 | 23 | ![create mediainfo rules](../images/create-new-rule.png) 24 | 25 | 3. Use input JSON metadata to trigger Video on Demand on AWS workflows. The metadata includes **_Rule Mappings_** to decide on which MediaConvert Template should be used for a specific input using JSON metadata. 26 | 27 | ![metadata watchfolder](../images/metadata-watchfolder-new.png) 28 | 29 | 4. View the results of the Video on Demand on AWS jobs in the Workflow webpage. 30 | 31 | ![view the workflows](../images/success.png) 32 | 33 | ## Overview of Implementation Steps: 34 | 35 | The changes to the Video on Demand on AWS solution that are needed to support the new workflow are as follows: 36 | 37 | 1. Change the Video on Demand on AWS solution user interface (S3 bucket watchfolder) to take a file containing JSON metadata as input, including **Rule Mappings**. 38 | 2. Deploy and configure a serverless web application for creating named expressions, called **_Mediainfo Rules_**, that can be evaluated against facts from Mediainfo metadata for a video. 39 | 3. Deploy and configure a **_Mediainfo Rules Engine Profiler_** lambda function that take Mediainfo metadata for a specific video and evaluates a list of Mediainfo Rules and returns the result. 40 | 4. Change the Video on Demand on AWS solution to use the Mediainfo Rules Engine lambda in place of the existing logic to select a job template for a given input. 41 | 42 | ## Open source used in implementation 43 | 44 | The following Open Source packages are used to develop this customization. 45 | 46 | * [**jQuery QueryBuilder**](https://querybuilder.js.org/) An open source JavaScript package that lets you build expressions using a web UI and translates the expression into different query engine formats such as SQL, Elastic Search, etc. We will be translating the expressions to _business-rules_ JSON format. 47 | 48 | * [**business-rules**](https://github.com/venmo/business-rules) - an Open Source Python package that lets you execute rules against a set of configured variables. 49 | 50 | # Prerequisites 51 | 52 | 1. Amazon Web Services account with root or Administrator permissions 53 | 2. Workshop version of the [Video On Demand on AWS](https://aws.amazon.com/answers/media-entertainment/video-on-demand-on-aws/) solution deployed in the same region you will complete the tutorial in. The lab uses the name `reinvent-vod` for this stack. The following CloudFormation stack parameters need to be set during deployment: 54 | * FrameCapture = true 55 | * WorkflowTrigger = VideoFile (we will change the trigger to Metadata as part of the workshop) 56 | 57 | Click the button to launch the lab version of the Video on Demand on AWS stack. 58 | 59 | eu-west-1 (Ireland) | [![Launch in eu-west-1](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=reinvent-vod&templateURL=https://s3-eu-west-1.amazonaws.com/decepticons-eu-west-1/video-on-demand-on-aws/reinvent/video-on-demand-on-aws.yaml) 60 | 61 | 3. Google Chrome, Mozilla Firefox, or another current browser with JavaScript enabled. Google Chrome is recommended. 62 | 4. (OPTIONAL) If you are not completing this as part of an in-person workshop, you will need to copy the workshop test videos into the Source bucket for your Video On Demand on AWS Stack. This can be done from the AWS S3 commandline using the following commands. Replace YOUR-REINVENT-VOD-SOURCE-BUCKETNAME with the name of the Source output from the `reinvent-vod` stack: 63 | ``` 64 | aws s3 cp s3://rodeolabz-eu-west-1/rules/videos/van_life.mp4 s3://YOUR-REINVENT-VOD-SOURCE-BUCKETNAME 65 | 66 | aws s3 cp s3://rodeolabz-eu-west-1/rules/videos/starlight_2160p59.m2ts s3://YOUR-REINVENT-VOD-SOURCE-BUCKETNAME 67 | 68 | aws s3 cps3://rodeolabz-eu-west-1/rules/videos/silksintrees_MPEG2.mxf s3://YOUR-REINVENT-VOD-SOURCE-BUCKETNAME 69 | ``` 70 | 71 | # Deploy the Lab Toolkit 72 | 73 | The lab toolkit is installed in your account using the workshop.yaml CloudFormation template. This template runs several nested templates that create the S3, API Gateway, IAM and Lambda resources needed to complete the workshop. We will examine the resources created and modify them as we proceed through the tutorial. 74 | 75 | ## Instructions 76 | 77 | 1. Click **Launch Stack** to launch the template in your account in the same region you deployed the `reinvent-vod` stack. The stacks should take a few minutes to deploy. 78 | 79 | 80 | Region | Launch 81 | ------|----- 82 | 83 | 84 | eu-west-1 (Ireland) | [![Launch in eu-west-1](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=reinvent-rules&templateURL=https://s3.amazonaws.com/rodeolabz-eu-west-1/rules/3-rulesbasedencoding/v3/workshop.yaml) 85 | 86 | us-west-2 (Oregon) | [![Launch in us-west-2](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=reinvent-rules&templateURL=https://s3.amazonaws.com/rodeolabz-us-west-2/rules/3-rulesbasedencoding/v3/workshop.yaml) 87 | 88 | 1. Name the stack or use the default name `reinvent-rules`. We will refer to the stack as **reinvent-rules** throughout this tutorial. 89 | 2. Fill in the **vodstack** parameter with the name of the stack you created for the Video on Demand solution. We will refer to this stack as the **reinvent-vod** stack throughout this tutorial. 90 | 3. **Save the stack details page in a browser tab for future reference.** 91 | 4. During the installation of the CloudFormation templates you may be prompted to acknowledge the creation of IAM resources. Click on the check-boxes next to each entry. Finally, click the **Create** or **Create Change set** and **Execute** buttons where applicable. 92 | 93 | ![chagesets](../images/cfn-changesets.png) 94 | 95 | ## Outputs 96 | 97 | The information about the resources created by this stack is in the **Outputs** tab. For convenience, the outputs of the Video on Demand solution stack are also passed through and listed here as well. 98 | 99 | **Save this page in a browser tab for future reference. Or, copy and save the outputs to a file.** 100 | 101 | * **APIHandlerArn** - ARN of the Lambda function that serves as the back-end for the /rule and /vodonaws APIs 102 | * **APIHandlerName** - Name of the Lambda function that serves as the back-end for the /rule and /vodonaws APIs 103 | * **APIEndpointURL** - HTTPS endpoint for the Rules and Workflow API endpoint 104 | * **APIKey** - API Key for testing the Rules and Workflow API 105 | * **WebsiteURL** - Workflow Monitoring page and Rule creation webpages 106 | * **WorkflowCloudFront** - CloudFront Domain Name for the Video on Demand on AWS solution 107 | * **WorkflowSource** - Source Bucket for the Video on Demand on AWS solution 108 | * **WorkflowDestination** - Destination Bucket for the Video on Demand on AWS solution 109 | * **WorkflowDynamoDBTable** - DynamoDB Table for the Video on Demand on AWS solution 110 | * **RulesTableName** - DynamoDB Table for Rules API 111 | 112 | ## Other resources we will work with 113 | 114 | We will also use the following resources created by the ruleapi nested stack: 115 | 116 | * **MediainfoRulesEngineProfiler** - a replacement lambda for the existing Video on Demand on AWS profiler lambda. 117 | 118 | # Part 1: Change the Video on Demand on AWS S3 trigger to use input Metadata 119 | 120 | ![metadata watchfolder](../images/metadata-watchfolder-new.png) 121 | 122 | The Video on Demand on AWS solution supports two types of S3 workflow triggers: 123 | 124 | 1. **Video-Only Workflow** 125 | 126 | When a new MP4, MPG, M4V, or MOV video is added to the source Amazon S3 bucket, a Lambda function triggers the ingest workflow. 127 | 128 | 2. **Metadata and Video Workflow** 129 | 130 | When a new JSON metadata file is added to the source Amazon S3 bucket, a Lambda function triggers the ingest workflow. 131 | 132 | Since we deployed the solution stack using the default, Video-Only Workflow trigger, we will need to change the S3 trigger setting to start the workflow using Metadata and Video. 133 | 134 | Once the Metadata and Video Workflow is enabled, any key-value pairs we add to the Metadata input file will be available to the Step Functions and Lambdas throughout the workflow. Each time the workflow is initiated, the solution creates a unique identifier (guid). All metadata including the input Metadata is stored in DynamoDB. The unique identifier is used as the primary key in Amazon DynamoDB and the execution ID in AWS Step Functions. The unique identifier is passed to each step in the workflow, allowing information to be stored and retrieved in DynamoDB. 135 | 136 | We will take advantage of Metadata to allow end-users to pass in addtional information the workflow can use to decide on encoding templates using rules. 137 | 138 | ## Instructions 139 | 140 | Use the steps below to change the **S3 ObjectCreate (All)** event trigger on the **Source** bucket to only trigger if the object created in the bucket has a suffix of "json". We don't need to make any other changes to the stack since the workflow will automatically handle extracting metadata from .json files. We will test out passing metadata as part of this step to see how it works. 141 | 142 | 1. Open the S3 console. 143 | 2. Find the **Source** bucket created by the **reinvent-vod** stack and click on the link to open the detail page for the bucket. The bucket name will have a pattern like: `reinvent-vod-source-`. 144 | 3. Navigate to the **Properties** tab and click on the **Events** tile in the **Advanced settings** panel at the bottom of the page. 145 | 4. On the **Event** card select the radio button for the first **ObjectCreate (All)** event trigger. This event should have the value `.mpg` in the filter column. Click on the **Edit** link. 146 | 5. Replace the existing value in the **Suffix** box with `.json` and click **Save** to update the trigger. 147 | 148 | ![json trigger](../images/s3-json-event-trigger-new.png) 149 | 150 | 6. On the **Event** card, select the radio button for each non-json **ObjectCreate (All)** event trigger and click on the **Delete** button. The only remaining trigger should be the JSON trigger. 151 | 152 | ![s3 events](../images/s3-events-new.png) 153 | 154 | 155 | ## Test the metadata trigger 156 | 157 | Now let's create a Video on Demand on AWS workflow using the Metadata trigger. We'll add an extra, arbitrary, key-value pair to pass a message to ourselves through the solution. This will help us prove to ourselves that JSON attributes added in the input metadata are persisted in the DynamoDB data stored for the workflow. 158 | 159 | 1. On your computer, create a file called `testMetadata.json` 160 | 2. Copy the following JSON into the file and save. 161 | ```json 162 | { 163 | "srcVideo": "van_life.mp4", 164 | "FrameCapture": true, 165 | "MetadataTestMessage": "Hello from the other side!" 166 | } 167 | ``` 168 | 3. Open the S3 console and click on the link for the **Source** bucket that was created by the Video on Demand on AWS workflow. 169 | 4. Click on the **Upload** button and use the dialog box to locate and select the `testMetadata.json` input metadata file you just created. 170 | 5. Click on the **Upload** button to start the upload. 171 | 6. To verify the VOD solution workflow is triggered, open the AWS Step Functions console and find the **reinvent-vod-process** step function. Click on the link to open the detail page. 172 | 7. On the **Executions** panel, find the most recent invocation of the state machine and click the link to go to the details of that invocation. 173 | 8. The **Execution status** should be **Succeeded**. 174 | 9. Now, click on the **Profiler** step in the **Visual Workflow** card. This will load the input, output and exception information for that step into the **Step details (Profiler)** card. 175 | 10. Expand the twisty for the **Step details (Profiler)** **Outputs** section. 176 | 11. Locate the "MetadataTestMessage" key value pair we passed in with the VOD workflow input metadata. 177 | 178 | ![Step Function Helllo](../images/step-hello.png) 179 | 180 | The VOD solution will pass along all the key value pairs in the JSON metadata used to trigger the workflow, even if they are not used by the current workflow. This behavior will enable us to pass Mediainfo Rules when we trigger a new workflow. The rules can be used later in code we add to the VOD solution step functions. 181 | 182 | # Part 2: Configure the serverless application used to create rules 183 | 184 | The Lab Toolkit stack (`reinvent-rules`) created webpages backed by API Gateway, Lambda and DynamoDB that will be used to: 185 | 1) drive the Mediainfo Rule creation 186 | 2) to visualize the workflow data created by the Video on Demand on AWS solution. 187 | 188 | In this section of the workshop, we will configure and test these resources to work with the Video on Demand on AWS solution stack you deployed earlier. 189 | 190 | ![Rules and Workflows](../images/ServerlessWebApp-new.png) 191 | 192 | 193 | You will need to refer to the stack outputs from the **reinvent-rules** stack throughout this module. 194 | 195 | The API and Lambda resources created for the **reinvent-rules** stack were developed using [Chalice](https://github.com/aws/chalice), a microframework for writing serverless applications in python. It allows you to quickly create and deploy applications that use AWS Lambda and API Gateway. For reference, the code for the API is [ruleapi/app.py](ruleapi/app.py). 196 | 197 | The **/rules API Gateway API** creates, manages and executes user-defined rule expressions. It has the following endpoints: 198 | 199 | * **POST /rules/{rule-name}** - create or update a rule with name `rule-name` 200 | * **GET /rules** - list all rules 201 | * **GET /rules/variables** - list all the mediainfo elements that can be used to write new rules. 202 | * **GET /rules/{rule-name}** - list the rule named `rule-name` 203 | 204 | The **/vodonaws API Gateway API** has the following endpoints. 205 | 206 | * **GET /vodonaws** - list all instances of the Video on Demand on AWS workflow 207 | 208 | ## Instructions 209 | 210 | ### Test the API in the browser 211 | 212 | 1. Open the link for **APIEndpointURL** output from the **reinvent-rules** stack in a new browser tab. 213 | 2. You should see a "Hello World!" response. 214 | 215 | ![hello output](../images/api-hello.png) 216 | 217 | 3. Now add `/vodonaws` to the end of the URL. You should see a "Forbidden" message because the /vodonaws API is configured to require and API key which we will add in the next step. 218 | 219 | ![FIXME vodonaws list output](../images/api-vodonaws-forbidden.png) 220 | 221 | 4. Now add `/rules` to the end of the EndpointURL value in your browser. You should see a "Forbidden" message because the /rules API is configured to require and API key which we will add in the next step. 222 | 223 | ![FIXME vodonaws list output](../images/api-rules-forbidden.png) 224 | 225 | 226 | ### Configure the VOD on AWS **Workflow** and **Rules** webpages 227 | 228 | The Mediainfo Rules Web Application is secure after installation because it requires an API key to access any of the REST API endpoints, and the CloudFormation template doesn't configure API keys for the web application automatically. **By default, no access is possible until the following steps are performed.** 229 | 230 | #### Instructions for First Run (Important) 231 | 232 | Each time the Mediainfo Rules web application is launched, the browser's locally stored cookies are checked for any previous API connection information. If a previous connection is found, it is used by the browser automatically on the next launch. 233 | 234 | An API key was created in the reinvent-rules toolkit stack. 235 | 236 | 1. Go to the **AWS API Gateway** console page. 237 | 2. Select the **API Keys** tab 238 | 3. Click on the API Key named **TestUserAPIKey**. This API key was created by the **reinvent-rules** stack. 239 | 240 | ![API Key](../images/api-gateway-key.png) 241 | 242 | 4. Click the **Show** option to see the actual API Key 243 | 5. Copy the API Key 244 | 245 | ![api key](../images/api-key.png) 246 | 247 | 6. Find the **WebsiteURL** output parameter from the **reinvent-rules** stack and open the link in a new browser tab. 248 | 7. Click on the **Setup** button at the top of the web page. 249 | 8. Enter the API key you copied in the **API Key** box. 250 | 9. Enter the API endpoint you tested in the previous section in the **Endpoint URL** box (i.e. the **APIEndpointURL** output from the **reinvent-rules** stack). 251 | 252 | ![setup](../images/web-setup.png) 253 | 254 | 10. Click on the **Save** button to save the settings. 255 | 256 | 257 | ### Test the **Workflow** web page 258 | 259 | The workflow webpage displays the output of the **GET /vodonaws** request. 260 | 261 | 1. After entering the API endpoint you should see a list of Video on Demand on AWS workflow instances you ran in previous modules of this tutorial. 262 | 263 | ![Workflow page screeenshot](../images/web-workflow.png) 264 | 265 | 2. Click on any of the rows to see the details for that workflow instance. 266 | 267 | ![Workflow page detail screenshot](../images/web-workflow-details.png) 268 | 269 | ## Test the **Rules** webpage and create some rules to use later 270 | 271 | The **Rules** webpage lists all the rules that have been created. It displays the results of the **GET /rules** API request. 272 | 273 | The **Create Rule** button on the **Rules** webpage lets you create or update a rule to be managed by the web application. It uses the form input as parameters to the **POST /rules/{rule-name}** API request. 274 | 275 | **IMPORTANT: Make sure to create the exact rules listed in these steps. We will use these later to test the end to end workflow.** 276 | 277 | 1. Navigate to the **Rules** webpage by selecting the **Rules** button at the top of the **Workflow** webpage. You can toggle back and forth between the pages using these buttons. 278 | 2. Click on **Create rule**. 279 | 3. Fill in the expression builder form to create a rule that tests if the container type of the input file is MXF and name it `Container_eq_MXF`. 280 | 281 | ![Container MXF](../images/ruleContainerEqMxf.png) 282 | 283 | 284 | 4. Finally use the **Create** button to save the rule so we can refer to it later. Clicking on **Create** will call the **POST /rules/{rule_name}** API with the form input. 285 | 5. Repeat this step with a rule that checks for the container type of the input file is MP4 and name it `Container_eq_MP4`. 286 | 6. Repeat this step with a rule that checks for the container type of the input file is QuickTime and name it `Container_eq_QuickTime`. 287 | 7. Now you should have the following rules defined on the Rules webpage. 288 | 289 | ![Test Rules](../images/TestRules.png) 290 | 291 | # Part 3: The new MediainfoRulesEngineProfiler lambda function 292 | 293 | Earlier in this workshop we changed the trigger for the VOD solution to use metadata JSON files so we can pass in Mediainfo Rule Mappings that can be used to select encoding settings based on the attributes of the video. 294 | 295 | We have one more change to the VOD solution so it can use the Mediainfo Rule Mappings. We need to replace he hard-coded, static rules in the Process Step Function to execute the rules specified in the input metadata ruleMappings using the Python business-rules package. 296 | 297 | ![changes](../images/vodonaws-changes-new.png) 298 | 299 | To understand this change we will do a quick overview of the existing **reinvent-vod-process** lambda function and the **reinvent-rules-MediainfoRulesEngineProfiler** lambda function. More detailed code walkthoughs and testing can be found in [Appendix: Code Walktroughs](#Appendix:-Code-Walktroughs) below. 300 | 301 | ## Locate the Process Step Function and the Profiler Lambda function 302 | 303 | The decision making for which template to use for the Video on Demand on AWS workflow is located in the _profiler_ lambda. This lambda is invoked as a step in the _Process_ step function. We will replace this lambda function with a new lambda that uses the Python **business-rules** package to evaluate our Mediainfo Rules. 304 | 305 | We'll examine the inputs and outputs of this Lambda. The new lambda needs to to use the same inputs and outputs in order to fit in the state machine. 306 | 307 | 1. Open the Step Functions AWS console. 308 | 2. Click on the link for the `reinvent-vod-process` step function. 309 | 3. Click on a link for one of the successful **Executions** of the step function. 310 | 4. From the Visual Workflow for the step function display on this page, you can see the Profiler step that triggers the profiler lambda. 311 | 312 | ![Process steps](../images/process-step-function.png) 313 | 314 | 5. Click on the **Profiler** node in the **Visual Workflow** to show the inputs and outputs for the step. This is what is passed in and out of the reinvent-vod-profiler lambda function. 315 | 316 | 6. The input is the guid for the Video on Demand on AWS workflow. 317 | 318 | ![profiler step input](../images/profiler-step-inputs.png) 319 | 320 | 7. The output is the updated workflow JSON that will be stored in reinvent-vod-table DynamoDB in table. 321 | 322 | ![FIXME - capture from clean stack - profiler step output](../images/profiler-step-outputs.png) 323 | 324 | 325 | 8. We will need to make sure our new lambda has the same inputs and outputs as the old lambda. As you will see when we examine the reinvent-vod-profiler lambda, the highlighted key-value pairs in the JSON above are what is modified by the reinvent-vod-profiler function. The rest of the JSON was retrieved from data stored in reinvent-vod-table from previous workflow steps. Note that the Mediainfo analysis is already available in the **srcMediainfo** element of the workflow JSON object at the start of this step. 326 | 327 | 9. Click on the lambda function link in the **Resource** section of the **Step details (Profiler)** panel to open the console page for the **reinvent-vod-profiler** lambda. 328 | 329 | ## Profiler lambda overview 330 | 331 | The profiler lambda does the following: 332 | 333 | * Get the latest workflow data from DynamoDB and copy it in to the _event_ output JSON object. 334 | * Retrieve the Mediainfo analysis from the workflow data and set the outputs that come directly from Mediainfo. 335 | * Decide what MediaConvert Job Template to use based on the resolution of the input video (Height x Width). **This is the key piece of code we need to replace with our dynamic rules execution from the mediainfoRulesEngine lambda**. 336 | * Return results in the event object. The results include the name of the MediaConvert job template to use to convert the video. 337 | 338 | For a more detailed walk-through see the Appendix [Profiler lambda](#Profiler-lambda) below. 339 | 340 | ## MediainfoRulesEngineProfiler lambda overview 341 | 342 | The MediainfoRulesEngineProfiler lambda is our replacement lambda for the profiler lambda. It executes the Mediainfo Rules the user specifies and selects a mapped MediaConvert job template based on the results. 343 | 344 | The new lambda starts with the logic from the existing profiler lambda. If no template is found using the ruleMappings, it will fall back to selecting a template using the existing method. 345 | 346 | The ruleMappings input is specified by the user in the JSON metadata file that is used to trigger the workflow. It can be accessed by our lambda from the workflow information stored in DynamoDB ruleMappings have following format: 347 | 348 | ```json 349 | "ruleMappings": [ 350 | { 351 | "ruleName": "Rule1", 352 | "template": "Template1" 353 | }, 354 | { 355 | "ruleName": "Rule2", 356 | "template": "Template2" 357 | }, 358 | ... 359 | { 360 | "ruleName": "RuleN", 361 | "template": "TemplateN" 362 | } 363 | ] 364 | ``` 365 | 366 | Each ruleName is the key for the store business-rules JSON object that was created using the Rules web page. Each template is the key for the stored MediaConvert job template create using the AWS Elemental MediaConvert service. 367 | 368 | The lambda Performs the following steps. Steps that different from the existing profiler lambda are labelled with the keyword NEW: 369 | 370 | * **NEW: Rules API DynamoDB table is mapped from the Lambda Enviornment** 371 | * Get the latest workflow data for this guid from DynamoDB 372 | * Retrieve the Mediainfo analysis from the workflow data and set the outputs that come directly from Mediainfo. 373 | * **NEW: Retrieve the ruleMappings from the workflow data.** 374 | * **NEW: Decide on a MediaConvert job template** 375 | 376 | For each ruleMapping passed in by the end-user: 377 | * Look up the rule JSON in the **reinvent-vod-RulesTable**. 378 | * Evaluate the rule using the Python **business-rules** package. 379 | * If the rule expression evaluates to TRUE, select the mapped template and break. 380 | 381 | This implements IF-THEN-ELSE semantics. The ELSE case falls back to using the the solution default templates based on the video resolution. Test results for each rule executed are stored in the ruleMapping JSON elements of the event JSON object. 382 | 383 | * Decide what _default_ MediaConvert Job Template to use based on the resolution of the input video (Height x Width) 384 | * **NEW** Select the default template if no template was selected from the business-rules execution step. 385 | * Return results in the event object. The results include the name of the MediaConvert job template to use to convert the video. 386 | 387 | The resulting code is in **mediainfoRuleEngineProfiler**. 388 | 389 | For a more detailed code walkthough see Appendix [MediainfoRulesEngineProfiler lambda](MediainfoRulesEngineProfiler-lambda) below. 390 | 391 | # Part 4: Replace static rules with dynamic rules in the Video on Demand on AWS solution 392 | 393 | In this section we will replace the **profiler** lambda in **Process** step function with the **mediainfoRuleEngineProfiler** lambda. 394 | 395 | ### Instructions 396 | 397 | 1. Open the AWS Lambda console and search for the pattern `reinvent-rules-api` to find the lambdas created by the ruleapi nested stack. The mediainfoRuleEngineProfiler lambda should be in the list. The name may be truncated if the function was created from a nested stack: 398 | 399 | ![find the lambda](../images/lambda-find-mediainfoRulesProfiler.png) 400 | 401 | 402 | 2. Click on the link to go to the detail page for the lambda. 403 | 3. Copy the ARN for the lambda from the top of the page. 404 | 4. Once again, open the **Step functions->State machines** AWS console page. 405 | 5. Find the `reinvent-vod-process` step function and click on the link to go to the **Details** page. 406 | 6. Click on the **Definition** tab in the lower panel of the page. 407 | 408 | ![definition page](../images/process-state-machine-definition.png) 409 | 410 | 7. Click the **Edit** button at the top of the page. 411 | 8. To replace the Profiler lambda in the **Process** state machine, we will simply copy the ARN for our new lambda and use it as the new value for the **States['Profiler']['Resource']** JSON element. 412 | 413 | ![replace profiler image](../images/step-replace-profiler-new.png) 414 | 415 | 9. Click **Save** 416 | 10. Save this page in a browser tab to use in the test step below. 417 | 418 | ## Test the end to end workflow 419 | 420 | 1. On your computer, create a file called `test.json` and copy the following JSON into the the file. If you named your VOD solution stack something other than **reinvent-vod**, you will need to replace the prefix with your stack name: 421 | 422 | ```json 423 | { 424 | "srcVideo": "van_life.mp4", 425 | "FrameCapture": true, 426 | "ruleMappings": [ 427 | { 428 | "ruleName": "Container_eq_MXF", 429 | "template": "reinvent-vod_Ott_720p_Avc_Aac_16x9" 430 | }, 431 | { 432 | "ruleName": "Container_eq_MP4", 433 | "template": "System-Ott_Hls_Ts_Avc_Aac" 434 | }, 435 | { 436 | "ruleName": "Container_eq_QuickTime", 437 | "template": "reinvent-vod_Ott_1080p_Avc_Aac_16x9" 438 | } 439 | ] 440 | } 441 | ``` 442 | 443 | 2. Open the S3 console and click on the link for the `reinvent-vod-source` bucket that was created by the Video on Demand on AWS workflow. 444 | 3. Click on the **Upload** button and use the dialog box to locate and select the `test.json` input metadata file you just created. 445 | 4. Click on the **Upload** button to start the upload. 446 | 5. Go back to the details page for the **Process** Step Function. 447 | 6. Find the most recent invocation of the function in the **Execution** panel and click on the link to see the execution details. 448 | 7. The **Execution status** should be **Succeeded** 449 | 8. Check the **Outputs** in the **Step details** panel to make sure there are ruleMappings and testResults in the output. 450 | 9. Finally, open the **Workflow** web page. Find the most recent workflow run and click on the row to show the workflow details. 451 | 10. You should see results of you rule execution for your test job. 452 | 453 | ![Success](../images/success.png) 454 | 455 | 11. You will notice that the result of the rule execution is listed in the details of the workflow instance. 456 | 457 | # Conclusion 458 | 459 | You have successfully completed the Rules Based Encoding workshop! At this point, you should have a feel for how to modify the internals of the Video on Demand on AWS solution as well as how the VOD solution might work as part of a larger application. Adding rules to an automated encoding workflow is a common and useful tool for video delivery. 460 | 461 | What you accomplished: 462 | 463 | 1. Changed the Video on Demand on AWS solution user interface from Video to Metadata inputs. 464 | 2. Deployed and configured a serverless web application for creating named expressions, called **_Mediainfo Rules_**, that can be evaluated against facts from Mediainfo metadata for a video. 465 | 3. Deployed and inspected a **_MediainfoRulesEngineProfiler_** lambda function that uses Mediainfo metadata for a specific video and evaluates a list of Mediainfo Rules to select a MediaConvert job template to use to process the video. 466 | 4. Changed the Video on Demand on AWS solution to use the MediainfoRulesEngineProfiler lambda in place of the existing logic to select a job template. 467 | 468 | Thank you for completing the workshop! 469 | 470 | **Next steps:** 471 | * To cleanup the resources created for this workshop see the [Cleanup](#Cleanup) section below. 472 | * Additional suggestions for working with this code sample and the workshop videos are available in the [More things to try](#More-things-to-try) section. 473 | 474 | # More things to try 475 | 476 | 1. Test the workshop workflow with different inputs 477 | 478 | To show that different templates are chosen for different inputs, replace the **srcVideo** value in the previous step with different input videos. Try: 479 | 480 | * `starlight_2160p59.m2ts` - no matching rule so the default, resolution based decision making will be used to select a template. 481 | * `silksintrees_MPEG2.mxf` - the first ruleMapping is selected. 482 | 483 | 2. Test the workflow with different rules 484 | 485 | Use the Rules web application to create new rules to replace the 3 rules in the sample workflow: 486 | 487 | * (Container Type equals M2TS OR Container Type equals MPEG-4) AND Video Codec equals HEVC) 488 | * Video Codec equals HEVC 489 | * Video Codec equals Prores 490 | 491 | Replace the Container rules in the workflow metadata with the new rules. 492 | 493 | Run the workflow with different **srcVideo** values: 494 | 495 | * `starlight_2160p59.m2ts` - no matching rule so the default, resolution based decision making will be used to select a template. 496 | * `silksintrees_MPEG2.mxf` - the first ruleMapping is selected. 497 | * `vanlife.mp4` 498 | 499 | 500 | 3. Add a new MediaConvert template and use it in a ruleMapping. You can find details on how to create custom templates in the [MediaConvert documentation](https://docs.aws.amazon.com/mediaconvert/latest/ug/working-with-job-templates.html). 501 | 502 | # Cleanup 503 | 504 | Delete the S3 bucket created by the `reinvent-rules` stack. 505 | 506 | 1. Find the value of the **WebsiteBucket** Output from the **reinvent-rules** stack and copy it. 507 | 2. Open the S3 console and search for the WebsiteBucket value. 508 | 3. Click on the bucket icon and delete the bucket. 509 | 510 | Delete the `reinvent-rules` CloudFormation stack. 511 | 512 | 1. Open the CloudFormation console. 513 | 2. Select the reinvent-rules stack radio button. 514 | 3. Select **Delete stack** from the **Actions** drop down. 515 | 516 | # Appendix: Code Walkthroughs 517 | 518 | ## profiler lambda 519 | 520 | The first thing to notice is that this is Node.js! The **mediainfoRuleEngineProfiler** lambda is written in Python, so we won't be copying code back and forth. The good news is that since we are using the function as a service model, we can have lambdas in any combination of programming languages we want. In this case, we want to take advantage of the open source business-rules python package, so we choose Python for our new lambda and reimplement the parts of the **Profiler** lambda that we need to match its interface to the VOD solution. 521 | 522 | * The first part of the lambda gets the latest workflow data from DynamoDB and copy it in to the _event_ output JSON object. 523 | * Retrieve the Mediainfo analysis from the workflow data and set the outputs that come directly from Mediainfo. 524 | * Decide what MediaConvert Job Template to use based on the resolution of the input video (Height x Width). **This is the key piece of code we need to replace with our dynamic rules execution from the mediainfoRulesEngine lambda**. 525 | 526 | ```javascript 527 | //Determine Encoding profile by matching the src Height to the nearest profile. 528 | const profiles = [2160, 1080,720]; 529 | let lastProfile; 530 | let encodeProfile; 531 | 532 | profiles.some(function(p) { 533 | let profile = Math.abs(event.srcHeight - p); 534 | if (profile > lastProfile) { 535 | return true; 536 | } 537 | encodeProfile = p; 538 | lastProfile = profile; 539 | }); 540 | event.EncodingProfile = encodeProfile; 541 | 542 | if (event.FrameCapture) { 543 | // Match Height x Width with the encoding profile. 544 | const ratios = { 545 | '2160':3840, 546 | '1080':1920, 547 | '720':1280 548 | }; 549 | event.frameCaptureHeight = encodeProfile; 550 | event.frameCaptureWidth = ratios[encodeProfile]; 551 | } 552 | 553 | // Update:: added support to pass in a custom encoding Template instead of using the 554 | // solution defaults 555 | 556 | if (!event.jobTemplate) { 557 | // Match the jobTemplate to the encoding Profile. 558 | const jobTemplates = { 559 | '2160': event.jobTemplate_2160p, 560 | '1080': event.jobTemplate_1080p, 561 | '720': event.jobTemplate_720p 562 | }; 563 | 564 | event.jobTemplate = jobTemplates[encodeProfile]; 565 | console.log('Encoding jobTemplates:: ', event.jobTemplate); 566 | } 567 | ``` 568 | 569 | * Return our results in the event object. 570 | 571 | ## MediainfoRulesEngineProfiler lambda 572 | 573 | We'll start with **profiler** lambda and replace the template decision making code with rule evaluation using the Python business-rules engine package. 574 | 575 | The resulting code is in **mediainfoRuleEngineProfiler**. Let's do a walk-through of the code and then run a test: 576 | 577 | 1. Open the AWS Lambda console and search for the pattern `reinvent-rules-api` to find the lambdas created by the ruleapi nested stack. The mediainfoRuleEngineProfiler lambda should be in the list. The name may be truncated if the function was created from a nested stack: 578 | 579 | ![find the lambda](../images/lambda-find-mediainfoRulesProfiler.png) 580 | 581 | 582 | 2. Click on the link to go to the detail page for the lambda. 583 | 3. Scroll down to the **Function code** panel to examine the code. 584 | 4. Find the function **mediainfoRuleEngineProfiler** in the file **app.py**. 585 | 5. Walk-through the code and note the steps listed below. 586 | 587 | ### Walk-through the mediainfoRuleEngineProfiler 588 | 589 | * **from profiler:** Input is {"guid":"guid-value"} 590 | * **from profiler:** Video on Demand on AWS workflow table is mapped from the Lambda Environment. 591 | * **NEW CODE** Rules API DynamoDB table is mapped from the Lambda Enviornment 592 | * **from profiler:** Get the latest workflow data for this guid from DynamoDB 593 | * **from profiler:** Get mediainfo analysis results that were collected earlier in the workflow 594 | * **from profiler:** Set outputs that are based on mediainfo 595 | * **NEW CODE** Decide on a MediaConvert encoding template by running the mediainfo business-rules and selecting the first mapped template whose rule is true. This implements IF-THEN-ELSE semantics. The ELSE case is to use the solution default templates. Test results for each rule executed are stored in the ruleMapping JSON elements of the event JSON object. 596 | 597 | ```python 598 | if ('ruleMappings' in event): 599 | video = Mediainfo(mediaInfo) 600 | 601 | for ruleMapping in event['ruleMappings']: 602 | 603 | ruleMapping['testCreateTime'] = datetime.utcnow().strftime( 604 | '%Y-%m-%d %H:%M.%S') 605 | 606 | logger.info("rule: {}".format(json.dumps( 607 | ruleMapping, indent=4, sort_keys=True))) 608 | 609 | response = rulesTable.get_item( 610 | Key={'name': ruleMapping['ruleName']}, ConsistentRead=True) 611 | logger.info("running test {}".format( 612 | json.dumps(response, cls=DecimalEncoder))) 613 | 614 | businessRules = response['Item']['rules'] 615 | 616 | ruleMapping['testResult'] = run_all(rule_list= [businessRules], 617 | defined_variables=MediainfoVariables(video), 618 | defined_actions=MediainfoActions(video), 619 | stop_on_first_trigger=True) 620 | 621 | logger.info("test result {}".format(json.dumps( 622 | ruleMapping['testResult'], cls=DecimalEncoder))) 623 | 624 | if ruleMapping['testResult'] == True: 625 | event['jobTemplate'] = ruleMapping['template'] 626 | break 627 | ``` 628 | 629 | * **from profiler:** Determine the resolution of the input to set the FrameCapture settings and calculate default profile. 630 | * **from profiler:** Set default template if no template was selected from the business-rules 631 | * **from profiler:** Output is the event JSON object 632 | 633 | ## Test the new lambda 634 | 635 | 1. To facilitate testing, the new lambda has a hard-coded test data you can use instead of getting the workflow datafrom DynamoDB Take a moment to examine the test data, MEDIAINFO_BUSINESSRULES_PROFILER_TEST, located just above the **mediainfoRuleEngineProfiler** function in app.py. 636 | 2. Make sure the line of code for running the test is set to `True` 637 | 638 | ```python 639 | testLambda = True 640 | if (testLambda): 641 | event = MEDIAINFO_BUSINESSRULES_PROFILER_TEST 642 | else: 643 | response = vodonawsTable.get_item(Key={'guid': guid}) 644 | event = response['Item'] 645 | ``` 646 | 647 | 3. Save the Lambda. 648 | 4. Select **Configure test event** from the lambda toolbar. 649 | 5. In the dialog box, check **Create new test event**. 650 | 6. Enter `dummyGuid` as the **Event name**. 651 | 7. Paste the JSON below into the the code box. This is just a fake guid to start the workflow. We will be using the test DynamoDB table data to test the lambda rather than looking up a workflow in DynamoDB. 652 | 653 | ```json 654 | {"guid":"notarealguid"} 655 | ``` 656 | 657 | 8. Click **Create** 658 | 9. Click on the **Test** button. 659 | 10. The Lambda **Execution result** should be successful and the output in the **Execution result** panel should look like this. Note that the output **ruleMappings** element now contain a **testCreateTime** for when the test was run and a **testResult** with the result of the test execution for this input: 660 | 661 | ```json 662 | { 663 | "guid": "67ea0602-49a9-48c8-bebd-d3bb4641665e", 664 | "destBucket": "vodaws45-destination-tc1fbugf08dz", 665 | "workflowStatus": "Ingest", 666 | "frameCapture": true, 667 | "srcMetadataFile": "944faeca-b2e5-47c4-adde-6c09ecbaec87.json", 668 | "jobTemplate_2160p": "vodaws45_Ott_2160p_Hevc_Aac_16x9", 669 | "jobTemplate_720p": "vodaws45_Ott_720p_Hevc_Aac_16x9", 670 | "workflowName": "vodaws45", 671 | "jobTemplate_1080p": "vodaws45_Ott_1080p_Hevc_Aac_16x9", 672 | "archiveSource": false, 673 | "ruleMappings": [ 674 | { 675 | "ruleName": "Container_eq_MXF", 676 | "template": "theTemplateForContainer_eq_MXF", 677 | "testCreateTime": "2018-10-05 18:11.48", 678 | "testResult": false 679 | }, 680 | { 681 | "ruleName": "Container_eq_MP4", 682 | "template": "theTemplateForContainer_eq_MP4", 683 | "testCreateTime": "2018-10-05 18:11.48", 684 | "testResult": true 685 | }, 686 | { 687 | "ruleName": "Container_eq_Prores", 688 | "template": "theTemplateForContainer_eq_Prores" 689 | } 690 | ], 691 | "srcMediainfo": "{\n \"filename\": \"futbol_720p60.mp4\",\n \"container\": {\n \"format\": \"MPEG-4\",\n \"mimeType\": \"video/mp4\",\n \"fileSize\": 229066618,\n \"duration\": 180224,\n \"totalBitrate\": 10168085\n },\n \"video\": [\n {\n \"codec\": \"AVC\",\n \"profile\": \"High@L3.2\",\n \"bitrate\": 10000000,\n \"duration\": 180200,\n \"frameCount\": 10812,\n \"width\": 1280,\n \"height\": 720,\n \"framerate\": 60,\n \"scanType\": \"Progressive\",\n \"aspectRatio\": \"16:9\",\n \"bitDepth\": 8,\n \"colorSpace\": \"YUV 4:2:0\"\n }\n ],\n \"audio\": [\n {\n \"codec\": \"AAC\",\n \"bitrate\": 93375,\n \"duration\": 180224,\n \"frameCount\": 8448,\n \"bitrateMode\": \"CBR\",\n \"language\": \"en\",\n \"channels\": 2,\n \"samplingRate\": 48000,\n \"samplePerFrame\": 1024\n }\n ]\n}", 692 | "startTime": "2018-09-28 18:14.9", 693 | "srcVideo": "futbol_720p60.mp4", 694 | "srcBucket": "vodaws45-source-1xc27xd0jfvkc", 695 | "FrameCapture": true, 696 | "srcHeight": 720, 697 | "srcWidth": 1280, 698 | "jobTemplate": "theTemplateForContainer_eq_MP4", 699 | "EncodingProfile": 720, 700 | "frameCaptureHeight": 720, 701 | "frameCaptureWidth": 1280 702 | } 703 | ``` 704 | 705 | 11. If the test passed, you are ready to try the dynamic rules in the Video on Demand on AWS workflow. Make sure the line of code for running the test in the mediainfoRuleEngineProfiler lambda is set to `False` 706 | 707 | ```python 708 | testLambda = False 709 | if (testLambda): 710 | event = MEDIAINFO_BUSINESSRULES_PROFILER_TEST 711 | else: 712 | response = vod-on-awsTable.get_item(Key={'guid': guid}) 713 | event = response['Item'] 714 | ``` 715 | 716 | 12. Save the Lambda 717 | 13. Scroll to the top of the page and copy the ARN for the lambda. 718 | 719 | 720 | # Next steps 721 | 722 | Feel free to enter a pull request if you want to add to this project! 723 | 724 | Some desired features: 725 | 726 | * **track selection** 727 | 728 | The current implementation is limited: 729 | * assumes only one video track in the input 730 | * only tests container and video analysis 731 | 732 | 733 | * **add other types of analysis/rules** such as ffmpeg for black detection, silence detection, ffprobe. 734 | 735 | * **Replace Python business-rules package with IOT Rules.** There's always more than one way to do something! I think we could use IOT Rules for this workflow by generating an SQL expression for each rule and creating a DynamoDB trigger rules with the following structure, name each track then write each rule as a column output of the select clause : 736 | 737 | ``` 738 | SELECT 739 | mediainfo.container AS container, mediainfo.video[0] AS video0, ..., 740 | mediainfo.audio[0] AS audio0, ..., 741 | mediainfo.text[0] AS text0, ..., 742 | (rule 1 expression) AS rule1result, ..., 743 | (rule N expression) AS ruleNresult 744 | FROM 745 | mediainfo 746 | ``` 747 | 748 | The rule would insert the result of each expression into a DynamoDB table. A DynamoDB trigger could be used to apply the rule mappings to select a template and continue the workflow. 749 | -------------------------------------------------------------------------------- /RuleBasedEncoding/assets/TheTemplateForM2TS.json: -------------------------------------------------------------------------------- 1 | { 2 | "OutputGroups": [ 3 | { 4 | "Name": "File Group", 5 | "Outputs": [ 6 | { 7 | "Preset": "System-Generic_Hd_Mp4_Hevc_Aac_16x9_1920x1080p_24Hz_4.5Mbps", 8 | "Extension": "mp4", 9 | "NameModifier": "_Generic_Hd_Mp4_Hevc_Aac_16x9_1920x1080p_24Hz_4.5Mbps" 10 | }, 11 | { 12 | "Preset": "System-Generic_Hd_Mp4_Hevc_Aac_16x9_1280x720p_24Hz_3.0Mbps", 13 | "Extension": "mp4", 14 | "NameModifier": "_Generic_Hd_Mp4_Hevc_Aac_16x9_1280x720p_24Hz_3.0Mbps" 15 | } 16 | ], 17 | "OutputGroupSettings": { 18 | "Type": "FILE_GROUP_SETTINGS", 19 | "FileGroupSettings": { 20 | "Destination": "MP4DESTINATION" 21 | } 22 | } 23 | }, 24 | { 25 | "Name": "HLS Group", 26 | "Outputs": [ 27 | { 28 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_0.4Mbps", 29 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_400Kbps" 30 | }, 31 | { 32 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_0.6Mbps", 33 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_600Kbps" 34 | }, 35 | { 36 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1.2Mbps", 37 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1200Kbps" 38 | }, 39 | { 40 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3.5Mbps", 41 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3500Kbps" 42 | }, 43 | { 44 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3.5Mbps", 45 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3500Kbps" 46 | }, 47 | { 48 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5.0Mbps", 49 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5000Kbps" 50 | }, 51 | { 52 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6.5Mbps", 53 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6500Kbps" 54 | }, 55 | { 56 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1920x1080p_30Hz_8.5Mbps", 57 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1920x1080p_30Hz_8500Kbps" 58 | } 59 | ], 60 | "OutputGroupSettings": { 61 | "Type": "HLS_GROUP_SETTINGS", 62 | "HlsGroupSettings": { 63 | "ManifestDurationFormat": "INTEGER", 64 | "SegmentLength": 5, 65 | "TimedMetadataId3Period": 10, 66 | "CaptionLanguageSetting": "OMIT", 67 | "Destination": "HLSDESTINATION", 68 | "TimedMetadataId3Frame": "PRIV", 69 | "CodecSpecification": "RFC_4281", 70 | "OutputSelection": "MANIFESTS_AND_SEGMENTS", 71 | "ProgramDateTimePeriod": 600, 72 | "MinSegmentLength": 0, 73 | "DirectoryStructure": "SINGLE_DIRECTORY", 74 | "ProgramDateTime": "EXCLUDE", 75 | "SegmentControl": "SEGMENTED_FILES", 76 | "ManifestCompression": "NONE", 77 | "ClientCache": "ENABLED", 78 | "StreamInfResolution": "INCLUDE" 79 | } 80 | } 81 | }, 82 | { 83 | "CustomName": "Frame Capture", 84 | "Name": "File Group", 85 | "Outputs": [ 86 | { 87 | "ContainerSettings": { 88 | "Container": "RAW" 89 | }, 90 | "VideoDescription": { 91 | "ScalingBehavior": "DEFAULT", 92 | "TimecodeInsertion": "DISABLED", 93 | "AntiAlias": "ENABLED", 94 | "Sharpness": 100, 95 | "CodecSettings": { 96 | "Codec": "FRAME_CAPTURE", 97 | "FrameCaptureSettings": { 98 | "FramerateNumerator": 1, 99 | "FramerateDenominator": 5, 100 | "MaxCaptures": 10000000, 101 | "Quality": 80 102 | } 103 | }, 104 | "AfdSignaling": "NONE", 105 | "DropFrameTimecode": "ENABLED", 106 | "RespondToAfd": "NONE", 107 | "ColorMetadata": "INSERT" 108 | }, 109 | "NameModifier": "_tumb" 110 | } 111 | ], 112 | "OutputGroupSettings": { 113 | "Type": "FILE_GROUP_SETTINGS", 114 | "FileGroupSettings": { 115 | "Destination": "THUMBNAILDESTINATION" 116 | } 117 | } 118 | } 119 | ], 120 | "AdAvailOffset": 0, 121 | "Inputs": [ 122 | { 123 | "AudioSelectors": { 124 | "Audio Selector 1": { 125 | "Tracks": [ 126 | 1 127 | ], 128 | "Offset": 0, 129 | "DefaultSelection": "NOT_DEFAULT", 130 | "SelectorType": "TRACK", 131 | "ProgramSelection": 1 132 | } 133 | }, 134 | "VideoSelector": { 135 | "ColorSpace": "FOLLOW" 136 | }, 137 | "FilterEnable": "AUTO", 138 | "PsiControl": "USE_PSI", 139 | "FilterStrength": 0, 140 | "DeblockFilter": "DISABLED", 141 | "DenoiseFilter": "DISABLED", 142 | "TimecodeSource": "EMBEDDED", 143 | "FileInput": "FILEINPUT" 144 | } 145 | ] 146 | } 147 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/assets/TheTemplateForMP4.json: -------------------------------------------------------------------------------- 1 | { 2 | "OutputGroups": [ 3 | { 4 | "Name": "File Group", 5 | "Outputs": [ 6 | { 7 | "Preset": "System-Generic_Hd_Mp4_Hevc_Aac_16x9_1920x1080p_24Hz_4.5Mbps", 8 | "Extension": "mp4", 9 | "NameModifier": "_Generic_Hd_Mp4_Hevc_Aac_16x9_1920x1080p_24Hz_4.5Mbps" 10 | }, 11 | { 12 | "Preset": "System-Generic_Hd_Mp4_Hevc_Aac_16x9_1280x720p_24Hz_3.0Mbps", 13 | "Extension": "mp4", 14 | "NameModifier": "_Generic_Hd_Mp4_Hevc_Aac_16x9_1280x720p_24Hz_3.0Mbps" 15 | } 16 | ], 17 | "OutputGroupSettings": { 18 | "Type": "FILE_GROUP_SETTINGS", 19 | "FileGroupSettings": { 20 | "Destination": "MP4DESTINATION" 21 | } 22 | } 23 | }, 24 | { 25 | "Name": "HLS Group", 26 | "Outputs": [ 27 | { 28 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_0.4Mbps", 29 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_400Kbps" 30 | }, 31 | { 32 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_0.6Mbps", 33 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_600Kbps" 34 | }, 35 | { 36 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1.2Mbps", 37 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1200Kbps" 38 | }, 39 | { 40 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3.5Mbps", 41 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3500Kbps" 42 | }, 43 | { 44 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3.5Mbps", 45 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3500Kbps" 46 | }, 47 | { 48 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5.0Mbps", 49 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5000Kbps" 50 | }, 51 | { 52 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6.5Mbps", 53 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6500Kbps" 54 | }, 55 | { 56 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1920x1080p_30Hz_8.5Mbps", 57 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1920x1080p_30Hz_8500Kbps" 58 | } 59 | ], 60 | "OutputGroupSettings": { 61 | "Type": "HLS_GROUP_SETTINGS", 62 | "HlsGroupSettings": { 63 | "ManifestDurationFormat": "INTEGER", 64 | "SegmentLength": 5, 65 | "TimedMetadataId3Period": 10, 66 | "CaptionLanguageSetting": "OMIT", 67 | "Destination": "HLSDESTINATION", 68 | "TimedMetadataId3Frame": "PRIV", 69 | "CodecSpecification": "RFC_4281", 70 | "OutputSelection": "MANIFESTS_AND_SEGMENTS", 71 | "ProgramDateTimePeriod": 600, 72 | "MinSegmentLength": 0, 73 | "DirectoryStructure": "SINGLE_DIRECTORY", 74 | "ProgramDateTime": "EXCLUDE", 75 | "SegmentControl": "SEGMENTED_FILES", 76 | "ManifestCompression": "NONE", 77 | "ClientCache": "ENABLED", 78 | "StreamInfResolution": "INCLUDE" 79 | } 80 | } 81 | }, 82 | { 83 | "CustomName": "Frame Capture", 84 | "Name": "File Group", 85 | "Outputs": [ 86 | { 87 | "ContainerSettings": { 88 | "Container": "RAW" 89 | }, 90 | "VideoDescription": { 91 | "ScalingBehavior": "DEFAULT", 92 | "TimecodeInsertion": "DISABLED", 93 | "AntiAlias": "ENABLED", 94 | "Sharpness": 100, 95 | "CodecSettings": { 96 | "Codec": "FRAME_CAPTURE", 97 | "FrameCaptureSettings": { 98 | "FramerateNumerator": 1, 99 | "FramerateDenominator": 5, 100 | "MaxCaptures": 10000000, 101 | "Quality": 80 102 | } 103 | }, 104 | "AfdSignaling": "NONE", 105 | "DropFrameTimecode": "ENABLED", 106 | "RespondToAfd": "NONE", 107 | "ColorMetadata": "INSERT" 108 | }, 109 | "NameModifier": "_tumb" 110 | } 111 | ], 112 | "OutputGroupSettings": { 113 | "Type": "FILE_GROUP_SETTINGS", 114 | "FileGroupSettings": { 115 | "Destination": "THUMBNAILDESTINATION" 116 | } 117 | } 118 | } 119 | ], 120 | "AdAvailOffset": 0, 121 | "Inputs": [ 122 | { 123 | "AudioSelectors": { 124 | "Audio Selector 1": { 125 | "Tracks": [ 126 | 1 127 | ], 128 | "Offset": 0, 129 | "DefaultSelection": "NOT_DEFAULT", 130 | "SelectorType": "TRACK", 131 | "ProgramSelection": 1 132 | } 133 | }, 134 | "VideoSelector": { 135 | "ColorSpace": "FOLLOW" 136 | }, 137 | "FilterEnable": "AUTO", 138 | "PsiControl": "USE_PSI", 139 | "FilterStrength": 0, 140 | "DeblockFilter": "DISABLED", 141 | "DenoiseFilter": "DISABLED", 142 | "TimecodeSource": "EMBEDDED", 143 | "FileInput": "FILEINPUT" 144 | } 145 | ] 146 | } 147 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/assets/rulesWorkflowM2TS.json: -------------------------------------------------------------------------------- 1 | { 2 | "Description": "OTT Profile - HLS, TS, AVC, AAC", 3 | "Category": "OTT-HLS", 4 | "Name": "RulesWorkflowM2TS", 5 | "Settings": { 6 | "OutputGroups": [ 7 | { 8 | "Name": "Apple HLS", 9 | "Outputs": [ 10 | { 11 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_0.4Mbps", 12 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_400Kbps" 13 | }, 14 | { 15 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_0.6Mbps", 16 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_600Kbps" 17 | }, 18 | { 19 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1.2Mbps", 20 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1200Kbps" 21 | }, 22 | { 23 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3.5Mbps", 24 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3500Kbps" 25 | }, 26 | { 27 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3.5Mbps", 28 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3500Kbps" 29 | }, 30 | { 31 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5.0Mbps", 32 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5000Kbps" 33 | }, 34 | { 35 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6.5Mbps", 36 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6500Kbps" 37 | } 38 | ], 39 | "OutputGroupSettings": { 40 | "Type": "HLS_GROUP_SETTINGS", 41 | "HlsGroupSettings": { 42 | "ManifestDurationFormat": "INTEGER", 43 | "SegmentLength": 3, 44 | "TimedMetadataId3Period": 10, 45 | "CaptionLanguageSetting": "OMIT", 46 | "TimedMetadataId3Frame": "PRIV", 47 | "CodecSpecification": "RFC_4281", 48 | "OutputSelection": "MANIFESTS_AND_SEGMENTS", 49 | "ProgramDateTimePeriod": 600, 50 | "MinSegmentLength": 0, 51 | "DirectoryStructure": "SINGLE_DIRECTORY", 52 | "ProgramDateTime": "EXCLUDE", 53 | "SegmentControl": "SEGMENTED_FILES", 54 | "ManifestCompression": "NONE", 55 | "ClientCache": "ENABLED", 56 | "StreamInfResolution": "INCLUDE" 57 | } 58 | } 59 | } 60 | ], 61 | "AdAvailOffset": 0 62 | } 63 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/assets/rulesWorkflowMXF.json: -------------------------------------------------------------------------------- 1 | { 2 | "Description": "OTT Profile - HLS, TS, AVC, AAC", 3 | "Category": "OTT-HLS", 4 | "Name": "RulesWorkflowMP4", 5 | "Settings": { 6 | "OutputGroups": [ 7 | { 8 | "Name": "Apple HLS", 9 | "Outputs": [ 10 | { 11 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_0.4Mbps", 12 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_400Kbps" 13 | }, 14 | { 15 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_0.6Mbps", 16 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_600Kbps" 17 | }, 18 | { 19 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1.2Mbps", 20 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1200Kbps" 21 | }, 22 | { 23 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3.5Mbps", 24 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3500Kbps" 25 | }, 26 | { 27 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3.5Mbps", 28 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3500Kbps" 29 | }, 30 | { 31 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5.0Mbps", 32 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5000Kbps" 33 | }, 34 | { 35 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6.5Mbps", 36 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6500Kbps" 37 | } 38 | ], 39 | "OutputGroupSettings": { 40 | "Type": "HLS_GROUP_SETTINGS", 41 | "HlsGroupSettings": { 42 | "ManifestDurationFormat": "INTEGER", 43 | "SegmentLength": 3, 44 | "TimedMetadataId3Period": 10, 45 | "CaptionLanguageSetting": "OMIT", 46 | "TimedMetadataId3Frame": "PRIV", 47 | "CodecSpecification": "RFC_4281", 48 | "OutputSelection": "MANIFESTS_AND_SEGMENTS", 49 | "ProgramDateTimePeriod": 600, 50 | "MinSegmentLength": 0, 51 | "DirectoryStructure": "SINGLE_DIRECTORY", 52 | "ProgramDateTime": "EXCLUDE", 53 | "SegmentControl": "SEGMENTED_FILES", 54 | "ManifestCompression": "NONE", 55 | "ClientCache": "ENABLED", 56 | "StreamInfResolution": "INCLUDE" 57 | } 58 | } 59 | } 60 | ], 61 | "AdAvailOffset": 0 62 | } 63 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/assets/rulesWorkflowQuickTime.json: -------------------------------------------------------------------------------- 1 | { 2 | "Description": "OTT Profile - HLS, TS, AVC, AAC", 3 | "Category": "OTT-HLS", 4 | "Name": "RulesWorkflowQuickTime", 5 | "Settings": { 6 | "OutputGroups": [ 7 | { 8 | "Name": "Apple HLS", 9 | "Outputs": [ 10 | { 11 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_0.4Mbps", 12 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_480x270p_15Hz_400Kbps" 13 | }, 14 | { 15 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_0.6Mbps", 16 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_600Kbps" 17 | }, 18 | { 19 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1.2Mbps", 20 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_640x360p_30Hz_1200Kbps" 21 | }, 22 | { 23 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3.5Mbps", 24 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_960x540p_30Hz_3500Kbps" 25 | }, 26 | { 27 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3.5Mbps", 28 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_3500Kbps" 29 | }, 30 | { 31 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5.0Mbps", 32 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_5000Kbps" 33 | }, 34 | { 35 | "Preset": "System-Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6.5Mbps", 36 | "NameModifier": "_Ott_Hls_Ts_Avc_Aac_16x9_1280x720p_30Hz_6500Kbps" 37 | } 38 | ], 39 | "OutputGroupSettings": { 40 | "Type": "HLS_GROUP_SETTINGS", 41 | "HlsGroupSettings": { 42 | "ManifestDurationFormat": "INTEGER", 43 | "SegmentLength": 3, 44 | "TimedMetadataId3Period": 10, 45 | "CaptionLanguageSetting": "OMIT", 46 | "TimedMetadataId3Frame": "PRIV", 47 | "CodecSpecification": "RFC_4281", 48 | "OutputSelection": "MANIFESTS_AND_SEGMENTS", 49 | "ProgramDateTimePeriod": 600, 50 | "MinSegmentLength": 0, 51 | "DirectoryStructure": "SINGLE_DIRECTORY", 52 | "ProgramDateTime": "EXCLUDE", 53 | "SegmentControl": "SEGMENTED_FILES", 54 | "ManifestCompression": "NONE", 55 | "ClientCache": "ENABLED", 56 | "StreamInfResolution": "INCLUDE" 57 | } 58 | } 59 | } 60 | ], 61 | "AdAvailOffset": 0 62 | } 63 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/deploy/build-s3-dist.sh: -------------------------------------------------------------------------------- 1 | !/bin/bash 2 | regions='REGION' 3 | bucket='BUCKET' # no / at the end 4 | prefix='PATH_WITHIN_BUCKET' #no / at the beginning or end 5 | aws_profile='PROFILE' 6 | 7 | #[ -e dist ] && rm -r dist 8 | #mkdir -p dist 9 | 10 | echo "create cloudformation for ruleapi" 11 | pwd 12 | 13 | pushd ../ruleapi 14 | chalice package ../deploy/dist 15 | popd 16 | 17 | # Add non-chalice managed resources to inputs 18 | ./chalice-fix-inputs.py 19 | 20 | echo "cloudformation for rule core resources" 21 | cp ../ruleweb.yaml ./dist 22 | 23 | echo "cloudformation for key resources" 24 | cp ../keys.yaml ./dist 25 | 26 | echo "cloudformation for workshop" 27 | cp ../workshop.yaml ./dist 28 | 29 | echo "website pages" 30 | cp -r ../website ./dist 31 | 32 | ls -l dist 33 | 34 | pushd dist 35 | for region in $regions; do 36 | echo 37 | echo "CREATING PACKAGE FOR $region" 38 | echo 39 | pwd 40 | bucket='rodeolabz-'$region 41 | echo $bucket 42 | # Create a normal cloudformation template from the Chalice generated SAM template 43 | aws cloudformation package --template-file sam.json --s3-bucket $bucket --s3-prefix $prefix --output-template-file "ruleapi.yaml" --profile default-tm 44 | ../sam-translate.py 45 | aws s3 sync . s3://$bucket/$prefix --region $region --acl public-read --profile $aws_profile 46 | 47 | done 48 | popd 49 | 50 | #aws s3 sync . s3://$bucket/$prefix --region $region --acl public-read --delete 51 | -------------------------------------------------------------------------------- /RuleBasedEncoding/deploy/chalice-fix-inputs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import json 3 | from pprint import pprint 4 | 5 | def fix_chalice_sam_template(): 6 | 7 | sam_json = json.load(open('./dist/sam.json')) 8 | # pprint(sam_json) 9 | 10 | #stack_inputs_json = json.load(open('./chalice-stack-inputs.json')) 11 | #pprint(stack_inputs_json) 12 | 13 | sam_json["Parameters"] = { 14 | "RulesTable": { 15 | "Type": "String", 16 | "Description": "RulesTable output from the ruleweb stack" 17 | }, 18 | "DynamoDbTable": { 19 | "Type": "String", 20 | "Description": "DynamoDbTable output from the Video On Demand on AWS stack" 21 | }, 22 | "Source": { 23 | "Type": "String", 24 | "Description": "Source output from the Video On Demand on AWS stack" 25 | } 26 | } 27 | 28 | sam_json["Resources"]["APIHandler"]["Properties"]["Environment"] = { 29 | "Variables": { 30 | "RULEWEB_RULETABLE": { 31 | "Ref":"RulesTable" 32 | }, 33 | "VODONAWS_DYNAMODBTABLE": { 34 | "Ref":"DynamoDbTable" 35 | }, 36 | "VODONAWS_SOURCE": { 37 | "Ref":"Source" 38 | } 39 | } 40 | } 41 | 42 | 43 | #MediainfoRuleEngine_environment_json = json.load(open('./chalice-environment-MediainfoRuleEngine.json')) 44 | #pprint(MediainfoRuleEngine_environment_json) 45 | sam_json["Resources"]["MediainfoRuleEngine"]["Properties"]["Environment"] = { 46 | "Variables": { 47 | "RULEWEB_RULETABLE": { 48 | "Ref":"RulesTable" 49 | } 50 | } 51 | } 52 | 53 | #MediainfoRuleEngineProfiler_environment_json = json.load(open('./chalice-environment-MediainfoRuleEngineProfiler.json')) 54 | #pprint(MediainfoRuleEngineProfiler_environment_json) 55 | sam_json["Resources"]["MediainfoRuleEngineProfiler"]["Properties"]["Environment"] = { 56 | "Variables": { 57 | "RULEWEB_RULETABLE": { 58 | "Ref":"RulesTable" 59 | }, 60 | "VODONAWS_DYNAMODBTABLE": { 61 | "Ref":"DynamoDbTable" 62 | } 63 | } 64 | } 65 | 66 | with open('./dist/sam.json', 'w') as outfile: 67 | json.dump(sam_json, outfile) 68 | 69 | pprint(json.dumps(sam_json)) 70 | 71 | def main(): 72 | fix_chalice_sam_template() 73 | 74 | 75 | if __name__ == '__main__': 76 | main() -------------------------------------------------------------------------------- /RuleBasedEncoding/deploy/sam-translate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Convert SAM templates to CloudFormation templates. 4 | Known limitations: cannot transform CodeUri pointing at local directory. 5 | Usage: 6 | sam-translate.py --input-file=sam-template.yaml [--output-file=] 7 | Options: 8 | -i FILE, --input-file=IN_FILE Location of SAM template to transform. 9 | -o FILE, --output-file=OUT_FILE Location to store resulting CloudFormation template [default: cfn-template.json]. 10 | """ 11 | import json 12 | import os 13 | 14 | import boto3 15 | from docopt import docopt 16 | 17 | from samtranslator.public.translator import ManagedPolicyLoader 18 | from samtranslator.translator.transform import transform 19 | from samtranslator.yaml_helper import yaml_parse 20 | from samtranslator.model.exceptions import InvalidDocumentException 21 | 22 | 23 | iam_client = boto3.client('iam') 24 | cwd = os.getcwd() 25 | 26 | 27 | def main(): 28 | print(cwd) 29 | input_file_path = cwd+'/ruleapi.yaml' 30 | output_file_path = cwd+'/ruleapi_cfn.yaml' 31 | 32 | print(input_file_path) 33 | 34 | with open(input_file_path, 'r') as f: 35 | sam_template = yaml_parse(f) 36 | 37 | try: 38 | cloud_formation_template = transform( 39 | sam_template, {}, ManagedPolicyLoader(iam_client)) 40 | cloud_formation_template_prettified = json.dumps( 41 | cloud_formation_template, indent=2) 42 | 43 | with open(output_file_path, 'w') as f: 44 | f.write(cloud_formation_template_prettified) 45 | 46 | print('Wrote transformed CloudFormation template to: ' + output_file_path) 47 | except InvalidDocumentException as e: 48 | errorMessage = reduce(lambda message, error: message + ' ' + error.message, e.causes, e.message) 49 | print(errorMessage) 50 | errors = map(lambda cause: cause.message, e.causes) 51 | print(errors) 52 | 53 | 54 | if __name__ == '__main__': 55 | main() 56 | -------------------------------------------------------------------------------- /RuleBasedEncoding/keys.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | 4 | Description: 5 | Rules Based Encoding Workshop - API Key resources for rules website 6 | 7 | Parameters: 8 | 9 | ApiId: 10 | Description: API ID to authenticate. 11 | Type: String 12 | 13 | Resources: 14 | 15 | APIUsagePlan: 16 | Type: AWS::ApiGateway::UsagePlan 17 | Properties: 18 | ApiStages: 19 | - ApiId: !Ref 'ApiId' 20 | Stage: 'api' 21 | Description: Usage Plan for Rules API 22 | UsagePlanName: !Sub "${AWS::StackName}-UsagePlan" 23 | 24 | ApiKey: 25 | Type: AWS::ApiGateway::ApiKey 26 | Properties: 27 | Name: !Sub "${AWS::StackName}-TestUserApiKey" 28 | Description: "CloudFormation API Key V1" 29 | Enabled: "true" 30 | StageKeys: 31 | - RestApiId: 32 | Ref: 'ApiId' 33 | StageName: "api" 34 | 35 | UsagePlanKey: 36 | Type: AWS::ApiGateway::UsagePlanKey 37 | Properties : 38 | KeyId: !Ref 'ApiKey' 39 | KeyType: API_KEY 40 | UsagePlanId: !Ref APIUsagePlan 41 | 42 | Outputs: 43 | ApiKey: 44 | Value: !Ref ApiKey 45 | -------------------------------------------------------------------------------- /RuleBasedEncoding/ruleapi/.chalice/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "app_name": "ruleapi", 4 | "environment_variables": { 5 | "RULEWEB_RULETABLE": "ruleweb-RulesTable", 6 | "VODONAWS_DYNAMODBTABLE": "RE-vod", 7 | "VODONAWS_SOURCE": "re-vod-source-i9nbhrubiuqn" 8 | }, 9 | "stages": { 10 | "dev": { 11 | "api_gateway_stage": "api", 12 | "autogen_policy": false, 13 | "iam_policy_file": "dev-app-policy.json" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RuleBasedEncoding/ruleapi/.chalice/dev-app-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": [ 6 | "logs:CreateLogGroup", 7 | "logs:CreateLogStream", 8 | "logs:PutLogEvents" 9 | ], 10 | "Resource": "*", 11 | "Effect": "Allow", 12 | "Sid": "Logging" 13 | }, 14 | { 15 | "Action": [ 16 | "dynamodb:*" 17 | ], 18 | "Resource": [ 19 | "*" 20 | ], 21 | "Effect": "Allow", 22 | "Sid": "DynamoTables" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/ruleapi/.gitignore: -------------------------------------------------------------------------------- 1 | .chalice/deployments/ 2 | .chalice/deployed/ 3 | .chalice/venv/ 4 | __pychache__/ 5 | -------------------------------------------------------------------------------- /RuleBasedEncoding/ruleapi/app.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from chalice import Chalice 3 | from chalice import BadRequestError, ChaliceViewError 4 | import boto3 5 | from boto3.dynamodb.conditions import Key 6 | from boto3.dynamodb.conditions import Attr 7 | from boto3 import resource 8 | from botocore.client import ClientError 9 | import uuid 10 | import logging 11 | import os 12 | from datetime import date 13 | from datetime import time 14 | from datetime import datetime 15 | import json 16 | import time 17 | import decimal 18 | import traceback 19 | from urllib.parse import urlparse 20 | import business_rules 21 | from business_rules import export_rule_data 22 | from business_rules import run_all 23 | from business_rules.actions import BaseActions 24 | from business_rules.actions import rule_action 25 | from business_rules.variables import BaseVariables 26 | from business_rules.variables import numeric_rule_variable, \ 27 | string_rule_variable, select_rule_variable 28 | from business_rules.fields import FIELD_NUMERIC, FIELD_TEXT 29 | 30 | # Setup logging 31 | logger = logging.getLogger('boto3') 32 | logger.setLevel(logging.INFO) 33 | 34 | # Helper class to convert a DynamoDB item to JSON. 35 | 36 | 37 | class DecimalEncoder(json.JSONEncoder): 38 | def default(self, o): 39 | if isinstance(o, decimal.Decimal): 40 | return str(o) 41 | return super(DecimalEncoder, self).default(o) 42 | 43 | 44 | # Setup boto3 resources 45 | dynamodb_resource = resource('dynamodb') 46 | S3 = boto3.client('s3') 47 | 48 | # Defines names and types of variables that can be used in business-rules expressions and how their values 49 | # can be found in a Mediainfo type object. 50 | 51 | 52 | class MediainfoVariables(BaseVariables): 53 | def __init__(self, video): 54 | self.video = video 55 | logger.info(video.mediainfo) 56 | # FIXME - there can be more than one of each track type. Need to add ability to 57 | # select a track to the UI. 58 | self.video0 = video.mediainfo['video'][0] 59 | self.container = video.mediainfo['container'] 60 | 61 | # container variables 62 | @select_rule_variable(options=["MPEG-4", "QuickTime", "Matroska", "AVI", "MPEG-PS", "MPEG-TS", "MXF", "GXF", "LXF", "WMV", "FLV", "Real"]) 63 | def container_format(self): 64 | print("container_format({}".format(self.container['format'])) 65 | return [self.container['format']] 66 | 67 | @numeric_rule_variable 68 | def container_file_size(self): 69 | return int(self.continer['fileSize']) 70 | 71 | @numeric_rule_variable 72 | def container_duration(self): 73 | return float(self.container['duration']) 74 | 75 | @string_rule_variable 76 | def container_mime_type(self): 77 | return self.container['mimeType'] 78 | 79 | @numeric_rule_variable 80 | def container_total_bitrate(self): 81 | return float(self.container['totalBitrate']) 82 | 83 | # video variables - assume a single video track in container 84 | @string_rule_variable 85 | def video_codec(self): 86 | return self.video0['codec'] 87 | 88 | @numeric_rule_variable 89 | def video_frame_count(self): 90 | return int(self.video0['frameCount']) 91 | 92 | @string_rule_variable 93 | def video_scan_type(self): 94 | return self.video0['scanType'] 95 | 96 | @string_rule_variable 97 | def video_profile(self): 98 | return self.video0['profile'] 99 | 100 | @numeric_rule_variable 101 | def video_height(self): 102 | return int(self.video0['height']) 103 | 104 | @numeric_rule_variable 105 | def video_frame_rate(self): 106 | return int(self.video0['framerate']) 107 | 108 | @string_rule_variable 109 | def video_aspect_ratio(self): 110 | return float(self.video0['aspectRatio']) 111 | 112 | @numeric_rule_variable 113 | def video_bit_depth(self): 114 | return int(self.video0['bitDepth']) 115 | 116 | @numeric_rule_variable 117 | def video_duration(self): 118 | return float(self.video0['duration']) 119 | 120 | @numeric_rule_variable 121 | def video_bit_rate(self): 122 | return int(self.video0['bitrate']) 123 | 124 | @string_rule_variable 125 | def video_color_space(self): 126 | return self.video0['colorSpace'] 127 | 128 | @numeric_rule_variable 129 | def video_width(self): 130 | return int(self.video0['width']) 131 | 132 | 133 | # Defines names and types of actions that can be triggers from the business-rules pacakage. These 134 | # are mocks for testing only. The rulesapi simply returns the test result and lets the client make 135 | # decisions about the workflow actions. 136 | class MediainfoActions(BaseActions): 137 | nothing = "nothing" 138 | 139 | def __init__(self, video): 140 | self.video = video 141 | 142 | @rule_action() 143 | def do_test(self): 144 | return "test result json" 145 | 146 | # Defines video facts object. For now, facts are mediainfo results only 147 | 148 | 149 | class Mediainfo: 150 | def __init__(self, mediainfo): 151 | self.mediainfo = mediainfo 152 | 153 | 154 | # Setup the Chalice app. See https://github.com/aws/chalice for more info 155 | app = Chalice(app_name='ruleapi') 156 | 157 | 158 | ''' 159 | Test input to be used in place of Dynamodb row for unit testing 160 | ''' 161 | MEDIAINFO_BUSINESSRULES_PROFILER_TEST = { 162 | "guid": "67ea0602-49a9-48c8-bebd-d3bb4641665e", 163 | "destBucket": "vodaws45-destination-tc1fbugf08dz", 164 | "workflowStatus": "Ingest", 165 | "frameCapture": True, 166 | "srcMetadataFile": "944faeca-b2e5-47c4-adde-6c09ecbaec87.json", 167 | "jobTemplate_2160p": "vodaws45_Ott_2160p_Hevc_Aac_16x9", 168 | "jobTemplate_720p": "vodaws45_Ott_720p_Hevc_Aac_16x9", 169 | "workflowName": "vodaws45", 170 | "jobTemplate_1080p": "vodaws45_Ott_1080p_Hevc_Aac_16x9", 171 | "archiveSource": False, 172 | "ruleMappings": [ 173 | {"ruleName": "Container_eq_MXF", "template": "theTemplateForContainer_eq_MXF"}, 174 | {"ruleName": "Container_eq_MP4", "template": "theTemplateForContainer_eq_MP4"}, 175 | {"ruleName": "Container_eq_Prores", 176 | "template": "theTemplateForContainer_eq_Prores"} 177 | ], 178 | "srcMediainfo": "{\n \"filename\": \"futbol_720p60.mp4\",\n \"container\": {\n \"format\": \"MPEG-4\",\n \"mimeType\": \"video/mp4\",\n \"fileSize\": 229066618,\n \"duration\": 180224,\n \"totalBitrate\": 10168085\n },\n \"video\": [\n {\n \"codec\": \"AVC\",\n \"profile\": \"High@L3.2\",\n \"bitrate\": 10000000,\n \"duration\": 180200,\n \"frameCount\": 10812,\n \"width\": 1280,\n \"height\": 720,\n \"framerate\": 60,\n \"scanType\": \"Progressive\",\n \"aspectRatio\": \"16:9\",\n \"bitDepth\": 8,\n \"colorSpace\": \"YUV 4:2:0\"\n }\n ],\n \"audio\": [\n {\n \"codec\": \"AAC\",\n \"bitrate\": 93375,\n \"duration\": 180224,\n \"frameCount\": 8448,\n \"bitrateMode\": \"CBR\",\n \"language\": \"en\",\n \"channels\": 2,\n \"samplingRate\": 48000,\n \"samplePerFrame\": 1024\n }\n ]\n}", 179 | "startTime": "2018-09-28 18:14.9", 180 | "srcVideo": "futbol_720p60.mp4", 181 | "srcBucket": "vodaws45-source-1xc27xd0jfvkc", 182 | "FrameCapture": True 183 | } 184 | 185 | ''' 186 | * @description set an endcoding profile based on the sorurce mediaInfo metadata). 187 | * define the height/width for framecapture. define the encoding 188 | * job template to be used based on the profile (passing in the the event.jobTemplate 189 | * will overide the workflow defaults) 190 | ''' 191 | 192 | 193 | @app.lambda_function(name="mediainfoRuleEngineProfiler") 194 | def mediainfoRuleEngineProfiler(event, context): 195 | 196 | vodonawsTable = dynamodb_resource.Table( 197 | os.environ['VODONAWS_DYNAMODBTABLE']) 198 | rulesTable = dynamodb_resource.Table(os.environ['RULEWEB_RULETABLE']) 199 | 200 | guid = event['guid'] 201 | 202 | try: 203 | 204 | # Get the latest workflow data from Dynamodb 205 | # Set testLambda to true to test with hardcoded input 206 | 207 | testLambda = False 208 | if (testLambda): 209 | logger.info("USING TEST EVENT") 210 | event = MEDIAINFO_BUSINESSRULES_PROFILER_TEST 211 | else: 212 | logger.info("Get event from Dynamodb") 213 | response = vodonawsTable.get_item(Key={'guid': guid}) 214 | event = response['Item'] 215 | 216 | logger.info("GetItem succeeded:") 217 | logger.info(json.dumps(event, indent=4, cls=DecimalEncoder)) 218 | 219 | # Get mediainfo analysis results that were collected earlier in the workflow (Ingest Step Function) 220 | mediaInfo = json.loads(event['srcMediainfo']) 221 | 222 | # Set outputs that are based on mediainfo 223 | event['srcHeight'] = mediaInfo['video'][0]['height'] 224 | event['srcWidth'] = mediaInfo['video'][0]['width'] 225 | 226 | # Decide on a MediaConvert encoding template by running the mediainfo business-rules and selecting 227 | # the first mapped template whose rule is true. This implements IF-THEN-ELSE semantics 228 | if ('ruleMappings' in event): 229 | video = Mediainfo(mediaInfo) 230 | 231 | for ruleMapping in event['ruleMappings']: 232 | 233 | ruleMapping['testCreateTime'] = datetime.utcnow().strftime( 234 | '%Y-%m-%d %H:%M.%S') 235 | 236 | logger.info("rule: {}".format(json.dumps( 237 | ruleMapping, indent=4, sort_keys=True))) 238 | 239 | response = rulesTable.get_item( 240 | Key={'name': ruleMapping['ruleName']}, ConsistentRead=True) 241 | logger.info("running test {}".format( 242 | json.dumps(response, cls=DecimalEncoder))) 243 | 244 | businessRules = response['Item']['rules'] 245 | 246 | ruleMapping['testResult'] = run_all(rule_list=[businessRules], 247 | defined_variables=MediainfoVariables( 248 | video), 249 | defined_actions=MediainfoActions( 250 | video), 251 | stop_on_first_trigger=True 252 | ) 253 | 254 | logger.info("test result {}".format(json.dumps( 255 | ruleMapping['testResult'], cls=DecimalEncoder))) 256 | 257 | if ruleMapping['testResult'] == True: 258 | event['jobTemplate'] = ruleMapping['template'] 259 | break 260 | 261 | profiles = [2160, 1080, 720] 262 | encodeProfile = 2160 263 | 264 | for p in profiles: 265 | if event['srcHeight'] > p: 266 | break 267 | encodeProfile = p 268 | 269 | event['EncodingProfile'] = encodeProfile 270 | 271 | if (event['FrameCapture']): 272 | # Match Height x Width with the encoding profile. 273 | ratios = { 274 | '2160': 3840, 275 | '1080': 1920, 276 | '720': 1280 277 | } 278 | event['frameCaptureHeight'] = encodeProfile 279 | event['frameCaptureWidth'] = ratios[str(encodeProfile)] 280 | 281 | # If no rule evaluated to True or there were no rules, use the existing default 282 | # templates 283 | 284 | if 'jobTemplate' not in event: 285 | # Match the jobTemplate to the encoding Profile. 286 | jobTemplates = { 287 | '2160': event['jobTemplate_2160p'], 288 | '1080': event['jobTemplate_1080p'], 289 | '720': event['jobTemplate_720p'] 290 | } 291 | 292 | event['jobTemplate'] = jobTemplates[str(encodeProfile)] 293 | 294 | logger.info('Encoding jobTemplates: %s ', event['jobTemplate']) 295 | 296 | except Exception as e: 297 | logger.info("Exception {}".format(e)) 298 | raise ChaliceViewError("Exception '%s'" % e) 299 | 300 | return event 301 | 302 | 303 | ''' 304 | * @description set an endcoding profile based on the sorurce mediaInfo metadata). 305 | * define the height/width for framecapture. define the encoding 306 | * job template to be used based on the profile (passing in the the event.jobTemplate 307 | * will overide the workflow defaults) 308 | ''' 309 | 310 | 311 | @app.lambda_function(name="mediainfoRuleEngine") 312 | def mediainfoRuleEngine(event, context): 313 | 314 | logger.info(json.dumps(event)) 315 | 316 | try: 317 | rulesTable = dynamodb_resource.Table(os.environ['RULEWEB_RULETABLE']) 318 | mediaInfo = event['mediainfo'] 319 | ruleMappings = event['ruleMappings'] 320 | 321 | video = Mediainfo(mediaInfo) 322 | 323 | # Determine Encoding template by running the encoding rules and selecting 324 | # the first mapped template whose rules is true. 325 | 326 | for ruleMapping in event['ruleMappings']: 327 | 328 | ruleMapping['testCreateTime'] = datetime.utcnow().strftime( 329 | '%Y-%m-%d %H:%M.%S') 330 | 331 | logger.info("rule: {}".format(json.dumps( 332 | ruleMapping, indent=4, sort_keys=True))) 333 | 334 | # retrieve the rule expression from the dyanamodb 335 | response = rulesTable.get_item( 336 | Key={'name': ruleMapping['ruleName']}, ConsistentRead=True) 337 | businessRules = response['Item']['rules'] 338 | 339 | logger.info("running test {}".format( 340 | json.dumps(businessRules, cls=DecimalEncoder))) 341 | 342 | ruleMapping['testResult'] = run_all(rule_list=[businessRules], 343 | defined_variables=MediainfoVariables( 344 | video), 345 | defined_actions=MediainfoActions( 346 | video), 347 | stop_on_first_trigger=True 348 | ) 349 | 350 | logger.info("test result {}".format(json.dumps( 351 | ruleMapping['testResult'], cls=DecimalEncoder))) 352 | 353 | # Stop on the first rule that evaluates to True as we have found our mapping 354 | if ruleMapping['testResult'] == True: 355 | event['selectedRuleString'] = ruleMapping['ruleString'] 356 | break 357 | 358 | except Exception as e: 359 | logger.info("Exception {}".format(e)) 360 | raise ChaliceViewError("Exception '%s'" % e) 361 | 362 | logger.info('Event: %s', json.dumps(event)) 363 | 364 | return event 365 | 366 | 367 | @app.route('/', cors=True) 368 | def index(): 369 | return {'hello': 'world'} 370 | 371 | 372 | @app.route('/ping', cors=True) 373 | def ping(): 374 | return {'ping': 'pong'} 375 | 376 | # Get a list of varaiable definitions that can be used to create rule expressions 377 | 378 | 379 | @app.route('/rules/variables', methods=['GET'], cors=True) 380 | def get_variables(): 381 | 382 | logger.info('ENTER get_variables()') 383 | 384 | try: 385 | 386 | rule_data = export_rule_data(MediainfoVariables, MediainfoActions) 387 | 388 | logger.info("{}".format(json.dumps( 389 | rule_data, indent=4, sort_keys=True))) 390 | 391 | except Exception as e: 392 | logger.info("Error getting rule data {}".format(e)) 393 | raise ChaliceViewError("Dynamodb returned error message '%s'" % e) 394 | 395 | logger.info('LEAVE get_variables: {}'.format( 396 | json.dumps(rule_data, indent=4, sort_keys=True))) 397 | return rule_data 398 | 399 | 400 | # Get a list of all rule defintions created through the API 401 | @app.route('/rules', methods=['GET'], api_key_required=True, cors=True) 402 | def get_rules(): 403 | 404 | logger.info('ENTER get_rules()') 405 | 406 | try: 407 | table = dynamodb_resource.Table(os.environ['RULEWEB_RULETABLE']) 408 | response = table.scan() 409 | 410 | except Exception as e: 411 | logger.info("Exception {}".format(e)) 412 | raise ChaliceViewError("Exception '%s'" % e) 413 | 414 | logger.info('LEAVE get_rules: {}'.format( 415 | json.dumps(response['Items'], cls=DecimalEncoder))) 416 | return response['Items'] 417 | 418 | # Get a rule defintion created through the API 419 | 420 | 421 | @app.route('/rules/{rule_name}', methods=['GET'], api_key_required=True, cors=True) 422 | def get_rule(rule_name): 423 | 424 | logger.info("ENTER get_rule(rule_name = {})".format(rule_name)) 425 | table = dynamodb_resource.Table(os.environ['RULEWEB_RULETABLE']) 426 | 427 | try: 428 | response = table.get_item(Key={'name': rule_name}, ConsistentRead=True) 429 | logger.info("{}".format(json.dumps(response, cls=DecimalEncoder))) 430 | except Exception as e: 431 | logger.info("Exception {}".format(e)) 432 | raise ChaliceViewError("Exception '%s'" % e) 433 | 434 | logger.info('LEAVE get_rule: {}'.format( 435 | json.dumps(response['Item'], cls=DecimalEncoder))) 436 | return response['Item'] 437 | 438 | # Create a rule defintion 439 | 440 | 441 | @app.route('/rules/{rule_name}', methods=['POST'], api_key_required=True, cors=True) 442 | def create_or_update_rule(rule_name): 443 | 444 | logger.info("ENTER create_rule: Create rule set name {}".format(rule_name)) 445 | 446 | table = dynamodb_resource.Table(os.environ['RULEWEB_RULETABLE']) 447 | 448 | try: 449 | request_body = app.current_request.json_body 450 | logger.info("request body {}".format( 451 | json.dumps(request_body, indent=4, sort_keys=True))) 452 | ruleSet = {} 453 | ruleSet['name'] = rule_name 454 | ruleSet['rules'] = json.loads(request_body['body']) 455 | ruleSet['rules']['actions'] = [ 456 | { 457 | "name": "do_test", 458 | }, 459 | ] 460 | ruleSet['createTime'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M.%S') 461 | 462 | # convert floats in input to decimal for Dynamodb 463 | s = json.dumps(ruleSet, cls=DecimalEncoder) 464 | ruleSet = json.loads(s, parse_float=decimal.Decimal) 465 | 466 | logger.info("create_rule: Create rule set name {} body {}".format( 467 | rule_name, json.dumps(request_body, indent=4, sort_keys=True))) 468 | 469 | response = table.put_item(Item=ruleSet) 470 | logger.info("put_item response {}".format( 471 | json.dumps(response, cls=DecimalEncoder))) 472 | 473 | except Exception as e: 474 | logger.info("Exception {}".format(e)) 475 | raise ChaliceViewError("Exception '%s'" % e) 476 | 477 | logger.info("LEAVE create_rule") 478 | return response 479 | 480 | 481 | @app.route('/vodonaws', methods=['GET'], api_key_required=True, cors=True) 482 | def vod_list_assets(): 483 | 484 | watchfolder_bucket = os.environ['VODONAWS_SOURCE'] 485 | table = dynamodb_resource.Table(os.environ['VODONAWS_DYNAMODBTABLE']) 486 | index_name = 'srcBucket-startTime-index' 487 | ret = {} 488 | 489 | logger.info('ENTER vod_list_assets()') 490 | 491 | try: 492 | 493 | filtering_exp = Key('srcBucket').eq(watchfolder_bucket) 494 | response = table.query( 495 | IndexName=index_name, KeyConditionExpression=filtering_exp, Limit=15, ScanIndexForward=False) 496 | logger.info("{}".format(json.dumps(response, cls=DecimalEncoder))) 497 | 498 | except ClientError as e: 499 | logger.info("ClientError from Dynamodb {}".format( 500 | e.response['Error']['Message'])) 501 | raise BadRequestError( 502 | "Dynamodb returned error message '%s'" % e.response['Error']['Message']) 503 | else: 504 | if 'Items' not in response: 505 | logger.info("No items in table") 506 | items = None 507 | else: 508 | logger.info("got some items") 509 | 510 | ret['items'] = response['Items'] 511 | ret['region'] = os.environ['AWS_REGION'] 512 | 513 | return json.dumps(ret, cls=DecimalEncoder) 514 | 515 | 516 | # The view function above will return {"hello": "world"} 517 | # whenever you make an HTTP GET request to '/'. 518 | # 519 | # Here are a few more examples: 520 | # 521 | # @app.route('/hello/{name}') 522 | # def hello_name(name): 523 | # # '/hello/james' -> {"hello": "james"} 524 | # return {'hello': name} 525 | # 526 | # @app.route('/users', methods=['POST']) 527 | # def create_user(): 528 | # # This is the JSON body the user sent in their POST request. 529 | # user_as_json = app.current_request.json_body 530 | # # We'll echo the json body back to the user in a 'user' key. 531 | # return {'user': user_as_json} 532 | # 533 | # See the README documentation for more examples. 534 | # 535 | -------------------------------------------------------------------------------- /RuleBasedEncoding/ruleapi/requirements.txt: -------------------------------------------------------------------------------- 1 | business-rules==1.0.1 -------------------------------------------------------------------------------- /RuleBasedEncoding/ruleweb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | 4 | Description: 5 | Rules Based Encoding Workshop - Dynamodb, S3 and Static website content 6 | 7 | Parameters: 8 | 9 | WebsiteBucketName: 10 | Description: Name for website bucket. 11 | Type: String 12 | 13 | Mappings: 14 | SourceCode: 15 | General: 16 | CodeBucket: "rodeolabz" 17 | KeyPrefix: "rules/3-rulesbasedencoding/v3" 18 | 19 | Resources: 20 | 21 | RulesTable: 22 | Type: AWS::DynamoDB::Table 23 | Properties: 24 | AttributeDefinitions: 25 | - 26 | AttributeName: "name" 27 | AttributeType: "S" 28 | KeySchema: 29 | - 30 | AttributeName: "name" 31 | KeyType: "HASH" 32 | ProvisionedThroughput: 33 | ReadCapacityUnits: 3 34 | WriteCapacityUnits: 3 35 | TableName: !Sub "${AWS::StackName}-RulesTable" 36 | 37 | Site: 38 | DeletionPolicy: Retain 39 | Properties: 40 | # BucketName: !Ref WebsiteBucketName 41 | WebsiteConfiguration: 42 | IndexDocument: index.html 43 | CorsConfiguration: 44 | CorsRules: 45 | - AllowedHeaders: ['*'] 46 | AllowedMethods: [GET] 47 | AllowedOrigins: ['*'] 48 | ExposedHeaders: [Date] 49 | Id: myCORSRuleId1 50 | MaxAge: '3600' 51 | Type: "AWS::S3::Bucket" 52 | 53 | #WebsiteBucketPolicy: 54 | # Properties: 55 | #Bucket: !Ref Site 56 | #PolicyDocument: 57 | # Version: 2012-10-17 58 | # Statement: 59 | # - 60 | # Effect: Allow 61 | # Principal: "*" 62 | # Action: s3:GetObject 63 | # Resource: !Sub "arn:aws:s3:::${Site}/*" 64 | # Type: "AWS::S3::BucketPolicy" 65 | 66 | WebsiteContent: 67 | Properties: 68 | ServiceToken: !GetAtt CopyS3ObjectsFunction.Arn 69 | SourceBucket: !Join ["-", [!FindInMap ["SourceCode", "General", "CodeBucket"], Ref: "AWS::Region"]] 70 | SourcePrefix: !Join ["/", [!FindInMap ["SourceCode", "General", "KeyPrefix"], "website/"]] 71 | Bucket: !Ref Site 72 | Type: "Custom::S3Objects" 73 | 74 | S3CopyRole: 75 | Type: AWS::IAM::Role 76 | Properties: 77 | 78 | Path: /vodonawslab/ 79 | AssumeRolePolicyDocument: 80 | Version: 2012-10-17 81 | Statement: 82 | - 83 | Effect: Allow 84 | Principal: 85 | Service: lambda.amazonaws.com 86 | Action: sts:AssumeRole 87 | Policies: 88 | - 89 | PolicyName: S3Access 90 | PolicyDocument: 91 | Version: 2012-10-17 92 | Statement: 93 | - 94 | Sid: AllowLogging 95 | Effect: Allow 96 | Action: 97 | - "logs:CreateLogGroup" 98 | - "logs:CreateLogStream" 99 | - "logs:PutLogEvents" 100 | Resource: "*" 101 | - 102 | Sid: SourceBucketReadAccess 103 | Effect: Allow 104 | Action: 105 | - "s3:ListBucket" 106 | - "s3:GetObject" 107 | Resource: 108 | - !Join 109 | - "" 110 | - - "arn:aws:s3:::" 111 | - !Join ["-", [!FindInMap ["SourceCode", "General", "CodeBucket"], Ref: "AWS::Region"]] 112 | - !Join 113 | - "" 114 | - - "arn:aws:s3:::" 115 | - !Join ["-", [!FindInMap ["SourceCode", "General", "CodeBucket"], Ref: "AWS::Region"]] 116 | - "/" 117 | - !FindInMap ["SourceCode", "General", "KeyPrefix"] 118 | - 119 | Sid: DestBucketWriteAccess 120 | Effect: Allow 121 | Action: 122 | - "s3:ListBucket" 123 | - "s3:GetObject" 124 | - "s3:PutObject" 125 | - "s3:PutObjectAcl" 126 | - "s3:PutObjectVersionAcl" 127 | - "s3:DeleteObject" 128 | - "s3:DeleteObjectVersion" 129 | - "s3:CopyObject" 130 | Resource: 131 | - !Sub "arn:aws:s3:::${Site}" 132 | - !Sub "arn:aws:s3:::${Site}/*" 133 | - "*" 134 | 135 | CopyS3ObjectsFunction: 136 | Properties: 137 | Description: Copies objects from a source S3 bucket to a destination 138 | Handler: index.handler 139 | Runtime: python2.7 140 | Role: !GetAtt S3CopyRole.Arn 141 | Timeout: 120 142 | Code: 143 | ZipFile: | 144 | import os 145 | import json 146 | import cfnresponse 147 | 148 | import boto3 149 | from botocore.exceptions import ClientError 150 | client = boto3.client('s3') 151 | 152 | import logging 153 | logger = logging.getLogger() 154 | logger.setLevel(logging.INFO) 155 | 156 | def handler(event, context): 157 | logger.info("Received event: %s" % json.dumps(event)) 158 | source_bucket = event['ResourceProperties']['SourceBucket'] 159 | source_prefix = event['ResourceProperties'].get('SourcePrefix') or '' 160 | bucket = event['ResourceProperties']['Bucket'] 161 | prefix = event['ResourceProperties'].get('Prefix') or '' 162 | 163 | result = cfnresponse.SUCCESS 164 | 165 | try: 166 | if event['RequestType'] == 'Create' or event['RequestType'] == 'Update': 167 | result = copy_objects(source_bucket, source_prefix, bucket, prefix) 168 | elif event['RequestType'] == 'Delete': 169 | result = delete_objects(bucket, prefix) 170 | except ClientError as e: 171 | logger.error('Error: %s', e) 172 | result = cfnresponse.FAILED 173 | 174 | cfnresponse.send(event, context, result, {}) 175 | 176 | 177 | def copy_objects(source_bucket, source_prefix, bucket, prefix): 178 | paginator = client.get_paginator('list_objects_v2') 179 | page_iterator = paginator.paginate(Bucket=source_bucket, Prefix=source_prefix) 180 | for key in {x['Key'] for page in page_iterator for x in page['Contents']}: 181 | dest_key = os.path.join(prefix, os.path.relpath(key, source_prefix)) 182 | if not key.endswith('/'): 183 | print 'copy {} to {}'.format(key, dest_key) 184 | client.copy_object(CopySource={'Bucket': source_bucket, 'Key': key}, Bucket=bucket, Key = dest_key, ACL='public-read') 185 | return cfnresponse.SUCCESS 186 | 187 | def delete_objects(bucket, prefix): 188 | #paginator = client.get_paginator('list_objects_v2') 189 | #page_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix) 190 | #objects = [{'Key': x['Key']} for page in page_iterator for x in page['Contents']] 191 | print ('delete no-op') 192 | #client.delete_objects(Bucket=bucket, Delete={'Objects': objects}) 193 | return cfnresponse.SUCCESS 194 | 195 | Type: AWS::Lambda::Function 196 | 197 | Outputs: 198 | RulesTableName: 199 | Value: !Ref RulesTable 200 | WebsiteURL: 201 | Value: !GetAtt Site.WebsiteURL 202 | WebsiteBucket: 203 | Value: !Ref Site -------------------------------------------------------------------------------- /RuleBasedEncoding/test/rulesonawsJobs/containervariables.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [1080, 720, 540, 360, 270], 3 | "dash": [1080, 720, 540, 360, 270], 4 | "mp4": [2160, 1080, 720], 5 | "srcVideo": "van_life.mp4", 6 | "FrameCapture": true, 7 | "ruleMappings": [ 8 | { 9 | "ruleName": "containervariables", 10 | "template": "theTemplateForContainerVariables" 11 | }, 12 | { 13 | "ruleName": "Container_eq_MXF", 14 | "template": "theTemplateForContainer_eq_MXF" 15 | }, 16 | { 17 | "ruleName": "Container_eq_MP4", 18 | "template": "System-Ott_Hls_Ts_Avc_Aac" 19 | }, 20 | { 21 | "ruleName": "Container_eq_Prores", 22 | "template": "theTemplateForContainer_eq_QuickTime" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/rulesonawsJobs/starlight.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [1080, 720, 540, 360, 270], 3 | "dash": [1080, 720, 540, 360, 270], 4 | "mp4": [2160, 1080, 720], 5 | "srcVideo": "starlight_2160p59.m2ts", 6 | "FrameCapture": true, 7 | "ruleMappings": [ 8 | { 9 | "ruleName": "Container_eq_MXF", 10 | "template": "theTemplateForContainer_eq_MXF" 11 | }, 12 | { 13 | "ruleName": "Container_eq_MP4", 14 | "template": "System-Ott_Hls_Ts_Avc_Aac", 15 | }, 16 | { 17 | "ruleName": "Container_eq_QuickTime", 18 | "template": "theTemplateForContainer_eq_Prores" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/rulesonawsJobs/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [1080, 720, 540, 360, 270], 3 | "dash": [1080, 720, 540, 360, 270], 4 | "mp4": [2160, 1080, 720], 5 | "srcVideo": "van_life.mp4", 6 | "FrameCapture": true, 7 | "ruleMappings": [ 8 | { 9 | "ruleName": "Container_eq_MXF", 10 | "template": "theTemplateForContainer_eq_MXF" 11 | }, 12 | { 13 | "ruleName": "Container_eq_MP4", 14 | "template": "System-Ott_Hls_Ts_Avc_Aac" 15 | }, 16 | { 17 | "ruleName": "Container_eq_Prores", 18 | "template": "theTemplateForContainer_eq_QuickTime" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/testMediainfoRulesEngine.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/RuleBasedEncoding/test/testMediainfoRulesEngine.json -------------------------------------------------------------------------------- /RuleBasedEncoding/test/testMediainfoRulesEngineProfiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "srcVideo": "van_life.mp4", 3 | "FrameCapture": true, 4 | "ruleMappings": [ 5 | { 6 | "ruleName": "Container_eq_MXF", 7 | "template": "theTemplateForContainer_eq_MXF" 8 | }, 9 | { 10 | "ruleName": "Container_eq_MP4", 11 | "template": "System-Ott_Hls_Ts_Avc_Aac" 12 | }, 13 | { 14 | "ruleName": "Container_eq_Prores", 15 | "template": "theTemplateForContainer_eq_Prores" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/beach.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "hls": [720, 540, 360, 270], 4 | "srcVideo": "beach_aerial_short.mp4", 5 | "FrameCapture": true, 6 | "MetadataTestMessage": "Hello from the other side!" 7 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/caminades.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [720, 540, 360, 270], 3 | "srcVideo": "02_gran_dillama_1080p.mp4", 4 | "FrameCapture": true, 5 | "MetadataTestMessage": "Hello from the other side!" 6 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/coffee.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [720, 540, 360, 270], 3 | "srcVideo": "coffee_720p60.mp4", 4 | "FrameCapture": true, 5 | "MetadataTestMessage": "Hello from the other side!" 6 | } 7 | -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/donuts.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [720, 540, 360, 270], 3 | "srcVideo": "donuts_720p60.mp4", 4 | "FrameCapture": true, 5 | "MetadataTestMessage": "Hello from the other side!" 6 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/silksmxf.json: -------------------------------------------------------------------------------- 1 | { 2 | "srcVideo": "silksintrees_MPEG2.mxf", 3 | "FrameCapture": true, 4 | "MetadataTestMessage": "Hello from the other side!" 5 | } 6 | -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/starlight.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [720, 540, 360, 270], 3 | "srcVideo": "STARLIGHT_PARADE_SDR-FLAT_SDR_25000_CBR_2_pass_10bit_1.m2ts", 4 | "FrameCapture": true, 5 | "MetadataTestMessage": "Hello from the other side!" 6 | } 7 | -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/testMetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [720, 540, 360, 270], 3 | "srcVideo": "van_life.mp4", 4 | "FrameCapture": true, 5 | "MetadataTestMessage": "Hello from the other side!" 6 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/van_life_scte35.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [720, 540, 360, 270], 3 | "srcVideo": "VANLIFE_FINAL_scte35_2tracks.m2ts", 4 | "FrameCapture": true, 5 | "MetadataTestMessage": "Hello from the other side!" 6 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/test/vodonawsJobs/wine.json: -------------------------------------------------------------------------------- 1 | { 2 | "hls": [720, 540, 360, 270], 3 | "srcVideo": "winetasting_720p60.mp4", 4 | "FrameCapture": true, 5 | "MetadataTestMessage": "Hello from the other side!" 6 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/website/deploy/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | "watchfolderBucket":"SOURCE OUTPUT FROM VOD ON AWS STACK", 3 | "apiEndpoint":"EndpointURL OUTPUT FROM RULEAPI STACK" 4 | } -------------------------------------------------------------------------------- /RuleBasedEncoding/website/images/RulesBasedEncodingArchitcture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/RuleBasedEncoding/website/images/RulesBasedEncodingArchitcture.png -------------------------------------------------------------------------------- /RuleBasedEncoding/website/images/RulesBasedEncodingWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/RuleBasedEncoding/website/images/RulesBasedEncodingWorkflow.png -------------------------------------------------------------------------------- /RuleBasedEncoding/website/images/RulesBasedEncodingWorkflow1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/RuleBasedEncoding/website/images/RulesBasedEncodingWorkflow1.png -------------------------------------------------------------------------------- /RuleBasedEncoding/website/images/RulesBasedEncodingWorkflowSimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/RuleBasedEncoding/website/images/RulesBasedEncodingWorkflowSimple.png -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/app.js: -------------------------------------------------------------------------------- 1 | /*! Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | SPDX-License-Identifier: Apache-2.0 */ 3 | 4 | // define the various loading paths 5 | requirejs.config({ 6 | "baseUrl": "js/lib", 7 | "paths": { 8 | "app": "https://cdn.jsdelivr.net/gh/awslabs/aws-media-services-application-mapper@v0.3.0/html/js/app/", 9 | "ruleapp": "../ruleapp", 10 | "automerge": "https://cdn.jsdelivr.net/npm/automerge@0.7.8/dist/automerge.min", 11 | "cookie": "https://cdn.jsdelivr.net/npm/js-cookie@2.2.0/src/js.cookie.min", 12 | "fuse": "https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min", 13 | "levenshtein": "https://cdn.jsdelivr.net/npm/fast-levenshtein@2.0.6/levenshtein.min", 14 | "lodash": "https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min", 15 | "machina": "https://cdn.jsdelivr.net/npm/machina@2.0.2/lib/machina.min", 16 | "vis": "https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min", 17 | "window": "https://cdn.jsdelivr.net/gh/awslabs/aws-media-services-application-mapper@v0.3.0/html/js/app/window", 18 | "api_check": "https://cdn.jsdelivr.net/gh/awslabs/aws-media-services-application-mapper@v0.3.0/html/js/app/api_check" 19 | }, 20 | // add a cache-buster for module loading 21 | //"urlArgs": "t=" + (new Date()).getTime(), 22 | "waitSeconds": 30 23 | }); 24 | // load the main module and go 25 | requirejs(["ruleapp/main"]); -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/lib/jquery.js: -------------------------------------------------------------------------------- 1 | /*! Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | SPDX-License-Identifier: Apache-2.0 */ 3 | 4 | define('jquery', [], function() { 5 | return jQuery; 6 | }); -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/ruleapp/main.js: -------------------------------------------------------------------------------- 1 | /*! Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | SPDX-License-Identifier: Apache-2.0 */ 3 | 4 | // this module bootstraps the state machine, which starts the application running 5 | 6 | define(["window"], function(window) { 7 | // wait for the signal before loading and initializing 8 | $(window.document).ready(function() { 9 | // minimal modules required to start running 10 | //require(["app/statemachine", "app/ui/status_view"], function(statemachine) { 11 | // say hello 12 | console.log("main"); 13 | 14 | // start the outer FSM at the beginning 15 | // statemachine.getToolStateMachine().start(); 16 | init(); 17 | 18 | }); 19 | }); -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/ruleapp/ui/querybuilder/filters.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var FILTERS = [{ 4 | id: 'count_of_tracks', 5 | label: 'Container: Count of Tracks', 6 | type: 'string' 7 | }, { 8 | id: 'count_of_audio_streams', 9 | label: 'Container: Count of Audio Steams', 10 | type: 'integer' 11 | }, { 12 | id: 'count_of_video_streams', 13 | label: 'Container: Count of Video Steams', 14 | type: 'integer' 15 | }, { 16 | id: 'count_of_menu_streams', 17 | label: 'Container: Count of Menu Steams', 18 | type: 'integer' 19 | },{ 20 | id: 'count_of_text_streams', 21 | label: 'Container: Count of Text Steams', 22 | type: 'integer' 23 | },{ 24 | id: 'container_format', 25 | label: 'Container: Format', 26 | type: 'integer', 27 | input: 'select', 28 | values: { 29 | 1: 'MPEG-4', 30 | 2: 'QuickTime', 31 | 3: 'Matroska', 32 | 4: 'AVI', 33 | 5: 'MPEG-PS', 34 | 6: 'MPEG-TS', 35 | 7: 'MXF', 36 | 8: 'GXF', 37 | 9: 'LXF', 38 | 10: 'WMV', 39 | 11: 'FLV', 40 | 12: 'Real' 41 | }, 42 | operators: ['equal', 'not_equal'] 43 | },{ 44 | id: 'container_datasize', 45 | label: 'Container: datasize (bytes)', 46 | type: 'integer' 47 | }, { 48 | id: 'container_duration(', 49 | label: 'Container: Duration (sec)', 50 | type: 'double', 51 | validation: { 52 | min: 0, 53 | step: 0.01 54 | } 55 | }]; 56 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/ruleapp/ui/querybuilder/qb-view-rule.js: -------------------------------------------------------------------------------- 1 | var RULES_BASIC = { 2 | condition: 'AND', 3 | rules: [{ 4 | id: 'container_format', 5 | operator: 'equal', 6 | value: 'MPEG-4' 7 | }] 8 | }; 9 | 10 | var BUSINESSRULES_BASIC = 11 | { 12 | "actions": [ 13 | { "name": "Encode with template", 14 | "label": "MediaConvert template", 15 | "params": {"MediaConvert template name": "string"} 16 | } 17 | ], 18 | }; 19 | //[{"name":"do_encode_with_job","params":{"job":"foo"}}] 20 | 21 | /** 22 | * Translate filters from business rules backend to QueryBuilder filters 23 | * @param data {business rules object} 24 | * @return {queryBuilder filters} 25 | */ 26 | function translateBusinessRulesFilters(rules) { 27 | 28 | var filters = []; 29 | 30 | rules.variables.forEach(function (v) { 31 | 32 | var variable = {}; 33 | variable.id = v.name; 34 | variable.label = v.label; 35 | //variable_type_operator = data.variable_type_operators.find(function (obj) { return obj.id === rule.field; }); 36 | // Use the most permissive type to translate "numeric" 37 | // We can't do any validation because the stronger types are lost in 38 | // translation 39 | if (v.field_type === "numeric") { 40 | variable.type = "double"; 41 | variable.operators = ["equal", "less", "less_or_equal", "greater_or_equal", "greater"]; 42 | //FIXME - add nulls 43 | } 44 | 45 | variable.options = []; 46 | 47 | if (v.field_type === "select") { 48 | variable.values = v.options; 49 | variable.type = "integer"; 50 | variable.input = "select", 51 | variable.operators = ["equal", "not_equal"] 52 | } 53 | 54 | if (v.type === "string") { 55 | variable.operators = ["equal", "starts_with", "ends_with", "contains", "is_not_empty" ]; 56 | //FIXME - add nulls 57 | //Can't support matches_regex 58 | 59 | } 60 | 61 | //FIXME - support date, datetime and multiselect 62 | filters.push(variable); 63 | }); 64 | 65 | return filters; 66 | } 67 | 68 | function initializeRules(filters, qbrulesin, rulesdiv) { 69 | //$('#builder').queryBuilder({ 70 | rulesdiv.queryBuilder({ 71 | plugins: ['bt-tooltip-errors'], 72 | filters: filters, 73 | rules: qbrulesin 74 | }); 75 | 76 | $('#btn-get').on('click', function() { 77 | var result = $('#builder').queryBuilder('getRules'); 78 | if (!$.isEmptyObject(result)) { 79 | alert(JSON.stringify(result, null, 2)); 80 | } 81 | else{ 82 | console.log("invalid object :"); 83 | } 84 | console.log(result); 85 | }); 86 | 87 | $('#btn-get-br').on('click', function() { 88 | var result = $('#builder').queryBuilder('getBusinessRules'); 89 | if (!$.isEmptyObject(result)) { 90 | alert(JSON.stringify(result, null, 2)); 91 | } 92 | else{ 93 | console.log("invalid object :"); 94 | } 95 | console.log(result); 96 | }); 97 | 98 | $('#btn-reset').on('click', function() { 99 | $('#builder').queryBuilder('reset'); 100 | }); 101 | 102 | $('#btn-set').on('click', function() { 103 | //$('#builder').queryBuilder('setRules', rules_basic); 104 | var ruleSet = 'foo'; 105 | var connections = requirejs('app/connections'); 106 | var current_connection = connections.get_current(); 107 | var CONFIG_API_BASE_URL = current_connection[0]; 108 | var key = current_connection[1]; 109 | const url = CONFIG_API_BASE_URL+'/rules/'+ruleSet; 110 | // var actions = $("#actions").actionsBuilder('data'); 111 | var qbrules = $('#builder').queryBuilder('getRules'); 112 | var brrules = $('#builder').queryBuilder('getBusinessRules'); 113 | var businessrules = {}; 114 | var fetchInit = { 115 | method: 'POST', mode: "cors", // no-cors, cors, *same-origin 116 | cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached 117 | credentials: "same-origin", // include, same-origin, *omit 118 | headers: { 119 | "Content-Type": "application/json; charset=utf-8", 120 | }, 121 | redirect: "follow", // manual, *follow, error 122 | referrer: "no-referrer", // no-referrer, *client 123 | }; 124 | 125 | businessrules["qb-rules"] = qbrules; 126 | businessrules["conditions"] = brrules; 127 | // businessrules["actions"] = actions; 128 | 129 | fetchInit['body'] = JSON.stringify(businessrules) 130 | 131 | console.log(businessrules); 132 | 133 | postData(url, key, fetchInit ) 134 | .then(function(data) { 135 | console.log(data) 136 | 137 | console.log("Rules POST OK") 138 | 139 | 140 | }) 141 | .catch(error => console.error('Error fetching inital data:', error)); 142 | 143 | }); 144 | 145 | $('#btn-load').on('click', function(){ 146 | load_rules_set("foo"); 147 | }); 148 | 149 | //When rules changed : 150 | $('#builder').on('getRules.queryBuilder.filter', function(e) { 151 | //$log.info(e.value); 152 | }); 153 | } 154 | 155 | // function initializeActions(ruleactions, actions) { 156 | // actions.actionsBuilder(ruleactions); 157 | // } 158 | 159 | // ruleSet is the name of the rule in this case. 160 | function load_rules_set(ruleSet){ 161 | //$('#builder').queryBuilder('setRules', rules_basic); 162 | var connections = requirejs('app/connections'); 163 | var current_connection = connections.get_current(); 164 | var CONFIG_API_BASE_URL = current_connection[0]; 165 | var key = current_connection[1]; 166 | const url = CONFIG_API_BASE_URL+'/rules/'+ruleSet; 167 | var actionsdiv = $("#actions-load"); 168 | var rulesdiv = $("#builder-load"); 169 | 170 | $('#ruleset_name').html("

Rule Name: "+ruleSet+"

"); 171 | 172 | getData(url, key) 173 | .then(function(data) { 174 | console.log(data) 175 | 176 | const url = CONFIG_API_BASE_URL+'/rules/variables'; 177 | 178 | 179 | getData(url, key) 180 | .then(function(vars) { 181 | console.log(vars) 182 | f = translateBusinessRulesFilters(vars); 183 | console.log(f); 184 | //FIXME - set this from stored actions not defined actions 185 | // initializeActions(vars, actionsdiv); 186 | initializeRules(f, data['rules']['qb-rules'], rulesdiv); 187 | 188 | }) 189 | .catch(error => console.error('Error fetching inital data:', error)); 190 | 191 | //initializeRules(f, data['qb-rules']); 192 | 193 | 194 | //initializeActions(data['actions'], actions); 195 | //FIXME - set this from stored actions 196 | //initializeActions(vars, actionsdiv); 197 | //initializeForm(); 198 | 199 | }) 200 | .catch(error => console.error('Error fetching inital data:', error)); 201 | 202 | } 203 | 204 | 205 | 206 | function init() { 207 | require(['app/connections'], function (connections) { 208 | var params = location.href.split('?')[1].split('&'); 209 | console.log("The parameter is " + params[0]); 210 | load_rules_set(params[0]); 211 | }); 212 | } 213 | 214 | function postData(url = ``, api_key = "", data = {}) { 215 | return fetch(url, { 216 | method: "POST", // *GET, POST, PUT, DELETE, etc. 217 | mode: "cors", // no-cors, cors, *same-origin 218 | cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached 219 | headers: { 220 | "Content-Type": "application/json; charset=utf-8", 221 | 'x-api-key': api_key 222 | }, 223 | redirect: "follow", // manual, *follow, error 224 | referrer: "no-referrer", // no-referrer, *client 225 | body: JSON.stringify(data), // body data type must match "Content-Type" header 226 | }) 227 | .then(response => response.json()); // parses response to JSON 228 | } 229 | 230 | function getData(url = ``, api_key = "", data = {}) { 231 | return fetch(url, { 232 | method: "GET", // *GET, POST, PUT, DELETE, etc. 233 | mode: "cors", // no-cors, cors, *same-origin 234 | cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached 235 | headers: { 236 | "Content-Type": "application/json; charset=utf-8", 237 | 'x-api-key': api_key 238 | }, 239 | redirect: "follow", // manual, *follow, error 240 | referrer: "no-referrer", // no-referrer, *client 241 | }) 242 | .then(response => response.json()); // parses response to JSON 243 | } 244 | /**************************************************************** 245 | Triggers and Changers QueryBuilder 246 | *****************************************************************/ 247 | 248 | //$(onReady); 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/ruleapp/ui/querybuilder/qb.js: -------------------------------------------------------------------------------- 1 | var RULES_BASIC = { 2 | condition: 'AND', 3 | rules: [{ 4 | id: 'container_format', 5 | operator: 'equal', 6 | value: 'MPEG-4' 7 | }] 8 | }; 9 | 10 | var BUSINESSRULES_BASIC = 11 | { 12 | "actions": [ 13 | { 14 | "name": "Encode with template", 15 | "label": "MediaConvert template", 16 | "params": { "MediaConvert template name": "string" } 17 | } 18 | ], 19 | }; 20 | //[{"name":"do_encode_with_job","params":{"job":"foo"}}] 21 | 22 | 23 | 24 | 25 | /** 26 | * Translate filters from business rules backend to QueryBuilder filters 27 | * @param data {business rules object} 28 | * @return {queryBuilder filters} 29 | */ 30 | function translateBusinessRulesFilters(rules) { 31 | 32 | var filters = []; 33 | 34 | rules.variables.forEach(function (v) { 35 | 36 | var variable = {}; 37 | variable.id = v.name; 38 | variable.label = v.label; 39 | //variable_type_operator = data.variable_type_operators.find(function (obj) { return obj.id === rule.field; }); 40 | // Use the most permissive type to translate "numeric" 41 | // We can't do any validation because the stronger types are lost in 42 | // translation 43 | if (v.field_type === "numeric") { 44 | variable.type = "double"; 45 | variable.operators = ["equal", "less", "less_or_equal", "greater_or_equal", "greater"]; 46 | //FIXME - add nulls 47 | } 48 | 49 | variable.options = []; 50 | 51 | if (v.field_type === "select") { 52 | variable.values = v.options; 53 | variable.type = "integer"; 54 | variable.input = "select", 55 | variable.operators = ["equal", "not_equal"] 56 | } 57 | 58 | if (v.type === "string") { 59 | variable.operators = ["equal", "starts_with", "ends_with", "contains", "is_not_empty"]; 60 | //FIXME - add nulls 61 | //Can't support matches_regex 62 | 63 | } 64 | 65 | //FIXME - support date, datetime and multiselect 66 | filters.push(variable); 67 | }); 68 | 69 | return filters; 70 | } 71 | 72 | function initializeRules(filters, qbrulesin, rulesdiv) { 73 | //$('#builder').queryBuilder({ 74 | rulesdiv.queryBuilder({ 75 | plugins: ['bt-tooltip-errors'], 76 | filters: filters, 77 | rules: qbrulesin 78 | }); 79 | 80 | $('#btn-get').on('click', function () { 81 | var result = $('#builder').queryBuilder('getRules'); 82 | if (!$.isEmptyObject(result)) { 83 | alert(JSON.stringify(result, null, 2)); 84 | } 85 | else { 86 | console.log("invalid object :"); 87 | } 88 | console.log(result); 89 | }); 90 | 91 | $('#btn-get-br').on('click', function () { 92 | var result = $('#builder').queryBuilder('getBusinessRules'); 93 | if (!$.isEmptyObject(result)) { 94 | alert(JSON.stringify(result, null, 2)); 95 | } 96 | else { 97 | console.log("invalid object :"); 98 | } 99 | console.log(result); 100 | }); 101 | 102 | $('#btn-get-sql').on('click', function () { 103 | var result = $('#builder').queryBuilder('getSQL', false, false); 104 | 105 | if (result.sql.length) { 106 | alert(result.sql + '\n\n' + JSON.stringify(result.params, null, 2)); 107 | } 108 | }); 109 | 110 | $('#btn-reset').on('click', function () { 111 | $('#builder').queryBuilder('reset'); 112 | }); 113 | 114 | $('#btn-set').on('click', function () { 115 | //$('#builder').queryBuilder('setRules', rules_basic); 116 | var connections = requirejs('app/connections'); 117 | var current_connection = connections.get_current(); 118 | var CONFIG_API_BASE_URL = current_connection[0]; 119 | var CONFIG_API_KEY = current_connection[1]; 120 | var ruleSet = $("#inputRulesetName").val(); 121 | const url = CONFIG_API_BASE_URL + '/rules/' + ruleSet; 122 | const key = CONFIG_API_KEY; 123 | var result = "Rule was created successfully!" 124 | // var actions = $("#actions").actionsBuilder('data'); 125 | 126 | //Remove previous results 127 | $("#result").empty() 128 | //Check inputs 129 | console.log("Rule name is " + ruleSet); 130 | if (ruleSet === "") { 131 | message = "\nInput Error: Please enter a Rule name."; 132 | //document.getElementById("result").innerHTML = message; 133 | $("#result").append('
' + message + '
'); 134 | return; 135 | } else { 136 | 137 | var qbrules = $('#builder').queryBuilder('getRules'); 138 | var brrules = $('#builder').queryBuilder('getBusinessRules'); 139 | var businessrules = {}; 140 | var fetchInit = { 141 | method: 'POST', mode: "cors", // no-cors, cors, *same-origin 142 | cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached 143 | credentials: "same-origin", // include, same-origin, *omit 144 | headers: { 145 | "Content-Type": "application/json; charset=utf-8", 146 | }, 147 | redirect: "follow", // manual, *follow, error 148 | referrer: "no-referrer", // no-referrer, *client 149 | }; 150 | 151 | businessrules["qb-rules"] = qbrules; 152 | businessrules["conditions"] = brrules; 153 | // businessrules["actions"] = actions; 154 | 155 | fetchInit['body'] = JSON.stringify(businessrules) 156 | 157 | console.log(businessrules); 158 | 159 | postData(url, key, fetchInit) 160 | //fetch(url, fetchInit) 161 | // .then(function (response) { 162 | // return (response.json()); 163 | // }) 164 | .then(function (data) { 165 | console.log(data) 166 | 167 | console.log("Rules POST OK") 168 | 169 | document.getElementById("result").append = '
' + 'Error: please enter a Rule Name.' + '
";'; 170 | 171 | }) 172 | .catch(error => console.error('Error fetching inital data:', error)); 173 | 174 | $("#result").append('
' + result + '
'); 175 | } 176 | }); 177 | 178 | $('#btn-load').on('click', function () { 179 | load_rules_set("foo"); 180 | }); 181 | 182 | //When rules changed : 183 | $('#builder').on('getRules.queryBuilder.filter', function (e) { 184 | //$log.info(e.value); 185 | }); 186 | 187 | } 188 | 189 | function initializeActions(ruleactions, actions) { 190 | //noop actions.actionsBuilder(ruleactions); 191 | } 192 | 193 | // ruleSet is the name of the rule in this case. 194 | function load_rules_set(ruleSet) { 195 | //$('#builder').queryBuilder('setRules', rules_basic); 196 | var connections = requirejs('app/connections'); 197 | var current_connection = connections.get_current(); 198 | var CONFIG_API_BASE_URL = current_connection[0]; 199 | var CONFIG_API_KEY = current_connection[1]; 200 | const url = CONFIG_API_BASE_URL + '/rules/' + ruleSet; 201 | var actionsdiv = $("#actions-load"); 202 | var rulesdiv = $("#builder-load"); 203 | 204 | fetch(url) 205 | .then(function (response) { 206 | return (response.json()); 207 | }) 208 | .then(function (data) { 209 | console.log(data) 210 | 211 | var connections = requirejs('app/connections'); 212 | var current_connection = connections.get_current(); 213 | var CONFIG_API_BASE_URL = current_connection[0]; 214 | var CONFIG_API_KEY = current_connection[1]; 215 | const url = CONFIG_API_BASE_URL + '/rules/variables'; 216 | 217 | fetch(url) 218 | .then(function (response) { 219 | return (response.json()); 220 | }) 221 | .then(function (vars) { 222 | console.log(vars) 223 | f = translateBusinessRulesFilters(vars); 224 | console.log(f); 225 | //FIXME - set this from stored actions not defined actions 226 | initializeActions(vars, actionsdiv); 227 | initializeRules(f, data['rules']['qb-rules'], rulesdiv); 228 | 229 | }) 230 | .catch(error => console.error('Error fetching inital data:', error)); 231 | 232 | //initializeRules(f, data['qb-rules']); 233 | 234 | 235 | //initializeActions(data['actions'], actions); 236 | //FIXME - set this from stored actions 237 | //initializeActions(vars, actionsdiv); 238 | //initializeForm(); 239 | 240 | }) 241 | .catch(error => console.error('Error fetching inital data:', error)); 242 | 243 | } 244 | 245 | function init() { 246 | 247 | require(['app/connections'], function (connections) { 248 | 249 | if (null != connections.get_current()) { 250 | 251 | console.log(""+connections) 252 | //var connections = require('app/connections'); 253 | var current_connection = connections.get_current(); 254 | 255 | console.log("current connection: " + current_connection[0]); 256 | 257 | const url = current_connection[0] + '/rules/variables'; 258 | const api_key = current_connection[1] 259 | var actionsdiv = $("#actions"); 260 | var rulesdiv = $("#builder"); 261 | //var actionsdiv = "#actions"; 262 | //var rulesdiv = "#builder"; 263 | 264 | getData(url, api_key) 265 | //.then(function (response) { 266 | // return (response.json()); 267 | //}) 268 | .then(function (data) { 269 | console.log(data) 270 | 271 | f = translateBusinessRulesFilters(data); 272 | console.log(f); 273 | initializeRules(f, RULES_BASIC, rulesdiv); 274 | initializeActions(data, actionsdiv); 275 | //initializeForm(); 276 | 277 | }) 278 | .catch(error => console.error('Error fetching inital data:', error)); 279 | } 280 | else { 281 | console.log("no current connection - set a connection to get going"); 282 | } 283 | 284 | 285 | console.log("history:" + connections.get_history()) 286 | 287 | }); 288 | 289 | } 290 | 291 | function postData(url = ``, api_key = "", data = {}) { 292 | return fetch(url, { 293 | method: "POST", // *GET, POST, PUT, DELETE, etc. 294 | mode: "cors", // no-cors, cors, *same-origin 295 | cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached 296 | headers: { 297 | "Content-Type": "application/json; charset=utf-8", 298 | 'x-api-key': api_key 299 | }, 300 | redirect: "follow", // manual, *follow, error 301 | referrer: "no-referrer", // no-referrer, *client 302 | body: JSON.stringify(data), // body data type must match "Content-Type" header 303 | }) 304 | .then(response => response.json()); // parses response to JSON 305 | } 306 | 307 | function getData(url = ``, api_key = "", data = {}) { 308 | return fetch(url, { 309 | method: "GET", // *GET, POST, PUT, DELETE, etc. 310 | mode: "cors", // no-cors, cors, *same-origin 311 | cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached 312 | headers: { 313 | "Content-Type": "application/json; charset=utf-8", 314 | 'x-api-key': api_key 315 | }, 316 | redirect: "follow", // manual, *follow, error 317 | referrer: "no-referrer", // no-referrer, *client 318 | }) 319 | .then(response => response.json()); // parses response to JSON 320 | } 321 | 322 | /**************************************************************** 323 | Triggers and Changers QueryBuilder 324 | *****************************************************************/ 325 | 326 | //$(init); 327 | 328 | 329 | 330 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/ruleapp/ui/querybuilder/query-builder-business-rules.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery QueryBuilder business-rules Support 3 | * Allows to export rules as a business-rules statement. 4 | * https://github.com/mistic100/jQuery-QueryBuilder 5 | */ 6 | 7 | (function(root, factory) { 8 | if (typeof define === 'function' && define.amd) { 9 | define(['jquery', 'query-builder'], factory); 10 | } 11 | else { 12 | factory(root.jQuery); 13 | } 14 | }(this, function($) { 15 | "use strict"; 16 | 17 | var QueryBuilder = $.fn.queryBuilder; 18 | 19 | // DEFAULT CONFIG 20 | // =============================== 21 | QueryBuilder.defaults({ 22 | businessRulesOperators: { 23 | //numeric, string, boolean 24 | equal: function(v){ 25 | var obj = new Object; 26 | obj["operator"] = 'equal_to'; 27 | obj["value"] = v[0]; 28 | 29 | return obj 30 | }, 31 | //numeric 32 | not_equal: function(v){ 33 | var obj = new Object; 34 | obj["operator"] = 'not_equal_to'; 35 | obj["value"] = v[0]; 36 | return obj 37 | }, 38 | //numeric 39 | less: function(v){ 40 | var obj = new Object; 41 | obj["operator"] = 'less_than'; 42 | obj["value"] = v[0]; 43 | return obj 44 | }, 45 | //numeric 46 | less_or_equal: function(v){ 47 | var obj = new Object; 48 | obj["operator"] = 'less_than_or_equal_to'; 49 | obj["value"] = v[0]; 50 | return obj 51 | }, 52 | //numeric 53 | greater: function(v){ 54 | var obj = new Object; 55 | obj["operator"] = 'greater_than'; 56 | obj["value"] = v[0]; 57 | return obj 58 | }, 59 | //numeric 60 | greater_or_equal: function(v){ 61 | var obj = new Object; 62 | obj["operator"] = 'greater_than_or_equal'; 63 | obj["value"] = v[0]; 64 | return obj 65 | }, 66 | //string 67 | begins_with: function(v){ return {'starts_with': v[0]}; }, 68 | //string 69 | xxxxxx: function(v){ return {'matches_regex': '^' + escapeRegExp(v[0])}; }, 70 | //string, select 71 | contains: function(v){ 72 | var obj = new Object; 73 | obj["operator"] = 'contains'; 74 | obj["value"] = v[0]; 75 | return obj 76 | }, 77 | //string 78 | ends_with: function(v){ return {'ends_with': escapeRegExp(v[0]) + '$'}; }, 79 | //select 80 | not_contains: function(v){ 81 | var obj = new Object; 82 | obj["operator"] = 'does_not_contain'; 83 | obj["value"] = v[0]; 84 | return obj 85 | }, 86 | //function(v){ return {'does_not_contain': escapeRegExp(v[0])}; } 87 | } 88 | }); 89 | 90 | 91 | // PUBLIC METHODS 92 | // =============================== 93 | QueryBuilder.extend({ 94 | /** 95 | * Get rules as business-rules query 96 | * @param data {object} (optional) rules 97 | * @return {object} 98 | */ 99 | getBusinessRules: function(data) { 100 | data = (data===undefined) ? this.getRules() : data; 101 | 102 | var that = this; 103 | 104 | return (function parse(data) { 105 | if (!data.condition) { 106 | data.condition = that.settings.default_condition; 107 | } 108 | if (['AND', 'OR'].indexOf(data.condition.toUpperCase()) === -1) { 109 | error('Unable to build business-rules query with condition "{0}"', data.condition); 110 | } 111 | 112 | if (!data.rules) { 113 | return {}; 114 | } 115 | 116 | var parts = []; 117 | 118 | data.rules.forEach(function(rule) { 119 | if (rule.rules && rule.rules.length>0) { 120 | parts.push(parse(rule)); 121 | } 122 | else { 123 | //Map selection value from Array enumeration 124 | if(rule.input === "select") { 125 | var f = FILTERS.find(function (obj) { return obj.id === rule.field; }); 126 | if (rule.operator === "equal") { 127 | rule.operator = "contains" ; 128 | 129 | } else { 130 | rule.operator = "not_contains" ; 131 | } 132 | rule.type = "string" 133 | //assume only single select allowed 134 | //rule.value = f.values[rule.value]; 135 | } 136 | 137 | var mdb = that.settings.businessRulesOperators[rule.operator], 138 | ope = that.getOperatorByType(rule.operator), 139 | values = []; 140 | 141 | if (mdb === undefined) { 142 | error('Unknown business-rules operation for operator "{0}"', rule.operator); 143 | } 144 | 145 | if (ope.nb_inputs !== 0) { 146 | if (!(rule.value instanceof Array)) { 147 | rule.value = [rule.value]; 148 | } 149 | 150 | rule.value.forEach(function(v) { 151 | values.push(changeType(v, rule.type)); 152 | }); 153 | } 154 | 155 | var part = {}; 156 | part = mdb.call(that, values); 157 | part.name = rule.field 158 | parts.push(part); 159 | } 160 | }); 161 | 162 | var res = {}; 163 | if (parts.length > 0) { 164 | var condition = ((data.condition.toLowerCase() === 'and') ? 'all' : 'any'); 165 | res[ condition ] = parts; 166 | } 167 | return res; 168 | }(data)); 169 | } 170 | }); 171 | 172 | 173 | /** 174 | * Change type of a value to int or float 175 | * @param value {mixed} 176 | * @param type {string} 'integer', 'double' or anything else 177 | * @param boolAsInt {boolean} return 0 or 1 for booleans 178 | * @return {mixed} 179 | */ 180 | function changeType(value, type, boolAsInt) { 181 | switch (type) { 182 | case 'integer': return parseInt(value); 183 | case 'double': return parseFloat(value); 184 | case 'boolean': 185 | var bool = value.trim().toLowerCase() === "true" || value.trim() === '1' || value === 1; 186 | return boolAsInt ? (bool ? 1 : 0) : bool; 187 | default: return value; 188 | } 189 | } 190 | 191 | /** 192 | * Escape value for use in regex 193 | * @param value {string} 194 | * @return {string} 195 | */ 196 | function escapeRegExp(str) { 197 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 198 | } 199 | 200 | 201 | })); 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/js/ruleapp/ui/settings_menu.js: -------------------------------------------------------------------------------- 1 | /*! Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | SPDX-License-Identifier: Apache-2.0 */ 3 | 4 | define(["jquery", "app/connections", "app/ui/util", "app/api_check"], 5 | function($, connections, util, api_check) { 6 | 7 | var history_to_buttons = function(history) { 8 | $.each(history, function(index, item) { 9 | // console.log(item); 10 | var id = util.makeid(); 11 | var url = item[0]; 12 | var apiKey = item[1]; 13 | if (index == 0) { 14 | $("#input_endpoint_url").val(url); 15 | $("#input_endpoint_key").val(apiKey); 16 | } 17 | var html = `${url}`; 18 | $("#connectionHistoryDropdownMenu").append(html); 19 | // add event handlers to each item to populate dialog fields 20 | $("#" + id).click((function() { 21 | let u = url; 22 | let k = apiKey; 23 | return function(event) { 24 | $("#input_endpoint_url").val(u); 25 | $("#input_endpoint_key").val(k); 26 | } 27 | })()); 28 | }); 29 | }; 30 | 31 | var updateConnectionDialog = function() { 32 | var history = Array.from(connections.get_history()); 33 | // clear the existing dropdown items 34 | $("#connectionHistoryDropdownMenu").empty(); 35 | // replace with current history items 36 | if (history.length > 0) { 37 | $("#connectionHistoryMenuButton").removeClass("disabled"); 38 | history_to_buttons(history); 39 | } else { 40 | $("#connectionHistoryMenuButton").addClass("disabled"); 41 | } 42 | }; 43 | 44 | // add a save handler for the connection dialog 45 | $("#save_endpoint_connection").on("click", () => { 46 | try { 47 | // trim the string, normalize the link, remove any trailing slash 48 | // endpoint = new URI($("#input_endpoint_url").val().trim()).normalize().replace(/\/+$/, ""); 49 | endpoint = new URI($("#input_endpoint_url").val().trim()).normalize().toString().replace(/\/+$/, ""); 50 | console.log("normalized to " + endpoint); 51 | apiKey = $("#input_endpoint_key").val().trim(); 52 | // test the provided info before saving 53 | api_check.ping(endpoint, apiKey).then(function(response) { 54 | // test worked 55 | console.log(response); 56 | connections.set_current(endpoint, apiKey); 57 | hideConnectionDialog(); 58 | updateConnectionDialog(); 59 | //require("app/statemachine").getToolStateMachine().connectionChanged(); 60 | location.reload(); 61 | }).catch(function(error) { 62 | console.log(error); 63 | setConnectionAlert("There is a problem with this endpoint connection, please fix it"); 64 | }); 65 | } catch (error) { 66 | console.log(error); 67 | setConnectionAlert("There is a problem with this endpoint connection, please fix it"); 68 | } 69 | return true; 70 | }); 71 | 72 | $("#cancel_endpoint_connection").on("click", () => { 73 | console.log("cancel connection"); 74 | var current_connection = connections.get_current(); 75 | if (current_connection) { 76 | console.log("testing last connection used"); 77 | // test current connection with api-ping 78 | var endpoint = current_connection[0]; 79 | var api_key = current_connection[1]; 80 | api_check.ping(endpoint, api_key).then(function(response) { 81 | // test worked 82 | console.log("still working"); 83 | hideConnectionDialog(); 84 | require("app/statemachine").getToolStateMachine().connectionExists(); 85 | }).catch(function(error) { 86 | console.log("not working"); 87 | setConnectionAlert("There is a problem with this endpoint connection, please fix it"); 88 | require("app/statemachine").getToolStateMachine().noSavedConnection(); 89 | }); 90 | } else { 91 | setConnectionAlert("You must define at least one endpoint connection to continue") 92 | } 93 | return true; 94 | }); 95 | 96 | 97 | 98 | var setConnectionAlert = function(message) { 99 | var html = ``; 100 | $("#endpoint_connection_alert").replaceWith(html); 101 | }; 102 | 103 | var clearConnectionAlert = function() { 104 | var html = `
`; 105 | $("#endpoint_connection_alert").replaceWith(html); 106 | }; 107 | 108 | 109 | var showConnectionDialog = function() { 110 | updateConnectionDialog(); 111 | $("#configureEndpointModal").modal('show'); 112 | }; 113 | 114 | var hideConnectionDialog = function() { 115 | updateConnectionDialog(); 116 | $("#configureEndpointModal").modal('hide'); 117 | }; 118 | 119 | 120 | // only update if we have a connection 121 | if (connections.get_current() !== null) { 122 | updateConnectionDialog(); 123 | } 124 | 125 | 126 | // return the module object 127 | return { 128 | "showConnectionDialog": showConnectionDialog, 129 | "setConnectionAlert": setConnectionAlert, 130 | "clearConnectionAlert": clearConnectionAlert 131 | }; 132 | }); -------------------------------------------------------------------------------- /RuleBasedEncoding/website/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "bootstrap": { 6 | "version": "4.1.3", 7 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.3.tgz", 8 | "integrity": "sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==" 9 | }, 10 | "jquery": { 11 | "version": "3.3.1", 12 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", 13 | "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" 14 | }, 15 | "smartwizard": { 16 | "version": "4.3.1", 17 | "resolved": "https://registry.npmjs.org/smartwizard/-/smartwizard-4.3.1.tgz", 18 | "integrity": "sha512-C7gWnPwcLOi18kANTnCHYCMMo8LgZTcbMMXm6Q4bjUeY6ZaYGKXalaDjnTG6dSShEDmb50V6FeDHIZrXXiy0Hw==", 19 | "requires": { 20 | "bootstrap": "4.1.3", 21 | "jquery": "3.3.1" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/qb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 63 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/qb_view_rule.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 | 44 | 45 |
46 | 47 | 64 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/rules_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Video on Demand on AWS 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 27 | 28 | 29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 | Workflows 37 | Rules 38 | 39 |
40 | 44 | 46 |
47 | 48 |
49 | 54 |
55 | 57 |
58 |
59 |
60 |
61 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
NAMECREATE TIME
78 |
79 |
80 | 81 | 82 | 87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /RuleBasedEncoding/website/style.css: -------------------------------------------------------------------------------- 1 | .inputs { 2 | text-transform: capitalize; 3 | } 4 | /* 5 | pre { 6 | background: #000 !important; 7 | color: #fff !important; 8 | margin-top: 2rem; 9 | } */ 10 | 11 | input{ 12 | height:2.5em; 13 | line-height:2.5em; 14 | font-size:1em; 15 | margin: auto; 16 | margin-top: 2rem; 17 | width: 50%; 18 | } 19 | 20 | 21 | .header { 22 | background-color: #fff; 23 | } 24 | 25 | .header-thumbnail { 26 | margin-bottom: 5px; 27 | margin-top: 5px; 28 | } 29 | 30 | .center-header{ 31 | margin: auto; 32 | margin-top: 1rem; 33 | margin-bottom: 1rem; 34 | 35 | } 36 | 37 | 38 | .center-text{ 39 | margin: auto; 40 | margin-top: 2rem; 41 | } 42 | 43 | body{ 44 | font-family: 'Nunito', sans-serif; 45 | color: #384047; 46 | background-color:#f9fafc; 47 | } 48 | 49 | .page-container{ 50 | margin:20px; 51 | } 52 | 53 | h1{ 54 | font-size: 2em; 55 | margin: 30px 0; 56 | text-align: center; 57 | } 58 | 59 | html { 60 | font-family: sans-serif; 61 | -ms-text-size-adjust: 100%; 62 | -webkit-text-size-adjust: 100%; 63 | } 64 | 65 | .thumbnail{ 66 | margin: 10px; 67 | } 68 | 69 | .thumbnails{ 70 | width:100%; 71 | } 72 | 73 | 74 | 75 | 76 | /* For thumbnail overlays */ 77 | .img-container { 78 | position: relative; 79 | float: left; 80 | width: 300px; 81 | } 82 | 83 | .image { 84 | display: block; 85 | width: 100%; 86 | height: auto; 87 | } 88 | 89 | .overlay { 90 | position: absolute; 91 | bottom: 100%; 92 | left: 0; 93 | right: 0; 94 | /* background-color: #008CBA; */ 95 | background-color: #ff9900; 96 | overflow: hidden; 97 | width: 100%; 98 | height:0; 99 | transition: .5s ease; 100 | } 101 | 102 | .img-container:hover .overlay { 103 | bottom: 0; 104 | height: 100%; 105 | } 106 | 107 | .text { 108 | white-space: nowrap; 109 | color: white; 110 | font-size: 20px; 111 | position: absolute; 112 | overflow: hidden; 113 | top: 50%; 114 | left: 50%; 115 | transform: translate(-50%, -50%); 116 | -ms-transform: translate(-50%, -50%); 117 | } 118 | 119 | /* Used from W3Schools. Shows how to make a modal image popup */ 120 | /* Style the Image Used to Trigger the Modal */ 121 | 122 | #myImg:hover {opacity: 0.7;} */ 123 | 124 | /* The Modal (background) */ 125 | .modal { 126 | display: none; /* Hidden by default */ 127 | position: fixed; /* Stay in place */ 128 | z-index: 1; /* Sit on top */ 129 | padding-top: 100px; /* Location of the box */ 130 | left: 0; 131 | top: 0; 132 | width: 100%; /* Full width */ 133 | height: 100%; /* Full height */ 134 | overflow: auto; /* Enable scroll if needed */ 135 | background-color: rgb(0,0,0); /* Fallback color */ 136 | 137 | background-color: rgba(0,0,0,0.9); /* Black w/ opacity */ 138 | } 139 | 140 | /* Modal Content (Image) */ 141 | .modal-content { 142 | margin: auto; 143 | display: block; 144 | width: 200%; 145 | max-width: 100%; 146 | } 147 | 148 | /* Caption of Modal Image (Image Text) - Same Width as the Image */ 149 | #caption { 150 | margin: auto; 151 | display: block; 152 | width: 80%; 153 | max-width: 700px; 154 | text-align: center; 155 | color: #ccc; 156 | padding: 10px 0; 157 | height: 150px; 158 | } 159 | 160 | /* Add Animation - Zoom in the Modal */ 161 | .modal-content, #caption { 162 | -webkit-animation-name: zoom; 163 | -webkit-animation-duration: 0.6s; 164 | animation-name: zoom; 165 | animation-duration: 0.6s; 166 | } 167 | 168 | @-webkit-keyframes zoom { 169 | from {-webkit-transform:scale(0)} 170 | to {-webkit-transform:scale(1)} 171 | } 172 | 173 | @keyframes zoom { 174 | from {transform:scale(0)} 175 | to {transform:scale(1)} 176 | } 177 | 178 | /* The Close Button */ 179 | .close { 180 | position: absolute; 181 | top: 15px; 182 | right: 35px; 183 | color: #f1f1f1; 184 | font-size: 40px; 185 | font-weight: bold; 186 | transition: 0.3s; 187 | } 188 | 189 | .close:hover, 190 | .close:focus { 191 | color: #bbb; 192 | text-decoration: none; 193 | cursor: pointer; 194 | } 195 | 196 | /* 100% Image Width on Smaller Screens */ 197 | @media only screen and (max-width: 700px){ 198 | .modal-content { 199 | width: 100%; 200 | } 201 | } 202 | 203 | 204 | /* Edit mejs__container */ 205 | .mejs__container{ 206 | margin:auto; 207 | margin-top:2rem; 208 | margin-bottom:2rem; 209 | } 210 | 211 | .custom-top-button{ 212 | margin-top: 32px; 213 | } 214 | 215 | /* Container */ 216 | 217 | 218 | 219 | .btn-xlarge { 220 | padding: 18px 28px; 221 | font-size: 22px; 222 | line-height: normal; 223 | -webkit-border-radius: 8px; 224 | -moz-border-radius: 8px; 225 | border-radius: 8px; 226 | } 227 | 228 | .hiddenRow { 229 | padding: 0 !important; 230 | 231 | } 232 | 233 | .collapse-row.collapsed + tr { 234 | display: none; 235 | } 236 | 237 | .container { 238 | max-width: 2500px; 239 | margin-left: 20px; 240 | padding-right:50px; 241 | } 242 | 243 | .card { 244 | margin: 5px; 245 | font-size: 14px; 246 | font-family: sans-serif; 247 | } 248 | 249 | .col-3 { 250 | padding-left: 0; 251 | padding-right: 0; 252 | } 253 | 254 | .settings-card { 255 | height: 780px; 256 | overflow:scroll; 257 | overflow-x:hidden; 258 | } 259 | 260 | 261 | /* Exit Button */ 262 | .exit-button{ 263 | background-image:url(https://cdn0.iconfinder.com/data/icons/basic-ui-elements-plain/385/010_x-512.png); 264 | height:100px; 265 | width:100px; 266 | } 267 | 268 | .top-button{ 269 | font-size: 2rem; 270 | margin-bottom: .5rem; 271 | font-family: inherit; 272 | font-weight: 500; 273 | line-height: 1.2; 274 | color: inherit; 275 | margin-right: 10px; 276 | } 277 | 278 | /* Full screen modal */ 279 | @import url(https://fonts.googleapis.com/css?family=Roboto+Condensed:300,400); 280 | 281 | .font-roboto { 282 | font-family: 'roboto condensed'; 283 | } 284 | 285 | * { 286 | box-sizing: border-box; 287 | } 288 | 289 | .modal { 290 | position: fixed; 291 | top: 0; 292 | right: 0; 293 | bottom: 0; 294 | left: 0; 295 | overflow: hidden; 296 | } 297 | 298 | .modal-dialog { 299 | position: fixed; 300 | margin: 0; 301 | width: 100%; 302 | max-width: 100%; 303 | height: 100%; 304 | padding: 0; 305 | } 306 | 307 | .modal-content { 308 | position: absolute; 309 | top: 0; 310 | right: 0; 311 | bottom: 0; 312 | left: 0; 313 | border-radius: 0; 314 | box-shadow: none; 315 | } 316 | 317 | .modal-header { 318 | position: absolute; 319 | top: 0; 320 | right: 0; 321 | left: 0; 322 | height: 50px; 323 | padding: 10px; 324 | border: 0; 325 | } 326 | 327 | .modal-title { 328 | font-weight: 300; 329 | font-size: 2em; 330 | color: #fff; 331 | line-height: 30px; 332 | } 333 | 334 | .modal-body { 335 | position: absolute; 336 | top: 50px; 337 | bottom: 60px; 338 | width: 100%; 339 | font-weight: 300; 340 | overflow: auto; 341 | } 342 | 343 | .modal-footer { 344 | position: absolute; 345 | right: 0; 346 | bottom: 0; 347 | left: 0; 348 | height: 60px; 349 | padding: 10px; 350 | background: #f1f3f5; 351 | } 352 | 353 | ::-webkit-scrollbar { 354 | -webkit-appearance: none; 355 | width: 10px; 356 | background: #f1f3f5; 357 | border-left: 1px solid darken(#f1f3f5, 10%); 358 | } 359 | 360 | ::-webkit-scrollbar-thumb { 361 | background: darken(#f1f3f5, 20%); 362 | } 363 | 364 | .center { 365 | display: block; 366 | margin-left: auto; 367 | margin-right: auto; 368 | width: 50%; 369 | } 370 | -------------------------------------------------------------------------------- /RuleBasedEncoding/workshop.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | 4 | Description: 5 | Creates a static website and backend AWS services for the AWS Elemental Media Services VOD on AWS workshop 6 | 7 | Parameters: 8 | vodstack: 9 | Description: VOD on AWS stack to use 10 | Type: String 11 | Default: reinvent-vod 12 | 13 | Resources: 14 | 15 | website: 16 | Type: "AWS::CloudFormation::Stack" 17 | Properties: 18 | Parameters: 19 | WebsiteBucketName: !Sub "${AWS::StackName}-websitebucket" 20 | TemplateURL: 21 | Fn::Sub: https://s3.amazonaws.com/rodeolabz-${AWS::Region}/rules/3-rulesbasedencoding/v3/ruleweb.yaml 22 | TimeoutInMinutes: 60 23 | api: 24 | Type: "AWS::CloudFormation::Stack" 25 | Properties: 26 | Parameters: 27 | DynamoDbTable: 28 | Fn::ImportValue: 29 | !Sub "${vodstack}:DynamoDBTable" 30 | RulesTable: !GetAtt website.Outputs.RulesTableName 31 | Source: 32 | Fn::ImportValue: 33 | !Sub "${vodstack}:Source" 34 | TemplateURL: 35 | Fn::Sub: https://s3.amazonaws.com/rodeolabz-${AWS::Region}/rules/3-rulesbasedencoding/v3/ruleapi_cfn.yaml 36 | TimeoutInMinutes: 60 37 | keys: 38 | Type: "AWS::CloudFormation::Stack" 39 | Properties: 40 | Parameters: 41 | ApiId: !GetAtt api.Outputs.RestAPIId 42 | TemplateURL: 43 | Fn::Sub: https://s3.amazonaws.com/rodeolabz-${AWS::Region}/rules/3-rulesbasedencoding/v3/keys.yaml 44 | TimeoutInMinutes: 60 45 | 46 | Outputs: 47 | 48 | APIHandlerArn: 49 | Description: API Handler Lambda 50 | Value: !GetAtt api.Outputs.APIHandlerArn 51 | 52 | APIHandlerName: 53 | Description: API Handler ARN 54 | Value: !GetAtt api.Outputs.APIHandlerName 55 | 56 | APIEndpointURL: 57 | Description: Rules and Workflow API endpoint 58 | Value: !GetAtt api.Outputs.EndpointURL 59 | 60 | APIKey: 61 | Description: API Key for testing 62 | Value: !GetAtt keys.Outputs.ApiKey 63 | 64 | RulesTableName: 65 | Description: Dynamodb Table for Rules API 66 | Value: !GetAtt website.Outputs.RulesTableName 67 | 68 | WebsiteBucket: 69 | Description: Bucket used for static website 70 | Value: !GetAtt website.Outputs.WebsiteBucket 71 | 72 | WebsiteURL: 73 | Description: Workflow Monitoring page and Rule creation 74 | Value: !GetAtt website.Outputs.WebsiteURL 75 | 76 | WorkflowDynamoDBTable: 77 | Description: DynamoDB Table for VOD Workflow 78 | Value: 79 | Fn::ImportValue: 80 | !Sub "${vodstack}:DynamoDBTable" 81 | 82 | WorkflowSource: 83 | Description: Source Bucket 84 | Value: 85 | Fn::ImportValue: 86 | !Sub "${vodstack}:Source" 87 | 88 | WorkflowDestination: 89 | Description: Destination Bucket 90 | Value: 91 | Fn::ImportValue: 92 | !Sub "${vodstack}:Destination" 93 | 94 | WorkflowCloudFront: 95 | Description: CloudFront Domain Name 96 | Value: 97 | Fn::ImportValue: 98 | !Sub "${vodstack}:CloudFront" 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /images/RulesServerlessApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/RulesServerlessApp.png -------------------------------------------------------------------------------- /images/ServerlessWebApp-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/ServerlessWebApp-new.png -------------------------------------------------------------------------------- /images/ServerlessWebApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/ServerlessWebApp.png -------------------------------------------------------------------------------- /images/TestRules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/TestRules.png -------------------------------------------------------------------------------- /images/WorkflowServerlessApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/WorkflowServerlessApp.png -------------------------------------------------------------------------------- /images/api-RestAPI-resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-RestAPI-resource.png -------------------------------------------------------------------------------- /images/api-gateway-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-gateway-key.png -------------------------------------------------------------------------------- /images/api-gateway-resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-gateway-resources.png -------------------------------------------------------------------------------- /images/api-gateway-test-rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-gateway-test-rules.png -------------------------------------------------------------------------------- /images/api-gateway-test-vodonaws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-gateway-test-vodonaws.png -------------------------------------------------------------------------------- /images/api-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-hello.png -------------------------------------------------------------------------------- /images/api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-key.png -------------------------------------------------------------------------------- /images/api-rules-forbidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-rules-forbidden.png -------------------------------------------------------------------------------- /images/api-usage-plan-stage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-usage-plan-stage.png -------------------------------------------------------------------------------- /images/api-vodonaws-forbidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/api-vodonaws-forbidden.png -------------------------------------------------------------------------------- /images/app-rule-execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/app-rule-execution.png -------------------------------------------------------------------------------- /images/app-sample-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/app-sample-rule.png -------------------------------------------------------------------------------- /images/cfn-changesets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/cfn-changesets.png -------------------------------------------------------------------------------- /images/cfn-ruleapi-outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/cfn-ruleapi-outputs.png -------------------------------------------------------------------------------- /images/cfn-rules-outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/cfn-rules-outputs.png -------------------------------------------------------------------------------- /images/cfn-ruleweb-outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/cfn-ruleweb-outputs.png -------------------------------------------------------------------------------- /images/create-new-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/create-new-rule.png -------------------------------------------------------------------------------- /images/forbidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/forbidden.png -------------------------------------------------------------------------------- /images/lambda-find-mediainfoRulesProfiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/lambda-find-mediainfoRulesProfiler.png -------------------------------------------------------------------------------- /images/mediaconvert-create-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/mediaconvert-create-template.png -------------------------------------------------------------------------------- /images/mediainfoBusinessRulesLambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/mediainfoBusinessRulesLambda.png -------------------------------------------------------------------------------- /images/metadata-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/metadata-test.png -------------------------------------------------------------------------------- /images/metadata-watchfolder-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/metadata-watchfolder-new.png -------------------------------------------------------------------------------- /images/metadata-watchfolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/metadata-watchfolder.png -------------------------------------------------------------------------------- /images/process-state-machine-definition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/process-state-machine-definition.png -------------------------------------------------------------------------------- /images/process-step-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/process-step-function.png -------------------------------------------------------------------------------- /images/profiler-step-inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/profiler-step-inputs.png -------------------------------------------------------------------------------- /images/profiler-step-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/profiler-step-output.png -------------------------------------------------------------------------------- /images/profiler-step-outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/profiler-step-outputs.png -------------------------------------------------------------------------------- /images/ruleContainerEqMxf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/ruleContainerEqMxf.png -------------------------------------------------------------------------------- /images/s3-events-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/s3-events-new.png -------------------------------------------------------------------------------- /images/s3-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/s3-events.png -------------------------------------------------------------------------------- /images/s3-json-event-trigger-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/s3-json-event-trigger-new.png -------------------------------------------------------------------------------- /images/s3-json-event-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/s3-json-event-trigger.png -------------------------------------------------------------------------------- /images/step-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/step-hello.png -------------------------------------------------------------------------------- /images/step-replace-profiler-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/step-replace-profiler-new.png -------------------------------------------------------------------------------- /images/step-replace-profiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/step-replace-profiler.png -------------------------------------------------------------------------------- /images/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/success.png -------------------------------------------------------------------------------- /images/vodonaws-changes-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/vodonaws-changes-new.png -------------------------------------------------------------------------------- /images/vodonaws-changes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/vodonaws-changes.png -------------------------------------------------------------------------------- /images/web-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/web-setup.png -------------------------------------------------------------------------------- /images/web-workflow-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/web-workflow-details.png -------------------------------------------------------------------------------- /images/web-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-media-services-workflow-composer/36f0a19b6cd787f2d365cc82c21c359aacb403ac/images/web-workflow.png --------------------------------------------------------------------------------