├── workflow ├── host.json ├── check-media-asset │ ├── function.json │ ├── project.json │ └── run.csx ├── monitor-copy-blob │ ├── function.json │ └── run.csx ├── monitor-media-job │ ├── function.json │ ├── project.json │ └── run.csx ├── submit-media-job │ ├── function.json │ ├── project.json │ └── run.csx ├── Presets │ ├── Indexer-v2.json │ └── encodingmbr.json ├── add-file-to-mediaasset │ ├── function.json │ ├── project.json │ └── run.csx ├── create-empty-mediaasset │ ├── function.json │ ├── project.json │ └── run.csx ├── translate-webvtt-mediaasset │ ├── function.json │ ├── project.json │ └── run.csx ├── import-mediaasset-singleblob │ ├── function.json │ ├── project.json │ └── run.csx ├── import-mediaasset-singleblob-trigger │ ├── function.json │ ├── project.json │ └── run.csx ├── publish-assets │ ├── project.json │ ├── function.json │ └── run.csx └── wevbtt-azsearch-indexer │ ├── function.json │ └── run.py ├── portal ├── VERSION ├── .gitignore ├── api │ ├── azure-documentdb-php-sdk │ │ ├── vendor │ │ │ ├── composer │ │ │ │ ├── installed.json │ │ │ │ ├── autoload_classmap.php │ │ │ │ ├── autoload_namespaces.php │ │ │ │ ├── autoload_psr4.php │ │ │ │ ├── autoload_static.php │ │ │ │ ├── LICENSE │ │ │ │ ├── autoload_real.php │ │ │ │ └── ClassLoader.php │ │ │ └── autoload.php │ │ ├── .gitignore │ │ └── src │ │ │ ├── Verbs.php │ │ │ ├── Contracts │ │ │ ├── ClientInterface.php │ │ │ └── ResourceInterface.php │ │ │ ├── Resources │ │ │ ├── BaseResource.php │ │ │ ├── Database.php │ │ │ ├── Collection.php │ │ │ └── Document.php │ │ │ └── Client.php │ ├── .gitignore │ ├── azuresearch-caption-api.php │ ├── azuresearch-content-api.php │ └── service-init-api.php ├── img │ ├── azure_logo.png │ ├── docker_logo.png │ └── linux_penguin_logo.png ├── .htaccess ├── .scripts │ ├── local-run.sh │ └── build.sh ├── sshd_config ├── tmpl │ ├── init_button.php │ ├── content_search_form.php │ └── content_list.php ├── build.sh ├── config.php ├── init_container.sh ├── js │ ├── topsearch.js │ ├── initservice.js │ └── captionsearch.js ├── Dockerfile ├── css │ ├── design-index.css │ └── design.css ├── index.php ├── apache2.conf └── video.php ├── _config.yml ├── docs └── images │ ├── Setup.png │ ├── Step2.png │ ├── Step5.png │ ├── Step6.png │ ├── Step9.png │ ├── Step10.png │ ├── Step3-01.png │ ├── Step3-02.png │ └── Step3-03.png ├── azuredeploy-portal.parameters.json ├── azuredeploy-cosmosdb.parameters.json ├── azuredeploy-azuresearch.parameters.json ├── azuredeploy-cosmosdb.json ├── LICENSE ├── azuredeploy-functions.parameters.json ├── azuredeploy-azuresearch.json ├── azuredeploy-portal.json ├── README.md └── azuredeploy-functions.json /workflow/host.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /portal/VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /portal/.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | composer.lock 3 | -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | composer.lock 3 | -------------------------------------------------------------------------------- /docs/images/Setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Setup.png -------------------------------------------------------------------------------- /docs/images/Step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step2.png -------------------------------------------------------------------------------- /docs/images/Step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step5.png -------------------------------------------------------------------------------- /docs/images/Step6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step6.png -------------------------------------------------------------------------------- /docs/images/Step9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step9.png -------------------------------------------------------------------------------- /portal/api/.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | /dev-test-files 4 | composer.phar 5 | composer.lock 6 | -------------------------------------------------------------------------------- /docs/images/Step10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step10.png -------------------------------------------------------------------------------- /docs/images/Step3-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step3-01.png -------------------------------------------------------------------------------- /docs/images/Step3-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step3-02.png -------------------------------------------------------------------------------- /docs/images/Step3-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/docs/images/Step3-03.png -------------------------------------------------------------------------------- /portal/img/azure_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/portal/img/azure_logo.png -------------------------------------------------------------------------------- /portal/img/docker_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/portal/img/docker_logo.png -------------------------------------------------------------------------------- /portal/img/linux_penguin_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tikyau/ai-digitalmedia/master/portal/img/linux_penguin_logo.png -------------------------------------------------------------------------------- /portal/.htaccess: -------------------------------------------------------------------------------- 1 | ### Re-write module 2 | RewriteEngine on 3 | RewriteCond %{REQUEST_FILENAME} !^/(css|js|api) 4 | RewriteRule ^/?$ index.php [L] 5 | RewriteRule ^video/(.*)/?$ video.php?cid=$1 [L] 6 | -------------------------------------------------------------------------------- /azuredeploy-portal.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema":"http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion":"1.0.0.0", 4 | "parameters":{ 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/vendor/autoload.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /workflow/check-media-asset/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /workflow/monitor-copy-blob/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /workflow/monitor-media-job/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /workflow/submit-media-job/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /workflow/Presets/Indexer-v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "1.0", 3 | "Features": [ 4 | { 5 | "Options": { 6 | "Formats": [ 7 | "Sami", 8 | "WebVtt", 9 | "TTML" 10 | ], 11 | "Language": "EnUs", 12 | "Type": "RecoOptions" 13 | }, 14 | "Type": "SpReco" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /workflow/add-file-to-mediaasset/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /workflow/create-empty-mediaasset/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /workflow/translate-webvtt-mediaasset/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /workflow/import-mediaasset-singleblob/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | } 14 | ], 15 | "disabled": false 16 | } -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Verbs.php: -------------------------------------------------------------------------------- 1 | .documents.azure.com:443/" \ 5 | -e CosmosdbMasterKey="" \ 6 | -e AzureSearchServiceName="" \ 7 | -e AzureSearchApiKey="" \ 8 | -p 8080:8080 -p 2222:2222 -it ai-digitalmedia-portal:0.1.0 9 | 10 | -------------------------------------------------------------------------------- /portal/sshd_config: -------------------------------------------------------------------------------- 1 | # This is ssh server systemwide configuration file. 2 | # 3 | # /etc/sshd_config 4 | 5 | Port 2222 6 | ListenAddress 0.0.0.0 7 | LoginGraceTime 180 8 | X11Forwarding yes 9 | Ciphers aes128-cbc,3des-cbc,aes256-cbc 10 | MACs hmac-sha1,hmac-sha1-96 11 | StrictModes yes 12 | SyslogFacility DAEMON 13 | PasswordAuthentication yes 14 | PermitEmptyPasswords no 15 | PermitRootLogin yes 16 | -------------------------------------------------------------------------------- /workflow/import-mediaasset-singleblob-trigger/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "name": "myBlob", 5 | "type": "blobTrigger", 6 | "direction": "in", 7 | "path": "mediaimports/{name}.mp4", 8 | "connection": "gbbdemostorage_STORAGE" 9 | }, 10 | { 11 | "type": "http", 12 | "name": "res", 13 | "direction": "out" 14 | } 15 | ], 16 | "disabled": true 17 | } -------------------------------------------------------------------------------- /portal/tmpl/init_button.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Action Required

6 |

Press the button to start the service

7 |
8 | 9 |
10 |
11 |
12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /portal/tmpl/content_search_form.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /workflow/check-media-asset/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /workflow/monitor-media-job/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /workflow/publish-assets/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /workflow/submit-media-job/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /workflow/add-file-to-mediaasset/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /workflow/create-empty-mediaasset/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /workflow/import-mediaasset-singleblob/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /workflow/translate-webvtt-mediaasset/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /workflow/import-mediaasset-singleblob-trigger/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "frameworks": { 3 | "net46": { 4 | "dependencies": { 5 | "windowsazure.mediaservices": "4.0.0.4", 6 | "windowsazure.mediaservices.extensions": "4.0.0.4", 7 | "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1", 8 | "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /portal/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x -e 3 | 4 | if [ $# -ne 3 ] 5 | then 6 | echo "$0 [username] [dockerhub account] [dockerhub password]" 7 | exit 8 | fi 9 | echo "username=$1" 10 | echo "docker account name=$2" 11 | echo "docker account passwd=$3" 12 | 13 | docker login -u "$2" -p "$3" 14 | 15 | version=`cat ./VERSION` 16 | tag="$version" 17 | echo "tag=$tag" 18 | docker build -t "$1"/gbbdemoportal:$tag . 19 | docker push "$1"/gbbdemoportal:$tag 20 | 21 | docker logout 22 | -------------------------------------------------------------------------------- /azuredeploy-azuresearch.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "name": { 6 | "value": "GEN-UNIQUE" 7 | }, 8 | "sku": { 9 | "value": "standard" 10 | }, 11 | "replicaCount": { 12 | "value": 1 13 | }, 14 | "partitionCount": { 15 | "value": 1 16 | }, 17 | "hostingMode": { 18 | "value": "default" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /portal/config.php: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /portal/.scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x -e 3 | 4 | cwd=`dirname "$0"` 5 | expr "$0" : "/.*" > /dev/null || cwd=`(cd "$cwd" && pwd)` 6 | 7 | if [ $# -ne 3 ] 8 | then 9 | echo "$0 [username] [dockerhub account] [dockerhub password]" 10 | exit 11 | fi 12 | echo "username=$1" 13 | echo "docker account name=$2" 14 | echo "docker account passwd=$3" 15 | 16 | docker login -u "$2" -p "$3" 17 | 18 | version=`cat $cwd/../VERSION` 19 | tag="$version" 20 | echo "tag=$tag" 21 | docker build -t "$1"/ai-digitalmedia-portal:$tag $cwd/.. 22 | docker push "$1"/ai-digitalmedia-portal:$tag 23 | 24 | docker logout 25 | -------------------------------------------------------------------------------- /workflow/wevbtt-azsearch-indexer/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "name": "inputMessage", 5 | "type": "queueTrigger", 6 | "direction": "in", 7 | "queueName": "azsearch-feeding-queuetrigger-gbbdemofunc", 8 | "connection": "IndexFeed_Storage" 9 | }, 10 | { 11 | "type": "documentDB", 12 | "name": "inputDocument", 13 | "databaseName": "asset", 14 | "collectionName": "meta", 15 | "sqlQuery": "SELECT * from c where c.id = {queueTrigger}", 16 | "connection": "CosmosDB_Connection", 17 | "direction": "in" 18 | } 19 | ], 20 | "disabled": false 21 | } 22 | -------------------------------------------------------------------------------- /portal/init_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | service ssh start 3 | sed -i "s/error.log/error_$WEBSITE_ROLE_INSTANCE_ID.log/g" /etc/apache2/apache2.conf 4 | sed -i "s/access.log/access_$WEBSITE_ROLE_INSTANCE_ID.log/g" /etc/apache2/apache2.conf 5 | sed -i "s/{PORT}/$PORT/g" /etc/apache2/apache2.conf 6 | 7 | [ ! -d /var/lock/apache2 ] && mkdir /var/lock/apache2 8 | [ ! -d /var/run/apache2 ] && mkdir /var/run/apache2 9 | [ ! -d /var/log/apache2 ] && mkdir /var/log/apache2 10 | 11 | touch /var/log/apache2/access_$WEBSITE_ROLE_INSTANCE_ID.log 12 | echo "$(date) Container started" >> /var/log/apache2/access_$WEBSITE_ROLE_INSTANCE_ID.log 13 | /usr/sbin/apache2ctl -D FOREGROUND 14 | -------------------------------------------------------------------------------- /workflow/publish-assets/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "type": "httpTrigger", 5 | "direction": "in", 6 | "webHookType": "genericJson", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "res" 13 | }, 14 | { 15 | "type": "documentDB", 16 | "name": "outputDocument", 17 | "databaseName": "asset", 18 | "collectionName": "meta", 19 | "createIfNotExists": true, 20 | "connection": "CosmosDB_Connection", 21 | "direction": "out" 22 | }, 23 | { 24 | "type": "queue", 25 | "name": "outputQueueItem", 26 | "queueName": "azsearch-feeding-queuetrigger-gbbdemofunc", 27 | "connection": "IndexFeed_Storage", 28 | "direction": "out" 29 | } 30 | ], 31 | "disabled": false 32 | } 33 | -------------------------------------------------------------------------------- /portal/tmpl/content_list.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
", 13 | $content_id, 14 | $thumbnail_url, 15 | $name); 16 | echo $s; 17 | $content_index++; 18 | } 19 | ?> 20 |
21 | 22 | -------------------------------------------------------------------------------- /portal/js/topsearch.js: -------------------------------------------------------------------------------- 1 | 2 | function execTopSearch() 3 | { 4 | var keyword = $("#q").val(); 5 | var searchAPI = "/api/azuresearch-content-api.php"; 6 | if (keyword != '' ) { 7 | searchAPI = searchAPI + "?search=" + encodeURIComponent(keyword); 8 | } 9 | 10 | $.ajax({ 11 | url: searchAPI, 12 | type: "GET", 13 | success: function (data) { 14 | $( "#mediaContainer" ).html(''); 15 | var caption_counter = 0; 16 | for (var item in data.value) 17 | { 18 | var content_id = data.value[item].content_id; 19 | var name = data.value[item].name; 20 | var thumbnail_url = data.value[item].thumbnail_url; 21 | $( "#mediaContainer" ).append( '' ); 22 | } 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /azuredeploy-cosmosdb.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "databaseAccountName": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "The DocumentDB database account name." 9 | } 10 | } 11 | }, 12 | "variables": { 13 | "offerType": "Standard" 14 | }, 15 | "resources": [ 16 | { 17 | "apiVersion": "2015-04-08", 18 | "type": "Microsoft.DocumentDB/databaseAccounts", 19 | "name": "[parameters('databaseAccountName')]", 20 | "location": "[resourceGroup().location]", 21 | "properties": { 22 | "name": "[parameters('databaseAccountName')]", 23 | "databaseAccountOfferType": "[variables('offerType')]", 24 | "locations": [ 25 | { 26 | "locationName": "[resourceGroup().location]", 27 | "failoverPriority": 0 28 | } 29 | ] 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Contracts/ClientInterface.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'DreamFactory\\DocumentDb\\' => 24, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'DreamFactory\\DocumentDb\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/src', 20 | ), 21 | ); 22 | 23 | public static function getInitializer(ClassLoader $loader) 24 | { 25 | return \Closure::bind(function () use ($loader) { 26 | $loader->prefixLengthsPsr4 = ComposerStaticInitc3709ed9d93a5e5f544421fa368a1613::$prefixLengthsPsr4; 27 | $loader->prefixDirsPsr4 = ComposerStaticInitc3709ed9d93a5e5f544421fa368a1613::$prefixDirsPsr4; 28 | 29 | }, null, ClassLoader::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /portal/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-apache 2 | MAINTAINER Yoichi Kawasaki 3 | 4 | RUN apt-get update && \ 5 | apt-get install -y php5-mysql && \ 6 | apt-get clean 7 | 8 | COPY apache2.conf /bin/ 9 | COPY init_container.sh /bin/ 10 | COPY . /var/www/html/ 11 | 12 | RUN a2enmod rewrite expires include 13 | 14 | # install the PHP extensions we need 15 | RUN apt update \ 16 | && apt install -y --no-install-recommends \ 17 | openssh-server \ 18 | && rm -f /var/log/apache2/* \ 19 | && rmdir /var/lock/apache2 \ 20 | && rmdir /var/run/apache2 \ 21 | && rmdir /var/log/apache2 \ 22 | && chmod 777 /var/log \ 23 | && chmod 777 /var/run \ 24 | && chmod 777 /var/lock \ 25 | && chmod 777 /bin/init_container.sh \ 26 | && echo "root:Docker!" | chpasswd \ 27 | && cp /bin/apache2.conf /etc/apache2/apache2.conf 28 | 29 | COPY sshd_config /etc/ssh/ 30 | 31 | EXPOSE 8080 2222 32 | 33 | ENV PORT 8080 34 | ENV WEBSITE_ROLE_INSTANCE_ID localRoleInstance 35 | ENV WEBSITE_INSTANCE_ID localInstance 36 | 37 | WORKDIR /var/www/html 38 | ENTRYPOINT ["/bin/init_container.sh"] 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Shige 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Contracts/ResourceInterface.php: -------------------------------------------------------------------------------- 1 | ×Error!: Service initialization failure! Please try again later"); 17 | request_ok = false; 18 | } else { 19 | $( "#infomsg" ).html(''); 20 | $( "#infomsg" ).append("
×Success!: Now you're ready for processing video contents. Continue
"); 21 | $( "#buttondiv" ).html(''); 22 | } 23 | }, 24 | error: function(xhr, textStatus, errorThrown) { 25 | request_ok = false; 26 | $( "#infomsg" ).html(''); 27 | $( "#infomsg" ).append("
×Error!: Service initialization failure! Please try again later
"); 28 | } 29 | }); 30 | return request_ok; 31 | } 32 | -------------------------------------------------------------------------------- /portal/css/design-index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px; 3 | } 4 | 5 | .starter-template { 6 | padding: 30px 15px; 7 | text-align: center; 8 | } 9 | 10 | .colmask { 11 | clear:both; 12 | float:left; 13 | width:100%; /* width of whole page */ 14 | overflow:hidden; /* This chops off any overhanging divs */ 15 | } 16 | 17 | /* common column settings */ 18 | .colleft { 19 | float:left; 20 | width:100%; 21 | position:relative; 22 | } 23 | 24 | .col, 25 | .col1, 26 | .col2 { 27 | float:left; 28 | position:relative; 29 | padding:0 0 0 0; 30 | overflow:hidden; 31 | } 32 | 33 | .scrollbox { 34 | padding: 0 0 10px 10px; 35 | height: 600px; 36 | overflow-y: scroll; 37 | /*background-color: #fef3b9;*/ 38 | /*background-color: #feefa0;*/ 39 | /*background-color: #e3e46e;*/ 40 | /* background-color: #DCDCDC; */ 41 | } 42 | 43 | /* 2 Column (double page) settings */ 44 | .doublepage { 45 | background:#fff; /* right column background colour */ 46 | } 47 | .doublepage .colleft { 48 | right:48%; /* right column width */ 49 | background:#fff; /* left column background colour */ 50 | } 51 | .doublepage .col1 { 52 | width:45%; /* left column content width (column width minus left and right padding) */ 53 | left:52%; /* right column width plus left column left padding */ 54 | } 55 | .doublepage .col2 { 56 | width: 50%; /* right column content width (column width minus left and right padding) */ 57 | left:52%; /* (right column width) plus (left column left and right padding) plus (right column left padding) */ 58 | } 59 | 60 | video::-webkit-media-text-track-display { 61 | font-size: 50%; 62 | } 63 | -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInitc3709ed9d93a5e5f544421fa368a1613::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /portal/api/azuresearch-caption-api.php: -------------------------------------------------------------------------------- 1 | $value) { 14 | $params[$name] = $value; 15 | } 16 | } 17 | if (!array_key_exists('content_id', $params)) { 18 | print "Error!"; 19 | exit(1); 20 | } 21 | 22 | $lang='en'; 23 | if (array_key_exists('lang', $params)) { 24 | $lang=$params['lang']; 25 | } 26 | 27 | $AZURESEARCH_URL_BASE= sprintf( "https://%s.search.windows.net/indexes/%s-%s/docs", 28 | $azsearch_service_name, $azsearch_index_name, strtolower($lang)); 29 | 30 | $url = $AZURESEARCH_URL_BASE . '?' 31 | . '$top=1000&$select=id,content_id,begin_sec,begin_str,end_str,caption_text' 32 | . '&$count=true&$orderby=begin_sec&highlight=caption_text&api-version=' . $azsearch_api_version 33 | . '&$filter=content_id%20eq%20%27' . $params['content_id'] . '%27'; 34 | if (array_key_exists('search', $params)) { 35 | $url = $url . '&search=' . urlencode($params['search']); 36 | } 37 | 38 | $opts = array( 39 | 'http'=>array( 40 | 'method'=>"GET", 41 | 'header'=>"Accept: application/json\r\n" . 42 | "api-key: $azsearch_api_key\r\n", 43 | 'timeout' =>10 44 | ) 45 | ); 46 | 47 | $context = stream_context_create($opts); 48 | $data = file_get_contents($url, false, $context); 49 | 50 | if ($data === false) { 51 | print "Error!"; 52 | exit(1); 53 | } 54 | else 55 | { 56 | header('Content-Length: '.strlen($data)); 57 | header('Content-Type: application/json; odata.metadata=minimal'); 58 | header('Access-Control-Allow-Origin: *'); 59 | print $data; 60 | } 61 | ?> 62 | -------------------------------------------------------------------------------- /portal/css/design.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 30px; 3 | } 4 | 5 | .starter-template { 6 | padding: 30px 15px; 7 | text-align: center; 8 | } 9 | 10 | .topmenu { 11 | font-size: 120%; 12 | } 13 | 14 | textarea { 15 | width: 95%; 16 | height: 70px; 17 | } 18 | 19 | td.bgcolor-odd { 20 | background-color: #f5f5f5; 21 | } 22 | td.bgcolor-even { 23 | background-color: #e8e8e8; 24 | } 25 | 26 | td.timecol { 27 | width: 160px; 28 | padding: 10px 2px 10px 10px; 29 | font-size: 100%; 30 | font-weight: bold; 31 | } 32 | 33 | td.clickme { 34 | width: 500px; 35 | padding: 10px 10px 10px 2px; 36 | font-size: 100%; 37 | font-weight: bold; 38 | } 39 | 40 | .colmask { 41 | clear:both; 42 | float:left; 43 | width:100%; /* width of whole page */ 44 | overflow:hidden; /* This chops off any overhanging divs */ 45 | } 46 | 47 | /* common column settings */ 48 | .colleft { 49 | float:left; 50 | width:100%; 51 | position:relative; 52 | } 53 | 54 | .col, 55 | .col1, 56 | .col2 { 57 | float:left; 58 | position:relative; 59 | padding:0 0 0 0; 60 | overflow:hidden; 61 | } 62 | 63 | .scrollbox { 64 | /*padding: 0 0 10px 10px;*/ 65 | height: 650px; 66 | overflow-y: scroll; 67 | /*background-color: #fef3b9;*/ 68 | /*background-color: #feefa0;*/ 69 | /*background-color: #e3e46e;*/ 70 | /*background-color: #DCDCDC;*/ 71 | } 72 | 73 | /* 2 Column (double page) settings */ 74 | .doublepage { 75 | background:#fff; /* right column background colour */ 76 | } 77 | .doublepage .colleft { 78 | right:48%; /* right column width */ 79 | background:#fff; /* left column background colour */ 80 | } 81 | .doublepage .col1 { 82 | width:45%; /* left column content width (column width minus left and right padding) */ 83 | left:51%; /* right column width plus left column left padding */ 84 | } 85 | .doublepage .col2 { 86 | width: 50%; /* right column content width (column width minus left and right padding) */ 87 | left:52%; /* (right column width) plus (left column left and right padding) plus (right column left padding) */ 88 | } 89 | 90 | video::-webkit-media-text-track-display { 91 | font-size: 20%; 92 | } 93 | 94 | em { 95 | background:yellow; 96 | font-size: 100%; 97 | font-style: normal; 98 | } 99 | -------------------------------------------------------------------------------- /portal/js/captionsearch.js: -------------------------------------------------------------------------------- 1 | 2 | function substr_yymmdd(s) { 3 | return (s.length>8) ? s.substring(0, 8) : s; 4 | } 5 | 6 | function execSearch(content_id, lang) { 7 | var keyword = $("#q").val(); 8 | var lang = $("#lang").val(); 9 | execCustomParamsSearch(content_id, lang, keyword); 10 | } 11 | 12 | //function execSearch(content_id, lang) 13 | function execCustomParamsSearch(content_id, lang, keyword) 14 | { 15 | // assigned given lang value to #lang 16 | $("#lang").val(lang); 17 | $("#q").val(keyword); 18 | 19 | var searchAPI = "/api/azuresearch-caption-api.php?content_id=" + content_id + "&lang=" + lang; 20 | if (keyword != '' ) { 21 | searchAPI = searchAPI + "&search=" + encodeURIComponent(keyword); 22 | } 23 | 24 | $.ajax({ 25 | url: searchAPI, 26 | type: "GET", 27 | success: function (data) { 28 | $( "#colcontainer2" ).html(''); 29 | $( "#colcontainer2" ).append(''); 30 | var caption_counter = 0; 31 | for (var item in data.value) 32 | { 33 | var caption_id = data.value[item].id; 34 | var caption_text = data.value[item].caption_text; 35 | if ( '@search.highlights' in data.value[item] ){ 36 | caption_text = data.value[item]['@search.highlights'].caption_text; 37 | } 38 | var begin_sec = data.value[item].begin_sec; 39 | var begin_str = data.value[item].begin_str; 40 | begin_str = substr_yymmdd(begin_str); 41 | var end_str = data.value[item].end_str; 42 | end_str = substr_yymmdd(end_str); 43 | var bgcolor_class = ( caption_counter % 2 ==0 ) ? "bgcolor-odd" : "bgcolor-even"; 44 | $( "#colcontainer2" ).append( 45 | "\n"); 46 | $( "#colcontainer2" ).append( 47 | "\n"); 49 | $( "#colcontainer2" ).append( 50 | "\n"); 51 | $( "#colcontainer2" ).append("\n"); 52 | caption_counter++; 53 | } 54 | $( "#colcontainer2" ).append('
[" 48 | + begin_str + " - " + end_str + "]" + caption_text + "
'); 55 | } 56 | }); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Resources/BaseResource.php: -------------------------------------------------------------------------------- 1 | client = $client; 36 | $this->resourceType = $resourceType; 37 | $this->resourceId = $resourceId; 38 | } 39 | 40 | /** 41 | * Set any additional headers. 42 | * 43 | * @param array $headers Array of headers 44 | */ 45 | public function setHeaders(array $headers) 46 | { 47 | $this->headers = $headers; 48 | } 49 | 50 | /** 51 | * Determines the resource id to use. 52 | * 53 | * @param string|null $id Resource id 54 | * 55 | * @return string|null Resource id 56 | * @throws \Exception 57 | */ 58 | protected function getId($id = null) 59 | { 60 | $id = (empty($id)) ? $this->resourceId : $id; 61 | if (empty($id)) { 62 | throw new \Exception('Invalid id supplied [' . $id . ']. Operation requires valid resource id.'); 63 | } 64 | 65 | return $id; 66 | } 67 | 68 | /** 69 | * Makes the request for resources 70 | * 71 | * @param string $verb Request Method (HEAD, GET, POST, PUT, DELETE) 72 | * @param string $path Requested resource path 73 | * @param string $resource Requested resource 74 | * @param array $payload Posted data 75 | * 76 | * @return array 77 | */ 78 | protected function request($verb, $path, $resource = '', array $payload = []) 79 | { 80 | return $this->client->request($verb, $path, $this->resourceType, $resource, $payload, $this->headers); 81 | } 82 | } -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Resources/Database.php: -------------------------------------------------------------------------------- 1 | request(Verbs::GET, '/' . static::TYPE); 36 | 37 | return $result; 38 | } 39 | 40 | /** {@inheritdoc} */ 41 | public function get($id = null) 42 | { 43 | $id = $this->getId($id); 44 | $path = '/' . static::TYPE . '/' . $id; 45 | $resource = static::TYPE . '/' . $id; 46 | $result = $this->request(Verbs::GET, $path, $resource); 47 | 48 | return $result; 49 | } 50 | 51 | /** {@inheritdoc} */ 52 | public function create(array $data) 53 | { 54 | if (!isset($data['id'])) { 55 | throw new \InvalidArgumentException('No id found in data. Id is required to create a Database.'); 56 | } 57 | $path = '/' . static::TYPE; 58 | $result = $this->request(Verbs::POST, $path, '', $data); 59 | 60 | return $result; 61 | } 62 | 63 | /** {@inheritdoc} */ 64 | public function delete($id = null) 65 | { 66 | $id = $this->getId($id); 67 | $path = '/' . static::TYPE . '/' . $id; 68 | $resource = static::TYPE . '/' . $id; 69 | $result = $this->request(Verbs::DELETE, $path, $resource); 70 | 71 | return $result; 72 | } 73 | 74 | /** {@inheritdoc} */ 75 | public function replace(array $data, $id = null) 76 | { 77 | throw new \Exception('Replacing Database resource is not supported.'); 78 | } 79 | 80 | /** {@inheritdoc} */ 81 | public function query($sql, array $parameters = []) 82 | { 83 | throw new \Exception('Querying Database resource is not supported.'); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /azuredeploy-azuresearch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "name": { 6 | "type": "string", 7 | "minLength": 2, 8 | "maxLength": 60, 9 | "metadata": { 10 | "description": "Service name must only contain lowercase letters, digits or dashes, cannot use dash as the first two or last one characters, cannot contain consecutive dashes, and is limited between 2 and 60 characters in length." 11 | } 12 | }, 13 | "sku": { 14 | "type": "string", 15 | "defaultValue": "standard", 16 | "allowedValues": [ 17 | "basic", 18 | "standard", 19 | "standard2", 20 | "standard3" 21 | ], 22 | "metadata": { 23 | "description": "The SKU of the search service you want to create. E.g. free or standard" 24 | } 25 | }, 26 | "replicaCount": { 27 | "type": "int", 28 | "minValue": 1, 29 | "maxValue": 12, 30 | "defaultValue": 1, 31 | "metadata": { 32 | "description": "Replicas distribute search workloads across the service. You need 2 or more to support high availability (applies to Basic and Standard only)." 33 | } 34 | }, 35 | "partitionCount": { 36 | "type": "int", 37 | "allowedValues": [ 38 | 1, 39 | 2, 40 | 3, 41 | 4, 42 | 6, 43 | 12 44 | ], 45 | "defaultValue": 1, 46 | "metadata": { 47 | "description": "Partitions allow for scaling of document count as well as faster indexing by sharding your index over multiple Azure Search units." 48 | } 49 | }, 50 | "hostingMode": { 51 | "type": "string", 52 | "defaultValue": "default", 53 | "allowedValues": [ 54 | "default", 55 | "highDensity" 56 | ], 57 | "metadata": { 58 | "description": "Applicable only for SKU set to standard3. You can set this property to enable a single, high density partition that allows up to 1000 indexes, which is much higher than the maximum indexes allowed for any other SKU." 59 | } 60 | } 61 | }, 62 | "resources": [ 63 | { 64 | "apiVersion": "2015-08-19", 65 | "name": "[parameters('name')]", 66 | "type": "Microsoft.Search/searchServices", 67 | "location": "[resourceGroup().location]", 68 | "sku": { 69 | "name": "[toLower(parameters('sku'))]" 70 | }, 71 | "properties": { 72 | "replicaCount": "[parameters('replicaCount')]", 73 | "partitionCount": "[parameters('partitionCount')]", 74 | "hostingMode": "[parameters('hostingMode')]" 75 | } 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /workflow/monitor-media-job/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using Newtonsoft.Json; 7 | using Microsoft.WindowsAzure.MediaServices.Client; 8 | using Microsoft.WindowsAzure.Storage; 9 | using Microsoft.WindowsAzure.Storage.Blob; 10 | using Microsoft.WindowsAzure.Storage.Auth; 11 | using System.Threading.Tasks; 12 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 13 | 14 | private static CloudMediaContext _context = null; 15 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 16 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 17 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 18 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 19 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 20 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 21 | 22 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 23 | { 24 | log.Info($"Webhook was triggered!"); 25 | string jsonContent = await req.Content.ReadAsStringAsync(); 26 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 27 | log.Info("Request : " + jsonContent); 28 | 29 | // Validate input objects 30 | int delay = 15000; 31 | if (data.JobId == null) 32 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass JobId in the input object" }); 33 | if (data.Delay != null) 34 | delay = data.Delay; 35 | log.Info("Input - JobId : " + data.JobId); 36 | 37 | log.Info($"Wait " + delay + "(ms)"); 38 | System.Threading.Thread.Sleep(delay); 39 | 40 | IJob job = null; 41 | try 42 | { 43 | // Load AMS account context 44 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 45 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 46 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 47 | AzureEnvironments.AzureCloudEnvironment); 48 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 49 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 50 | 51 | // Get the job 52 | string jobid = (string)data.JobId; 53 | job = _context.Jobs.Where(j => j.Id == jobid).FirstOrDefault(); 54 | if (job == null) 55 | { 56 | log.Info("Job not found : " + jobid); 57 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Job not found" }); 58 | } 59 | } 60 | catch (Exception ex) 61 | { 62 | log.Info("Exception " + ex); 63 | return req.CreateResponse(HttpStatusCode.BadRequest); 64 | } 65 | 66 | // IJob.State 67 | // - Queued = 0 68 | // - Scheduled = 1 69 | // - Processing = 2 70 | // - Finished = 3 71 | // - Error = 4 72 | // - Canceled = 5 73 | // - Canceling = 6 74 | log.Info($"Job {job.Id} status is {job.State}"); 75 | 76 | return req.CreateResponse(HttpStatusCode.OK, new 77 | { 78 | JobId = job.Id, 79 | JobState = job.State 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /workflow/monitor-copy-blob/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using Newtonsoft.Json; 7 | using Microsoft.WindowsAzure.Storage; 8 | using Microsoft.WindowsAzure.Storage.Blob; 9 | using Microsoft.WindowsAzure.Storage.Auth; 10 | 11 | // Read values from the App.config file. 12 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 13 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 14 | 15 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 16 | { 17 | log.Info($"Webhook was triggered!"); 18 | 19 | string jsonContent = await req.Content.ReadAsStringAsync(); 20 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 21 | log.Info("Request : " + jsonContent); 22 | 23 | // Validate input objects 24 | int delay = 15000; 25 | if (data.DestinationContainer == null) 26 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass DestinationContainer in the input object" }); 27 | if (data.Delay != null) 28 | delay = data.Delay; 29 | log.Info("Input - DestinationContainer : " + data.DestinationContainer); 30 | //log.Info("delay : " + delay); 31 | 32 | log.Info($"Wait " + delay + "(ms)"); 33 | System.Threading.Thread.Sleep(delay); 34 | 35 | CopyStatus copyStatus = CopyStatus.Success; 36 | try 37 | { 38 | string destinationContainerName = data.DestinationContainer; 39 | CloudBlobContainer destinationBlobContainer = GetCloudBlobContainer(_amsStorageAccountName, _amsStorageAccountKey, destinationContainerName); 40 | 41 | string blobPrefix = null; 42 | bool useFlatBlobListing = true; 43 | var destBlobList = destinationBlobContainer.ListBlobs(blobPrefix, useFlatBlobListing, BlobListingDetails.Copy); 44 | foreach (var dest in destBlobList) 45 | { 46 | var destBlob = dest as CloudBlob; 47 | if (destBlob.CopyState.Status == CopyStatus.Aborted || destBlob.CopyState.Status == CopyStatus.Failed) 48 | { 49 | // Log the copy status description for diagnostics and restart copy 50 | destBlob.StartCopyAsync(destBlob.CopyState.Source); 51 | copyStatus = CopyStatus.Pending; 52 | } 53 | else if (destBlob.CopyState.Status == CopyStatus.Pending) 54 | { 55 | // We need to continue waiting for this pending copy 56 | // However, let us log copy state for diagnostics 57 | copyStatus = CopyStatus.Pending; 58 | } 59 | // else we completed this pending copy 60 | } 61 | } 62 | catch (Exception ex) 63 | { 64 | log.Info("Exception " + ex); 65 | return req.CreateResponse(HttpStatusCode.BadRequest); 66 | } 67 | 68 | return req.CreateResponse(HttpStatusCode.OK, new 69 | { 70 | CopyStatus = copyStatus 71 | }); 72 | } 73 | 74 | static public CloudBlobContainer GetCloudBlobContainer(string storageAccountName, string storageAccountKey, string containerName) 75 | { 76 | CloudStorageAccount sourceStorageAccount = new CloudStorageAccount(new StorageCredentials(storageAccountName, storageAccountKey), true); 77 | CloudBlobClient sourceCloudBlobClient = sourceStorageAccount.CreateCloudBlobClient(); 78 | return sourceCloudBlobClient.GetContainerReference(containerName); 79 | } -------------------------------------------------------------------------------- /portal/api/azuresearch-content-api.php: -------------------------------------------------------------------------------- 1 | $value) { 21 | $params[$name] = $value; 22 | } 23 | } 24 | 25 | $azsearch_index_name = "content"; 26 | $AZURESEARCH_URL_BASE= sprintf( "https://%s.search.windows.net/indexes/%s/docs", 27 | $azsearch_service_name, $azsearch_index_name ); 28 | 29 | $url = $AZURESEARCH_URL_BASE . '?' 30 | . '$top=1000&$select=id,content_id' 31 | . '&$count=true&api-version=' . $azsearch_api_version; 32 | if (array_key_exists('search', $params)) { 33 | $url = $url . '&search=' . urlencode($params['search']); 34 | } 35 | 36 | $opts = array( 37 | 'http'=>array( 38 | 'method'=>"GET", 39 | 'header'=>"Accept: application/json\r\n" . 40 | "api-key: $azsearch_api_key\r\n", 41 | 'timeout' =>10 42 | ) 43 | ); 44 | 45 | $context = stream_context_create($opts); 46 | $data = file_get_contents($url, false, $context); 47 | 48 | $search_res=json_decode($data,true); // return as assoc array 49 | //var_dump($search_res); 50 | $contents_arr = $search_res["value"]; 51 | //var_dump($contents_arr); 52 | $content_ids = array(); 53 | foreach ($contents_arr as $c) { 54 | if (array_key_exists('content_id', $c) ) { 55 | array_push($content_ids, $c['content_id']); 56 | } 57 | } 58 | 59 | $ret_value_array =array(); 60 | if (count($content_ids) > 0) { 61 | $client = new \DreamFactory\DocumentDb\Client($docdb_host, $docdb_master_key); 62 | $contentdb = new \DreamFactory\DocumentDb\Resources\Document($client, $docdb_db_content, $docdb_coll_content); 63 | //$res = $contentdb->query('SELECT * FROM c WHERE c.id = @id', [['name' => '@id', 'value' => $content_id],]); 64 | $querystr = sprintf( "SELECT * FROM c WHERE c.id IN ('%s')", implode("','",$content_ids) ); 65 | //echo $querystr; 66 | $cosmos_res = $contentdb->query($querystr); 67 | //var_dump($cosmos_res); 68 | $docs_arr = $cosmos_res["Documents"]; 69 | foreach ($docs_arr as $d) { 70 | if( array_key_exists('thumbnail_url', $d) && array_key_exists('name', $d) ) { 71 | array_push( 72 | $ret_value_array, 73 | array( 74 | 'content_id' => $d['id'], 75 | 'name' => $d['name'], 76 | 'thumbnail_url' => $d['thumbnail_url'] 77 | ) 78 | ); 79 | } 80 | } 81 | } 82 | // Making return value as the same JSON format as Azure Search API response format 83 | $ret_array =array( 84 | '@odata.count' => count($ret_value_array), 85 | 'value' => $ret_value_array 86 | ); 87 | $ret_data = json_encode($ret_array); 88 | //var_dump($ret_array); 89 | 90 | header('Content-Length: '.strlen($ret_data)); 91 | header('Content-Type: application/json; odata.metadata=minimal'); 92 | header('Access-Control-Allow-Origin: *'); 93 | print $ret_data; 94 | 95 | ?> 96 | -------------------------------------------------------------------------------- /workflow/create-empty-mediaasset/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using Newtonsoft.Json; 7 | using Microsoft.WindowsAzure.MediaServices.Client; 8 | using System.Threading.Tasks; 9 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 10 | 11 | private static CloudMediaContext _context = null; 12 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 13 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 14 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 15 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 16 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 17 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 18 | private static readonly string _srcStorageAccountName = Environment.GetEnvironmentVariable("SourceStorageAccountName"); 19 | private static readonly string _srcStorageAccountKey = Environment.GetEnvironmentVariable("SourceStorageAccountKey"); 20 | 21 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 22 | { 23 | log.Info($"Webhook was triggered!"); 24 | 25 | string jsonContent = await req.Content.ReadAsStringAsync(); 26 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 27 | log.Info("Request : " + jsonContent); 28 | 29 | // Validate input objects 30 | if (data.FileName == null) 31 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass FileName in the input object" }); 32 | log.Info("Input - File Name : " + data.FileName); 33 | 34 | string path = data.FileName; 35 | Blob blob = new Blob(path); 36 | string name = blob.name; 37 | log.Info("Input - Blob Name : " + name); 38 | 39 | IAsset newAsset = null; 40 | //IIngestManifest manifest = null; 41 | try 42 | { 43 | // Load AMS account context 44 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 45 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 46 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 47 | AzureEnvironments.AzureCloudEnvironment); 48 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 49 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 50 | 51 | // Create Asset 52 | string assetName = "AzureFn - " + name; 53 | newAsset = _context.Assets.Create(assetName, AssetCreationOptions.None); 54 | log.Info("Created Azure Media Services Asset : "); 55 | log.Info(" - Asset Name = " + name); 56 | log.Info(" - Asset Creation Option = " + AssetCreationOptions.None); 57 | } 58 | catch (Exception ex) 59 | { 60 | log.Info("Exception " + ex); 61 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 62 | } 63 | 64 | log.Info("Asset ID : " + newAsset.Id); 65 | 66 | return req.CreateResponse(HttpStatusCode.OK, new 67 | { 68 | AssetId = newAsset.Id, 69 | DestinationContainer = newAsset.Uri.Segments[1] 70 | }); 71 | } 72 | 73 | public class Blob 74 | { 75 | public string containerName { get; set; } 76 | public string name { get; set; } 77 | 78 | public Blob(string path) 79 | { 80 | containerName = path.Substring(1, (path.IndexOf('/', 1) - 1)); 81 | name = path.Substring(path.IndexOf('/', 1) + 1); 82 | } 83 | } -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Resources/Collection.php: -------------------------------------------------------------------------------- 1 | dbId = $dbId; 32 | 33 | parent::__construct($client, static::TYPE, $resourceId); 34 | } 35 | 36 | //yoichika 37 | /** {@inheritdoc} */ 38 | public function getlist() 39 | { 40 | $path = '/' . Database::TYPE . 41 | '/' . $this->dbId . 42 | '/' . static::TYPE; 43 | $resource = Database::TYPE . 44 | '/' . $this->dbId; 45 | $result = $this->request(Verbs::GET, $path, $resource); 46 | 47 | return $result; 48 | } 49 | 50 | /** {@inheritdoc} */ 51 | public function get($id = null) 52 | { 53 | $id = $this->getId($id); 54 | $path = '/' . Database::TYPE . 55 | '/' . $this->dbId . 56 | '/' . static::TYPE . 57 | '/' . $id; 58 | $resource = trim($path, '/'); 59 | $result = $this->request(Verbs::GET, $path, $resource); 60 | 61 | return $result; 62 | } 63 | 64 | /** {@inheritdoc} */ 65 | public function create(array $data) 66 | { 67 | if (!isset($data['id'])) { 68 | throw new \InvalidArgumentException('No id found in data. Id is required to create a Collection.'); 69 | } 70 | $path = '/' . Database::TYPE . 71 | '/' . $this->dbId . 72 | '/' . static::TYPE; 73 | $resource = Database::TYPE . 74 | '/' . $this->dbId; 75 | $result = $this->request(Verbs::POST, $path, $resource, $data); 76 | 77 | return $result; 78 | } 79 | 80 | /** {@inheritdoc} */ 81 | public function replace(array $data, $id = null) 82 | { 83 | if (!isset($data['id'])) { 84 | throw new \InvalidArgumentException('No id found in data. Id is required to replace a Collection.'); 85 | } 86 | $id = $this->getId($id); 87 | $path = '/' . Database::TYPE . 88 | '/' . $this->dbId . 89 | '/' . static::TYPE . 90 | '/' . $id; 91 | $resource = trim($path, '/'); 92 | $result = $this->request(Verbs::PUT, $path, $resource, $data); 93 | 94 | return $result; 95 | } 96 | 97 | /** {@inheritdoc} */ 98 | public function delete($id = null) 99 | { 100 | $id = $this->getId($id); 101 | $path = '/' . Database::TYPE . 102 | '/' . $this->dbId . 103 | '/' . static::TYPE . 104 | '/' . $id; 105 | $resource = trim($path, '/'); 106 | $result = $this->request(Verbs::DELETE, $path, $resource); 107 | 108 | return $result; 109 | } 110 | 111 | /** {@inheritdoc} */ 112 | public function query($sql, array $parameters = []) 113 | { 114 | throw new \Exception('Querying Collection resource is not supported.'); 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /portal/index.php: -------------------------------------------------------------------------------- 1 | setHeaders(['x-ms-max-item-count: 2000']); 16 | $res = $contentdb->getlist(); 17 | $http_code = get_http_code($res); 18 | $contents_arr = array(); 19 | $service_init_need = false; 20 | 21 | switch ($http_code) { 22 | case 200: 23 | $contents_arr = $res['Documents']; 24 | break; 25 | case 404: 26 | $service_init_need = true; 27 | break; 28 | default: 29 | print "ERROR: Loading Content!"; 30 | exit; 31 | } 32 | 33 | function get_http_code($res) { 34 | if( is_array($res) and array_key_exists('_curl_info', $res)) { 35 | $curl_info=$res['_curl_info']; 36 | if ( array_key_exists('http_code', $curl_info) ) { 37 | return $curl_info['http_code']; 38 | } 39 | } 40 | return 500; 41 | } 42 | 43 | function array_has_value_of( $key, $arr) { 44 | if( array_key_exists($key, $arr) ){ 45 | $v = $arr[$key]; 46 | if( isset($v) && !empty($v) ) { 47 | return true; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | ?> 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Azure Media & AI Demo 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | 81 |
82 |
83 |

84 |

85 |

86 |
87 | 96 |
97 | 98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /workflow/check-media-asset/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using Newtonsoft.Json; 7 | using Microsoft.WindowsAzure.MediaServices.Client; 8 | using System.Threading.Tasks; 9 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 10 | 11 | private static CloudMediaContext _context = null; 12 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 13 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 14 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 15 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 16 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 17 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 18 | private static readonly string _srcStorageAccountName = Environment.GetEnvironmentVariable("SourceStorageAccountName"); 19 | private static readonly string _srcStorageAccountKey = Environment.GetEnvironmentVariable("SourceStorageAccountKey"); 20 | 21 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 22 | { 23 | log.Info($"Webhook was triggered!"); 24 | 25 | string jsonContent = await req.Content.ReadAsStringAsync(); 26 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 27 | 28 | // Validate input objects 29 | if (data.QueueMessage == null) 30 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 31 | log.Info("Input - QueueMessage : " + data.QueueMessage); 32 | 33 | dynamic queueMessage = JsonConvert.DeserializeObject((string)(data.QueueMessage)); 34 | if (queueMessage.SourceLocation == null) 35 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 36 | if (queueMessage.TargetLocation == null) 37 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 38 | log.Info("Input - SourceLocation : " + queueMessage.SourceLocation); 39 | log.Info("Input - TargetLocation : " + queueMessage.TargetLocation); 40 | 41 | string storageUrl = (string)(queueMessage.TargetLocation); 42 | log.Info("Asset Storage URL - " + storageUrl); 43 | string assetid = null; 44 | string filename = null; 45 | try 46 | { 47 | // Load AMS account context 48 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 49 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 50 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 51 | AzureEnvironments.AzureCloudEnvironment); 52 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 53 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 54 | 55 | // Get the Asset 56 | Uri storageUri = new Uri(storageUrl); 57 | string assetPath = Path.GetFileName(storageUri.AbsolutePath); 58 | string assetid2 = "nb:cid:UUID:" + assetPath.Substring(assetPath.IndexOf('-', 1) + 1); 59 | log.Info("AssetId - " + assetid2); 60 | //IAsset asset = _context.Assets.Where(a => a.id == assetid2).FirstOrDefault(); 61 | var asset = _context.Assets.Where(a => a.Id == assetid2).FirstOrDefault(); 62 | if (asset == null) 63 | { 64 | log.Info("Asset not found - " + assetid); 65 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Asset not found" }); 66 | } 67 | log.Info("Asset found, AssetId : " + asset.Id); 68 | 69 | assetid = asset.Id; 70 | filename = Path.GetFileName((string)(queueMessage.SourceLocation)); 71 | 72 | log.Info("Output - AssetId : " + assetid); 73 | log.Info("Output - FileName : " + filename); 74 | } 75 | catch (Exception ex) 76 | { 77 | log.Info("Exception " + ex); 78 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 79 | } 80 | 81 | return req.CreateResponse(HttpStatusCode.OK, new 82 | { 83 | AssetId = assetid, 84 | FileName = filename 85 | }); 86 | } -------------------------------------------------------------------------------- /workflow/add-file-to-mediaasset/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using Newtonsoft.Json; 7 | using Microsoft.WindowsAzure.MediaServices.Client; 8 | using Microsoft.WindowsAzure.Storage; 9 | using Microsoft.WindowsAzure.Storage.Blob; 10 | using Microsoft.WindowsAzure.Storage.Auth; 11 | using System.Threading.Tasks; 12 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 13 | 14 | private static CloudMediaContext _context = null; 15 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 16 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 17 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 18 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 19 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 20 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 21 | 22 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 23 | { 24 | log.Info($"Webhook was triggered!"); 25 | 26 | string jsonContent = await req.Content.ReadAsStringAsync(); 27 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 28 | log.Info("Request : " + jsonContent); 29 | 30 | // Validate input objects 31 | if (data.AssetId == null) 32 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass AssetId in the input object" }); 33 | log.Info("Input - AssetId : " + data.AssetId); 34 | if (data.CopyFileName == null) 35 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass CopyFileName in the input object" }); 36 | log.Info("Input - CopyFileName : " + data.CopyFileName); 37 | 38 | string assetid = data.AssetId; 39 | string fileName = data.CopyFileName; 40 | try 41 | { 42 | // Load AMS account context 43 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 44 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 45 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 46 | AzureEnvironments.AzureCloudEnvironment); 47 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 48 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 49 | 50 | // Get the Asset 51 | var asset = _context.Assets.Where(a => a.Id == assetid).FirstOrDefault(); 52 | if (asset == null) 53 | { 54 | log.Info("Asset not found - " + assetid); 55 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Asset not found" }); 56 | } 57 | log.Info("Asset found, Asset ID : " + asset.Id); 58 | 59 | // Add AssetFiles to the Asset 60 | CloudBlobContainer destinationBlobContainer = GetCloudBlobContainer(_amsStorageAccountName, _amsStorageAccountKey, asset.Uri.Segments[1]); 61 | IAssetFile assetFile = asset.AssetFiles.Create(fileName); 62 | CloudBlockBlob blob = destinationBlobContainer.GetBlockBlobReference(fileName); 63 | blob.FetchAttributes(); 64 | assetFile.ContentFileSize = blob.Properties.Length; 65 | assetFile.IsPrimary = true; 66 | assetFile.Update(); 67 | log.Info("Asset file registered : " + assetFile.Name); 68 | } 69 | catch (Exception ex) 70 | { 71 | log.Info("Exception " + ex); 72 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 73 | } 74 | log.Info("Asset ID : " + assetid); 75 | 76 | return req.CreateResponse(HttpStatusCode.OK, new 77 | { 78 | AssetId = assetid 79 | }); 80 | } 81 | 82 | static public CloudBlobContainer GetCloudBlobContainer(string storageAccountName, string storageAccountKey, string containerName) 83 | { 84 | CloudStorageAccount sourceStorageAccount = new CloudStorageAccount(new StorageCredentials(storageAccountName, storageAccountKey), true); 85 | CloudBlobClient sourceCloudBlobClient = sourceStorageAccount.CreateCloudBlobClient(); 86 | return sourceCloudBlobClient.GetContainerReference(containerName); 87 | } -------------------------------------------------------------------------------- /workflow/Presets/encodingmbr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 1.0, 3 | "Codecs": [ 4 | { 5 | "KeyFrameInterval": "00:00:02", 6 | "H264Layers": [ 7 | { 8 | "Profile": "Auto", 9 | "Level": "auto", 10 | "Bitrate": 6000, 11 | "MaxBitrate": 6000, 12 | "BufferWindow": "00:00:05", 13 | "Width": 1920, 14 | "Height": 1080, 15 | "BFrames": 3, 16 | "ReferenceFrames": 3, 17 | "AdaptiveBFrame": true, 18 | "Type": "H264Layer", 19 | "FrameRate": "0/1" 20 | }, 21 | { 22 | "Profile": "Auto", 23 | "Level": "auto", 24 | "Bitrate": 4700, 25 | "MaxBitrate": 4700, 26 | "BufferWindow": "00:00:05", 27 | "Width": 1920, 28 | "Height": 1080, 29 | "BFrames": 3, 30 | "ReferenceFrames": 3, 31 | "AdaptiveBFrame": true, 32 | "Type": "H264Layer", 33 | "FrameRate": "0/1" 34 | }, 35 | { 36 | "Profile": "Auto", 37 | "Level": "auto", 38 | "Bitrate": 3400, 39 | "MaxBitrate": 3400, 40 | "BufferWindow": "00:00:05", 41 | "Width": 1280, 42 | "Height": 720, 43 | "BFrames": 3, 44 | "ReferenceFrames": 3, 45 | "AdaptiveBFrame": true, 46 | "Type": "H264Layer", 47 | "FrameRate": "0/1" 48 | }, 49 | { 50 | "Profile": "Auto", 51 | "Level": "auto", 52 | "Bitrate": 2250, 53 | "MaxBitrate": 2250, 54 | "BufferWindow": "00:00:05", 55 | "Width": 960, 56 | "Height": 540, 57 | "BFrames": 3, 58 | "ReferenceFrames": 3, 59 | "AdaptiveBFrame": true, 60 | "Type": "H264Layer", 61 | "FrameRate": "0/1" 62 | }, 63 | { 64 | "Profile": "Auto", 65 | "Level": "auto", 66 | "Bitrate": 1500, 67 | "MaxBitrate": 1500, 68 | "BufferWindow": "00:00:05", 69 | "Width": 960, 70 | "Height": 540, 71 | "BFrames": 3, 72 | "ReferenceFrames": 3, 73 | "AdaptiveBFrame": true, 74 | "Type": "H264Layer", 75 | "FrameRate": "0/1" 76 | }, 77 | { 78 | "Profile": "Auto", 79 | "Level": "auto", 80 | "Bitrate": 1000, 81 | "MaxBitrate": 1000, 82 | "BufferWindow": "00:00:05", 83 | "Width": 640, 84 | "Height": 360, 85 | "BFrames": 3, 86 | "ReferenceFrames": 3, 87 | "AdaptiveBFrame": true, 88 | "Type": "H264Layer", 89 | "FrameRate": "0/1" 90 | }, 91 | { 92 | "Profile": "Auto", 93 | "Level": "auto", 94 | "Bitrate": 650, 95 | "MaxBitrate": 650, 96 | "BufferWindow": "00:00:05", 97 | "Width": 640, 98 | "Height": 360, 99 | "BFrames": 3, 100 | "ReferenceFrames": 3, 101 | "AdaptiveBFrame": true, 102 | "Type": "H264Layer", 103 | "FrameRate": "0/1" 104 | }, 105 | { 106 | "Profile": "Auto", 107 | "Level": "auto", 108 | "Bitrate": 400, 109 | "MaxBitrate": 400, 110 | "BufferWindow": "00:00:05", 111 | "Width": 320, 112 | "Height": 180, 113 | "BFrames": 3, 114 | "ReferenceFrames": 3, 115 | "AdaptiveBFrame": true, 116 | "Type": "H264Layer", 117 | "FrameRate": "0/1" 118 | } 119 | ], 120 | "Type": "H264Video" 121 | }, 122 | { 123 | "Profile": "AACLC", 124 | "Channels": 2, 125 | "SamplingRate": 48000, 126 | "Bitrate": 128, 127 | "Type": "AACAudio" 128 | }, 129 | { 130 | "Start": "{Best}", 131 | "Type": "PngImage", 132 | "PngLayers": [ 133 | { 134 | "Type": "PngLayer", 135 | "Width": 640, 136 | "Height": 360 137 | } 138 | ] 139 | } 140 | ], 141 | "Outputs": [ 142 | { 143 | "FileName": "{Basename}_{Width}x{Height}_{VideoBitrate}.mp4", 144 | "Format": { 145 | "Type": "MP4Format" 146 | } 147 | }, 148 | { 149 | "FileName": "{Basename}_{Index}{Extension}", 150 | "Format": { 151 | "Type": "PngFormat" 152 | } 153 | } 154 | ] 155 | } -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Resources/Document.php: -------------------------------------------------------------------------------- 1 | dbId = $dbId; 36 | $this->collId = $collId; 37 | 38 | parent::__construct($client, static::TYPE, $resourceId); 39 | } 40 | 41 | //yoichika 42 | /** {@inheritdoc} */ 43 | //public function list() 44 | public function getlist() 45 | { 46 | $path = '/' . Database::TYPE . 47 | '/' . $this->dbId . 48 | '/' . Collection::TYPE . 49 | '/' . $this->collId . 50 | '/' . static::TYPE; 51 | $resource = Database::TYPE . 52 | '/' . $this->dbId . 53 | '/' . Collection::TYPE . 54 | '/' . $this->collId; 55 | $result = $this->request(Verbs::GET, $path, $resource); 56 | 57 | return $result; 58 | } 59 | 60 | /** {@inheritdoc} */ 61 | public function get($id = null) 62 | { 63 | $id = $this->getId($id); 64 | $path = '/' . Database::TYPE . 65 | '/' . $this->dbId . 66 | '/' . Collection::TYPE . 67 | '/' . $this->collId . 68 | '/' . static::TYPE . 69 | '/' . $id; 70 | $resource = trim($path, '/'); 71 | $result = $this->request(Verbs::GET, $path, $resource); 72 | 73 | return $result; 74 | } 75 | 76 | /** {@inheritdoc} */ 77 | public function create(array $data) 78 | { 79 | if (!isset($data['id'])) { 80 | throw new \InvalidArgumentException('No id found in data. Id is required to create a Document.'); 81 | } 82 | $path = '/' . Database::TYPE . 83 | '/' . $this->dbId . 84 | '/' . Collection::TYPE . 85 | '/' . $this->collId . 86 | '/' . static::TYPE; 87 | $resource = Database::TYPE . 88 | '/' . $this->dbId . 89 | '/' . Collection::TYPE . 90 | '/' . $this->collId; 91 | $result = $this->request(Verbs::POST, $path, $resource, $data); 92 | 93 | return $result; 94 | } 95 | 96 | /** {@inheritdoc} */ 97 | public function replace(array $data, $id = null) 98 | { 99 | if (!isset($data['id'])) { 100 | throw new \InvalidArgumentException('No id found in data. Id is required to create a Document.'); 101 | } 102 | $id = $this->getId($id); 103 | $path = '/' . Database::TYPE . 104 | '/' . $this->dbId . 105 | '/' . Collection::TYPE . 106 | '/' . $this->collId . 107 | '/' . static::TYPE . 108 | '/' . $id; 109 | $resource = trim($path, '/'); 110 | $result = $this->request(Verbs::PUT, $path, $resource, $data); 111 | 112 | return $result; 113 | } 114 | 115 | /** {@inheritdoc} */ 116 | public function delete($id = null) 117 | { 118 | $id = $this->getId($id); 119 | $path = '/' . Database::TYPE . 120 | '/' . $this->dbId . 121 | '/' . Collection::TYPE . 122 | '/' . $this->collId . 123 | '/' . static::TYPE . 124 | '/' . $id; 125 | $resource = trim($path, '/'); 126 | $result = $this->request(Verbs::DELETE, $path, $resource); 127 | 128 | return $result; 129 | } 130 | 131 | /** {@inheritdoc} */ 132 | public function query($sql, array $parameters = []) 133 | { 134 | if (empty($sql)) { 135 | throw new \InvalidArgumentException('No query provided. A SQL query string is required for Query operation'); 136 | } 137 | $data = ['query' => $sql, 'parameters' => $parameters]; 138 | $path = '/' . Database::TYPE . 139 | '/' . $this->dbId . 140 | '/' . Collection::TYPE . 141 | '/' . $this->collId . 142 | '/' . static::TYPE; 143 | $resource = Database::TYPE . 144 | '/' . $this->dbId . 145 | '/' . Collection::TYPE . 146 | '/' . $this->collId; 147 | $result = $this->request(Verbs::POST, $path, $resource, $data); 148 | 149 | return $result; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /azuredeploy-portal.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "siteName": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "The name of the web app that you wish to create." 9 | } 10 | }, 11 | "hostingPlanName": { 12 | "type": "string", 13 | "metadata": { 14 | "description": "The name of the App Service plan to use for hosting the web app." 15 | } 16 | }, 17 | "sku": { 18 | "type": "string", 19 | "allowedValues": [ 20 | "B1", 21 | "B2", 22 | "B3", 23 | "S1", 24 | "S2", 25 | "S3" 26 | ], 27 | "defaultValue": "S1", 28 | "metadata": { 29 | "description": "The pricing tier for the hosting plan." 30 | } 31 | }, 32 | "workerSize": { 33 | "type": "string", 34 | "allowedValues": [ 35 | "0", 36 | "1", 37 | "2" 38 | ], 39 | "defaultValue": "0", 40 | "metadata": { 41 | "description": "The instance size of the hosting plan (small, medium, or large)." 42 | } 43 | }, 44 | "repoURL": { 45 | "type": "string", 46 | "defaultValue": "https://github.com/shigeyf/ai-digitalmedia", 47 | "metadata": { 48 | "description": "The URL for the GitHub repository that contains the project to deploy." 49 | } 50 | }, 51 | "branch": { 52 | "type": "string", 53 | "defaultValue": "master", 54 | "metadata": { 55 | "description": "The branch of the GitHub repository to use." 56 | } 57 | }, 58 | "cosmosdbAccountName": { 59 | "type": "string", 60 | "defaultValue": "", 61 | "metadata": { 62 | "description": "Cosmos DB Service Name" 63 | } 64 | }, 65 | "searchServiceName": { 66 | "type": "string", 67 | "defaultValue": "", 68 | "metadata": { 69 | "description": "Azure Search Account Name" 70 | } 71 | } 72 | }, 73 | "variables": { 74 | "project": "portal", 75 | "searchServicesId": "[resourceId('Microsoft.Search/searchServices', parameters('searchServiceName'))]", 76 | "cosmosdbAccountid": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosdbAccountName'))]", 77 | "cosmosdbServiceHost": "[concat('https://', parameters('cosmosdbAccountName'), '.documents.azure.com:443')]" 78 | }, 79 | "resources": [ 80 | { 81 | "apiVersion": "2016-09-01", 82 | "name": "[parameters('hostingPlanName')]", 83 | "type": "Microsoft.Web/serverfarms", 84 | "location": "[resourceGroup().location]", 85 | "sku": { 86 | "name": "[parameters('sku')]", 87 | "capacity": "[parameters('workerSize')]" 88 | }, 89 | "properties": { 90 | "name": "[parameters('hostingPlanName')]" 91 | }, 92 | "kind":"linux" 93 | }, 94 | { 95 | "apiVersion":"2016-08-01", 96 | "name": "[parameters('siteName')]", 97 | "type": "Microsoft.Web/sites", 98 | "location": "[resourceGroup().location]", 99 | "dependsOn": [ 100 | "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]" 101 | ], 102 | "properties": { 103 | "serverFarmId": "[parameters('hostingPlanName')]", 104 | "siteConfig":{ 105 | "properties":{ 106 | "numberOfWorkers":"1", 107 | "phpVersion":"7.0" 108 | }, 109 | "maxNumberOfWorkers":"1" 110 | } 111 | }, 112 | "resources": [ 113 | { 114 | "apiVersion":"2016-08-01", 115 | "type":"config", 116 | "name":"web", 117 | "dependsOn":[ 118 | "[concat('Microsoft.Web/sites/', parameters('siteName'))]" 119 | ], 120 | "properties":{ 121 | "phpVersion":"7.0" 122 | } 123 | }, 124 | { 125 | "apiVersion": "2016-08-01", 126 | "name": "web", 127 | "type": "sourcecontrols", 128 | "dependsOn": [ 129 | "[resourceId('Microsoft.Web/sites', parameters('siteName'))]", 130 | "[concat('Microsoft.Web/sites/', parameters('siteName'), '/config/web')]" 131 | ], 132 | "properties": { 133 | "RepoUrl": "[parameters('repoURL')]", 134 | "branch": "[parameters('branch')]", 135 | "IsManualIntegration": true 136 | } 137 | }, 138 | { 139 | "type": "config", 140 | "name": "appsettings", 141 | "apiVersion": "2016-08-01", 142 | "comments": "These are the default appsettings configured on the app.", 143 | "dependsOn": [ 144 | "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]", 145 | "[resourceId('Microsoft.Web/sites', parameters('siteName'))]" 146 | ], 147 | "properties": { 148 | "Project": "[variables('project')]", 149 | "CosmosdbServiceHost": "[variables('cosmosdbServiceHost')]", 150 | "CosmosdbMasterKey": "[listKeys(variables('cosmosdbAccountid'),'2015-04-08').primaryMasterKey]", 151 | "AzureSearchServiceName": "[parameters('SearchServiceName')]", 152 | "AzureSearchApiKey": "[listAdminKeys(variables('searchServicesId'),'2015-08-19').PrimaryKey]" 153 | } 154 | } 155 | ] 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /workflow/import-mediaasset-singleblob/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using System.Text; 7 | using Newtonsoft.Json; 8 | using Microsoft.WindowsAzure.MediaServices.Client; 9 | using System.Threading.Tasks; 10 | using Microsoft.WindowsAzure.Storage; 11 | using Microsoft.WindowsAzure.Storage.Blob; 12 | using Microsoft.WindowsAzure.Storage.Auth; 13 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 14 | 15 | private static CloudMediaContext _context = null; 16 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 17 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 18 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 19 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 20 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 21 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 22 | private static readonly string _srcStorageAccountName = Environment.GetEnvironmentVariable("SourceStorageAccountName"); 23 | private static readonly string _srcStorageAccountKey = Environment.GetEnvironmentVariable("SourceStorageAccountKey"); 24 | 25 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 26 | { 27 | log.Info($"Webhook was triggered!"); 28 | 29 | string jsonContent = await req.Content.ReadAsStringAsync(); 30 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 31 | log.Info("Request : " + jsonContent); 32 | 33 | // Validate input objects 34 | if (data.FileName == null) 35 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass FileName in the input object" }); 36 | log.Info("Input - FileName : " + data.FileName); 37 | if (data.DestinationContainer == null) 38 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass DestinationContainer in the input object" }); 39 | log.Info("Input - DestinationContainer : " + data.DestinationContainer); 40 | 41 | string path = data.FileName; 42 | Blob blob = new Blob(path); 43 | string srcContainerName = blob.containerName; 44 | string name = blob.name; 45 | log.Info("Input - Blob Container Name : " + srcContainerName); 46 | log.Info("Input - Blob Name : " + name); 47 | string destContainerName = data.DestinationContainer; 48 | 49 | IAsset newAsset = null; 50 | //IIngestManifest manifest = null; 51 | try 52 | { 53 | // Setup blob container 54 | CloudBlobContainer sourceBlobContainer = GetCloudBlobContainer(_srcStorageAccountName, _srcStorageAccountKey, srcContainerName); 55 | CloudBlobContainer destinationBlobContainer = GetCloudBlobContainer(_amsStorageAccountName, _amsStorageAccountKey, destContainerName); 56 | //sourceBlobContainer.CreateIfNotExists(); 57 | CloudBlockBlob myBlob = sourceBlobContainer.GetBlockBlobReference(name); 58 | // Copy Source Blob container into Destination Blob container that is associated with the asset. 59 | CopyBlobsAsync(myBlob, destinationBlobContainer, log); 60 | } 61 | catch (Exception ex) 62 | { 63 | log.Info("Exception " + ex); 64 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 65 | } 66 | 67 | return req.CreateResponse(HttpStatusCode.OK, new 68 | { 69 | CopyFileName = name, 70 | SourceContainer = srcContainerName 71 | }); 72 | } 73 | 74 | 75 | public class Blob 76 | { 77 | public string containerName { get; set; } 78 | public string name { get; set; } 79 | 80 | public Blob(string path) 81 | { 82 | containerName = path.Substring(1, (path.IndexOf('/', 1) - 1)); 83 | name = path.Substring(path.IndexOf('/', 1) + 1); 84 | } 85 | } 86 | 87 | static public CloudBlobContainer GetCloudBlobContainer(string storageAccountName, string storageAccountKey, string containerName) 88 | { 89 | CloudStorageAccount sourceStorageAccount = new CloudStorageAccount(new StorageCredentials(storageAccountName, storageAccountKey), true); 90 | CloudBlobClient sourceCloudBlobClient = sourceStorageAccount.CreateCloudBlobClient(); 91 | return sourceCloudBlobClient.GetContainerReference(containerName); 92 | } 93 | 94 | static public void CopyBlobsAsync(CloudBlockBlob sourceBlob, CloudBlobContainer destinationBlobContainer, TraceWriter log) 95 | { 96 | if (destinationBlobContainer.CreateIfNotExists()) 97 | { 98 | destinationBlobContainer.SetPermissions(new BlobContainerPermissions 99 | { 100 | PublicAccess = BlobContainerPublicAccessType.Blob 101 | }); 102 | } 103 | 104 | string blobPrefix = null; 105 | bool useFlatBlobListing = true; 106 | 107 | log.Info("Source blob : " + (sourceBlob as CloudBlob).Uri.ToString()); 108 | CloudBlob destinationBlob = destinationBlobContainer.GetBlockBlobReference((sourceBlob as CloudBlob).Name); 109 | if (destinationBlob.Exists()) 110 | { 111 | log.Info("Destination blob already exists. Skipping: " + destinationBlob.Uri.ToString()); 112 | } 113 | else 114 | { 115 | log.Info("Copying blob " + sourceBlob.Uri.ToString() + " to " + destinationBlob.Uri.ToString()); 116 | CopyBlobAsync(sourceBlob as CloudBlob, destinationBlob); 117 | } 118 | } 119 | 120 | static public async void CopyBlobAsync(CloudBlob sourceBlob, CloudBlob destinationBlob) 121 | { 122 | var signature = sourceBlob.GetSharedAccessSignature(new SharedAccessBlobPolicy 123 | { 124 | Permissions = SharedAccessBlobPermissions.Read, 125 | SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24) 126 | }); 127 | await destinationBlob.StartCopyAsync(new Uri(sourceBlob.Uri.AbsoluteUri + signature)); 128 | } -------------------------------------------------------------------------------- /workflow/import-mediaasset-singleblob-trigger/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using System.Text; 7 | using Newtonsoft.Json; 8 | using Microsoft.WindowsAzure.MediaServices.Client; 9 | using System.Threading.Tasks; 10 | using Microsoft.WindowsAzure.Storage; 11 | using Microsoft.WindowsAzure.Storage.Blob; 12 | using Microsoft.WindowsAzure.Storage.Auth; 13 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 14 | 15 | private static CloudMediaContext _context = null; 16 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 17 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 18 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 19 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 20 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 21 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 22 | 23 | public static async Task Run(CloudBlockBlob myBlob, string name, TraceWriter log) 24 | { 25 | log.Info($"C# Blob trigger function Processed blob\n Name:{name} \n URI:{myBlob.StorageUri}"); 26 | 27 | IAsset newAsset = null; 28 | IIngestManifest manifest = null; 29 | try 30 | { 31 | // Load AMS account context 32 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 33 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 34 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 35 | AzureEnvironments.AzureCloudEnvironment); 36 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 37 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 38 | 39 | //log.Info("Using Azure Media Services account : " + _mediaServicesAccountName); 40 | //_context = new CloudMediaContext(new MediaServicesCredentials(_mediaServicesAccountName, _mediaServicesAccountKey)); 41 | 42 | // Create Asset 43 | newAsset = _context.Assets.Create(name, AssetCreationOptions.None); 44 | log.Info("Created Azure Media Services Asset : "); 45 | log.Info(" - Asset Name = " + name); 46 | log.Info(" - Asset Creation Option = " + AssetCreationOptions.None); 47 | 48 | // Setup blob container 49 | //CloudBlobContainer sourceBlobContainer = GetCloudBlobContainer(_sourceStorageAccountName, _sourceStorageAccountKey, config.IngestSource.SourceContainerName); 50 | CloudBlobContainer destinationBlobContainer = GetCloudBlobContainer(_amsStorageAccountName, _amsStorageAccountKey, newAsset.Uri.Segments[1]); 51 | //sourceBlobContainer.CreateIfNotExists(); 52 | // Copy Source Blob container into Destination Blob container that is associated with the asset. 53 | CopyBlobsAsync(myBlob, destinationBlobContainer, log); 54 | } 55 | catch (Exception ex) 56 | { 57 | log.Info("Exception " + ex); 58 | return new HttpResponseMessage(HttpStatusCode.BadRequest); 59 | } 60 | 61 | log.Info("Asset ID : " + newAsset.Id); 62 | //log.Info("Source Container : " + config.IngestSource.SourceContainerName); 63 | 64 | AMSImportResponse data = new AMSImportResponse(); 65 | data.AssetId = newAsset.Id; 66 | data.DestinationContainer = newAsset.Uri.Segments[1]; 67 | var json = JsonConvert.SerializeObject(data, Formatting.Indented); 68 | 69 | return new HttpResponseMessage(HttpStatusCode.OK) 70 | { 71 | Content = new StringContent(json, Encoding.UTF8, "application/json") 72 | }; 73 | } 74 | 75 | public class AMSImportResponse 76 | { 77 | public string AssetId { get; set; } 78 | public string DestinationContainer { get; set; } 79 | } 80 | 81 | static public CloudBlobContainer GetCloudBlobContainer(string storageAccountName, string storageAccountKey, string containerName) 82 | { 83 | CloudStorageAccount sourceStorageAccount = new CloudStorageAccount(new StorageCredentials(storageAccountName, storageAccountKey), true); 84 | CloudBlobClient sourceCloudBlobClient = sourceStorageAccount.CreateCloudBlobClient(); 85 | return sourceCloudBlobClient.GetContainerReference(containerName); 86 | } 87 | 88 | static public void CopyBlobsAsync(CloudBlockBlob sourceBlob, CloudBlobContainer destinationBlobContainer, TraceWriter log) 89 | { 90 | if (destinationBlobContainer.CreateIfNotExists()) 91 | { 92 | destinationBlobContainer.SetPermissions(new BlobContainerPermissions 93 | { 94 | PublicAccess = BlobContainerPublicAccessType.Blob 95 | }); 96 | } 97 | 98 | string blobPrefix = null; 99 | bool useFlatBlobListing = true; 100 | 101 | log.Info("Source blob : " + (sourceBlob as CloudBlob).Uri.ToString()); 102 | CloudBlob destinationBlob = destinationBlobContainer.GetBlockBlobReference((sourceBlob as CloudBlob).Name); 103 | if (destinationBlob.Exists()) 104 | { 105 | log.Info("Destination blob already exists. Skipping: " + destinationBlob.Uri.ToString()); 106 | } 107 | else 108 | { 109 | log.Info("Copying blob " + sourceBlob.Uri.ToString() + " to " + destinationBlob.Uri.ToString()); 110 | CopyBlobAsync(sourceBlob as CloudBlob, destinationBlob); 111 | } 112 | } 113 | 114 | static public async void CopyBlobAsync(CloudBlob sourceBlob, CloudBlob destinationBlob) 115 | { 116 | var signature = sourceBlob.GetSharedAccessSignature(new SharedAccessBlobPolicy 117 | { 118 | Permissions = SharedAccessBlobPermissions.Read, 119 | SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24) 120 | }); 121 | await destinationBlob.StartCopyAsync(new Uri(sourceBlob.Uri.AbsoluteUri + signature)); 122 | } 123 | 124 | -------------------------------------------------------------------------------- /workflow/publish-assets/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | using Microsoft.WindowsAzure.MediaServices.Client; 9 | using Microsoft.WindowsAzure.Storage; 10 | using Microsoft.WindowsAzure.Storage.Blob; 11 | using Microsoft.WindowsAzure.Storage.Auth; 12 | using System.Threading.Tasks; 13 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 14 | using System.Text.RegularExpressions; 15 | 16 | private static CloudMediaContext _context = null; 17 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 18 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 19 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 20 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 21 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 22 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 23 | 24 | class Meta 25 | { 26 | public string id; 27 | public string name; 28 | public string asset_name; 29 | public string thumbnail_url; 30 | public string manifest_url; 31 | public string lang; 32 | public string last_updated_time; 33 | public string description; 34 | public string webvtt_url; 35 | public object subtitle_urls; 36 | } 37 | 38 | public static async Task Run(HttpRequestMessage req, IAsyncCollector outputDocument, IAsyncCollector outputQueueItem, TraceWriter log) 39 | { 40 | log.Info($"Webhook was triggered!"); 41 | 42 | string jsonContent = await req.Content.ReadAsStringAsync(); 43 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 44 | log.Info("Request : " + jsonContent); 45 | 46 | // Validate input objects 47 | if (data.AssetIds == null) 48 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass AssetIds in the input object" }); 49 | log.Info("Input - AssetIds : " + data.AssetIds); 50 | 51 | string[] assetids = data.AssetIds.ToObject(); 52 | string streamingUrl = ""; 53 | try 54 | { 55 | // Load AMS account context 56 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 57 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 58 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 59 | AzureEnvironments.AzureCloudEnvironment); 60 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 61 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 62 | 63 | Meta meta = new Meta(); 64 | List subtitleUrlList = new List(); 65 | 66 | foreach (string assetid in assetids) 67 | { 68 | // Get the Asset 69 | var asset = _context.Assets.Where(a => a.Id == assetid).FirstOrDefault(); 70 | if (asset == null) 71 | { 72 | log.Info("Asset not found - " + assetid); 73 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Asset not found" }); 74 | } 75 | log.Info("Asset found, Asset ID : " + asset.Id); 76 | 77 | // Publish with a streaming locator 78 | IAccessPolicy streamingAccessPolicy = _context.AccessPolicies.Create("streamingAccessPolicy", TimeSpan.FromDays(365), AccessPermissions.Read); 79 | ILocator outputLocator = _context.Locators.CreateLocator(LocatorType.OnDemandOrigin, asset, streamingAccessPolicy, DateTime.UtcNow.AddMinutes(-5)); 80 | 81 | var manifestFile = asset.AssetFiles.Where(f => f.Name.ToLower().EndsWith(".ism")).FirstOrDefault(); 82 | if (manifestFile != null && outputLocator != null) 83 | { 84 | streamingUrl = outputLocator.Path + manifestFile.Name + "/manifest"; 85 | meta.id = assetid; 86 | meta.manifest_url = streamingUrl; 87 | meta.name = asset.Name; 88 | meta.asset_name = asset.Name; 89 | log.Info("Streaming URL : " + streamingUrl); 90 | } 91 | 92 | // Get thumbnail_url 93 | var thumbnailFile = asset.AssetFiles.Where(f => f.Name.ToLower().EndsWith(".png")).FirstOrDefault(); 94 | if (thumbnailFile != null && outputLocator != null) { 95 | meta.thumbnail_url = outputLocator.Path + thumbnailFile.Name; 96 | log.Info("thumbnail url : " + meta.thumbnail_url); 97 | } 98 | 99 | // Get caption and subtitle webvtts in the output asset 100 | IEnumerable webvtts = asset 101 | .AssetFiles 102 | .ToList() 103 | .Where(af => af.Name.EndsWith(".vtt", StringComparison.OrdinalIgnoreCase)); 104 | 105 | foreach(IAssetFile af in webvtts) 106 | { 107 | var filename = af.Name; 108 | if (filename.EndsWith("_aud_SpReco.vtt", StringComparison.OrdinalIgnoreCase) ) { 109 | meta.webvtt_url = outputLocator.Path + filename; 110 | log.Info("webvtt url : " + meta.webvtt_url); 111 | } 112 | if (filename.StartsWith("subtitle", StringComparison.OrdinalIgnoreCase) ) { 113 | 114 | Match matched = Regex.Match(filename, @"subtitle-(.*)\.vtt"); 115 | if (matched.Success) 116 | { 117 | var matched_lang = matched.Groups[1].Value; 118 | subtitleUrlList.Add( 119 | new { 120 | lang = matched_lang, 121 | webvtt_url = outputLocator.Path + filename 122 | } 123 | ); 124 | } 125 | } 126 | } 127 | } 128 | 129 | object outdoc = new { 130 | id = meta.id, 131 | name = meta.name, 132 | asset_name = meta.asset_name, 133 | thumbnail_url=meta.thumbnail_url, 134 | lang = "en", 135 | description = "dummy description", 136 | manifest_url = meta.manifest_url, 137 | webvtt_url = meta.webvtt_url, 138 | subtitle_urls = subtitleUrlList.ToArray(), 139 | }; 140 | await outputDocument.AddAsync(outdoc); 141 | 142 | string outqueue = meta.id; 143 | await outputQueueItem.AddAsync(outqueue); 144 | 145 | } 146 | catch (Exception ex) 147 | { 148 | log.Info("Exception " + ex); 149 | return req.CreateResponse(HttpStatusCode.BadRequest); 150 | } 151 | 152 | return req.CreateResponse(HttpStatusCode.OK, new 153 | { 154 | StreamingUrl = streamingUrl 155 | }); 156 | } 157 | 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | services: media-services, functions, comsmosdb, search 3 | platforms: dotnet, python 4 | author: shigeyf, yoichika 5 | --- 6 | 7 | # ai-digitalmedia 8 | 9 | A sample demo application for AI powered digitial video library portal on Microsoft Azure cloud platform. 10 | 11 | This sample demo application shall focus on: 12 | * Transforming customer’s digital media assets on-prem into Azure 13 | * Driving media intelligence leveraged by Video AI 14 | * Online Video Portal (OVP) with indexing and search capability 15 | 16 | 17 | ## What Azure Workloads are used in this sample demo application: 18 | - Hybrid Storage (StorSimple) 19 | - StorSimple Data Transformation Manager 20 | - Azure Media Services 21 | - Azure Media Analytics 22 | - Azure Functions 23 | - Azure Logic Apps 24 | - Azure Search 25 | - Azure Web App for Container 26 | - Cosmos DB 27 | 28 | 29 | ## How to deploy this sample demo application 30 | 31 | ### 1. [Fork this](https://github.com/shigeyf/ai-digitalmedia#fork-destination-box) to your own repo 32 | 33 | ### 2. Create an Azure Media Services account 34 | 35 | Create a Media Services account in your subscription if don't have it already. 36 | 37 | 38 | 39 | * Settings 40 | - **Media Services Account Name**: specify any name 41 | - **Storage Account Name**: specify any name 42 | - **Storage Option**: *Starndard_LSR* is enough for this demo 43 | 44 | ### 3. Create a Service Principal 45 | 46 | Create a Service Principal and save the password. It will be needed in step 6. 47 | To do so, go to the API tab in the account ([follow this article](https://docs.microsoft.com/en-us/azure/media-services/media-services-portal-get-started-with-aad#service-principal-authentication)) 48 | 49 | ### 4. Make sure the AMS streaming endpoint is started 50 | 51 | To enable streaming, go to the Azure portal, select the Azure Media Services account which has been created, and start the default streaming endpoint. 52 | 53 | ### 5. Create an Azure Search Service account 54 | 55 | Create a Azure Search accountin your subscription. 56 | 57 | 58 | 59 | * Settings 60 | - **Name**: specify any name 61 | - **Sku**: *basic* is enough for this demo 62 | - **Replica Count**: *1* is enough for this demo 63 | - **Partition Count**: *1* is enough for this demo 64 | - **Hosting Mode**: *default* is enough for this demo 65 | 66 | ### 6. Create an Azure COSMOS Database account 67 | 68 | Create a Cosmos DB accountin your subscription. 69 | 70 | 71 | 72 | * Settings 73 | - **Database Account Name**: specify any name 74 | 75 | ### 7. Deploy sample media functions into an Azure Functions account 76 | 77 | Create a Function account with this sample code of media functions in your subscription. 78 | 79 | 80 | 81 | * This deployment script will not create an Azure Media Services account and an Azure Storage account 82 | * Please consider Consumption Plan or App Service Plan if you will deploy manually 83 | * Consumption Plan – Timeout of function will be 5 mins 84 | * App Service Plan (Dedicated Plan) – There is no timeout (if AlwaysOn is enabled) 85 | * If a deployment target resource group already contains an App Service Plan (Dedicated Plan), Azure Functions app will be contained in that App Service Plan (Dedicated Plan) 86 | 87 | * Settings 88 | - **Function App Name**: specify any name 89 | - **Function Key**: Use as is 90 | - **Source Code Repository URL**: Use your github repo 91 | - **Source Code Branch**: Use your branch in your github repo 92 | - **Media Services Account Azure Active Directory Tenant Domain**: specify your own Azure AD domain for AMS account access 93 | - **Media Services Account Rest Api Endpoint**: specify your own AMS Api Endpoint (see Step 3) 94 | - **Media Services Account Service Principal Client Id**: specify your own SPN for AMS access (see Step 3) 95 | - **Media Services Account Service Principal Client Secret**: specify your own SPN for AMS access (see Step 3) 96 | - **Media Services Storage Account Name**: specify your AMS storage account created at Step 2 97 | - **Cosmosdb Account Name**: specify your COSMOS DB account name created at Step 6 98 | - **Search Service Name**: specify your Azure Search account name created at Step 5 99 | 100 | ### 8. Create blob container 101 | 102 | Create a container for importing media files in *mediaimportXXX* Azure Blob Storage account (*XXX* is random string). 103 | 104 | ### 9. Deploy sample media workflow into an Azure Logic App account 105 | 106 | Create a Logic App account with sample media workflow in your subscription. 107 | 108 | 109 | 110 | * Settings 111 | - **Logic App Name**: specify any name 112 | - **Function Deployment Resource Group**: Specify Resource Group used in Step 7 113 | - **Function Deployment Name**: Specify created Function App Name created in Step 7 114 | - **Source Asses Storage Account Name**: Specify created Storage Account Name created in Step 7 115 | - **IngestMedia Watch Container**: Specify container name created in Step 8 116 | 117 | ### 10. Deploy sample web portal for digital media library 118 | 119 | Create a Web App account with sample portal app code in your subscription. 120 | 121 | 122 | 123 | * Settings 124 | - **Site Name**: specify web site name 125 | - **Hostting Plan Name**: specify Linux App Service Plan Name 126 | - **Sku**: specify Linux App Service Plan pricing tier 127 | - **Worker Size**: *0* is enough for this demo 128 | - **Repo URL**: Use your github repo 129 | - **Branch**: Use your branch in your github repo 130 | - **Cosmosdb Account Name**: specify your COSMOS DB account name created at Step 6 131 | - **Search Service Name**: specify your Azure Search account name created at Step 5 132 | 133 | ## How to try this sample demo application 134 | 135 | 1. Open deployed web site http://*sitename*.azurewebsites.net/ (*) *sitename* is specified at Step 10. 136 | 2. When opening the web site, the message "Action Required" will be shown, then click "Start Service" button in the web site. 137 | 3. Click "Continue". 138 | 4. Upload video files into blob container specified at Step 8. 139 | -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/src/Client.php: -------------------------------------------------------------------------------- 1 | uri = $uri; 41 | $this->key = $key; 42 | } 43 | 44 | /** 45 | * Set API version to use. 46 | * 47 | * @param string $version API version date 48 | */ 49 | public function setApiVersion($version) 50 | { 51 | $this->apiVersion = $version; 52 | } 53 | 54 | /** 55 | * Makes a REST API call 56 | * 57 | * @param string $verb Request Method (HEAD, GET, POST, PUT, DELETE) 58 | * @param string $resourcePath Requested resource path 59 | * @param string $resourceType Requested resource type 60 | * @param string $resourceId Requested resource id 61 | * @param array $payload Posted data 62 | * @param array $extraHeaders Additional request headers 63 | * 64 | * @return array 65 | * @throws \Exception 66 | */ 67 | public function request( 68 | $verb, 69 | $resourcePath, 70 | $resourceType, 71 | $resourceId = '', 72 | array $payload = [], 73 | array $extraHeaders = [] 74 | ){ 75 | $query = false; 76 | $length = 0; 77 | $data = ''; 78 | if (!empty($payload)) { 79 | $query = (isset($payload['query'])) ? true : $query; 80 | $data = json_encode($payload); 81 | $length = strlen($data); 82 | } 83 | 84 | $headers = $this->generateRequestHeaders($verb, $resourceType, $resourceId, $query, $length, $extraHeaders); 85 | $url = $this->uri . '/' . trim(trim($resourcePath), '/'); 86 | 87 | $options = [ 88 | CURLOPT_URL => $url, 89 | CURLOPT_SSLVERSION => CURL_SSLVERSION_DEFAULT, 90 | CURLOPT_RETURNTRANSFER => true, 91 | CURLOPT_VERBOSE => static::$debug, 92 | CURLOPT_HTTPHEADER => $headers, 93 | CURLOPT_HEADER => true, 94 | ]; 95 | 96 | if ($verb === Verbs::POST) { 97 | $options[CURLOPT_POST] = true; 98 | } 99 | 100 | if ($verb === Verbs::HEAD) { 101 | $options[CURLOPT_NOBODY] = true; 102 | } 103 | 104 | if (in_array($verb, [Verbs::POST, Verbs::PUT, Verbs::DELETE])) { 105 | $options[CURLOPT_POSTFIELDS] = $data; 106 | } 107 | 108 | if (!in_array($verb, [Verbs::HEAD, Verbs::GET, Verbs::POST])) { 109 | $options[CURLOPT_CUSTOMREQUEST] = $verb; 110 | } 111 | 112 | if (true === static::$debug) { 113 | print_r($options); 114 | } 115 | 116 | try { 117 | $ch = curl_init(); 118 | curl_setopt_array($ch, $options); 119 | $result = curl_exec($ch); 120 | $info = curl_getinfo($ch); 121 | curl_close($ch); 122 | 123 | $headerArray = []; 124 | $headers = explode("\n", substr($result, 0, $info['header_size'])); 125 | foreach ($headers as $header){ 126 | $firstColonPosition = strpos($header, ':'); 127 | if(!empty($header) && false !== $firstColonPosition && $firstColonPosition > 0){ 128 | $key = substr($header, 0, $firstColonPosition); 129 | $value = substr($header, $firstColonPosition+1); 130 | $headerArray[$key] = $value; 131 | } 132 | } 133 | $info['response_headers'] = $headerArray; 134 | $body = substr($result, $info['header_size']); 135 | 136 | $response = json_decode($body, true); 137 | $response['_curl_info'] = $info; 138 | 139 | return $response; 140 | } catch (\Exception $e) { 141 | throw $e; 142 | } 143 | } 144 | 145 | /** 146 | * Generates request headers based on request options. 147 | * 148 | * @param string $verb Request Method (HEAD, GET, POST, PUT, DELETE) 149 | * @param string $resourceType Requested resource type 150 | * @param string $resourceId Requested resource id 151 | * @param bool $isQuery Indicates if the request is query or not 152 | * @param int $contentLength Content length of posted data 153 | * @param array $extraHeaders Additional request headers 154 | * 155 | * @return array Array of request headers 156 | */ 157 | protected function generateRequestHeaders( 158 | $verb, 159 | $resourceType, 160 | $resourceId, 161 | $isQuery = false, 162 | $contentLength = 0, 163 | array $extraHeaders = [] 164 | ){ 165 | $xMsDate = gmdate('D, d M Y H:i:s T'); 166 | $headers = [ 167 | 'Accept: application/json', 168 | 'User-Agent: ' . static::USER_AGENT, 169 | 'Cache-Control: no-cache', 170 | 'x-ms-date: ' . $xMsDate, 171 | 'x-ms-version: ' . $this->apiVersion, 172 | 'Authorization: ' . $this->generateAuthHeader($verb, $xMsDate, $resourceType, $resourceId) 173 | ]; 174 | 175 | if (in_array($verb, [Verbs::POST, Verbs::PUT])) { 176 | $headers[] = 'Content-Length: ' . $contentLength; 177 | 178 | if ($isQuery === true) { 179 | $headers[] = 'Content-Type: application/query+json'; 180 | $headers[] = 'x-ms-documentdb-isquery: True'; 181 | } else { 182 | $headers[] = 'Content-Type: application/json'; 183 | } 184 | } 185 | 186 | return array_merge($headers, $extraHeaders); 187 | } 188 | 189 | /** 190 | * Generates the request Authorization header. 191 | * 192 | * @link http://msdn.microsoft.com/en-us/library/azure/dn783368.aspx 193 | * 194 | * @param string $verb Request Method (HEAD, GET, POST, PUT, DELETE) 195 | * @param string $xMsDate Request date/time string 196 | * @param string $resourceType Requested resource Type 197 | * @param string $resourceId Requested resource ID 198 | * 199 | * @return string Authorization string 200 | */ 201 | private function generateAuthHeader($verb, $xMsDate, $resourceType, $resourceId) 202 | { 203 | $master = 'master'; 204 | $token = '1.0'; 205 | $key = base64_decode($this->key); 206 | $stringToSign = $verb . "\n" . 207 | $resourceType . "\n" . 208 | $resourceId . "\n" . 209 | $xMsDate . "\n" . 210 | "\n"; 211 | $sig = base64_encode(hash_hmac('sha256', strtolower($stringToSign), $key, true)); 212 | 213 | return urlencode("type=$master&ver=$token&sig=$sig"); 214 | } 215 | } -------------------------------------------------------------------------------- /workflow/submit-media-job/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | 4 | using System; 5 | using System.Net; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | using Microsoft.WindowsAzure.MediaServices.Client; 9 | using Microsoft.WindowsAzure.Storage; 10 | using Microsoft.WindowsAzure.Storage.Blob; 11 | using Microsoft.WindowsAzure.Storage.Auth; 12 | using System.Threading.Tasks; 13 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 14 | 15 | private static CloudMediaContext _context = null; 16 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 17 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 18 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 19 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 20 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 21 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 22 | 23 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 24 | { 25 | log.Info($"Webhook was triggered!"); 26 | 27 | string jsonContent = await req.Content.ReadAsStringAsync(); 28 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 29 | log.Info("Request : " + jsonContent); 30 | 31 | // Validate input objects 32 | if (data.AssetId == null) 33 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass AssetId in the input object" }); 34 | log.Info("Input - AssetId : " + data.AssetId); 35 | if (data.Tasks == null) 36 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass Tasks in the input object" }); 37 | log.Info("Input - Tasks : " + data.Tasks); 38 | 39 | string assetid = data.AssetId; 40 | string[] tasks = data.Tasks.ToObject(); 41 | 42 | IJob job = null; 43 | List mediaTaskList = new List(); 44 | //IAsset outputAsset = null; 45 | //IAsset outputEncoding = null; 46 | try 47 | { 48 | // Load AMS account context 49 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 50 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 51 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 52 | AzureEnvironments.AzureCloudEnvironment); 53 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 54 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 55 | 56 | // Get the Asset 57 | var asset = _context.Assets.Where(a => a.Id == assetid).FirstOrDefault(); 58 | if (asset == null) 59 | { 60 | log.Info("Asset not found - " + assetid); 61 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Asset not found" }); 62 | } 63 | log.Info("Asset found, AssetId : " + asset.Id); 64 | 65 | int taskindex = 0; 66 | // Declare a new Media Processing job 67 | job = _context.Jobs.Create("Azure Functions - Media Processing Job - " + assetid); 68 | foreach (string proc in tasks) 69 | { 70 | log.Info("Executing Tasks : " + proc); 71 | MediaTask t = new MediaTask(); 72 | t.index = taskindex; 73 | t.processorName = proc; 74 | AddTask(job, asset, proc, null, false); 75 | mediaTaskList.Add(t); 76 | taskindex++; 77 | } 78 | job.Submit(); 79 | log.Info("Job Submitted"); 80 | 81 | } 82 | catch (Exception ex) 83 | { 84 | log.Info("Exception " + ex); 85 | return req.CreateResponse(HttpStatusCode.BadRequest); 86 | } 87 | 88 | JObject j = new JObject(); 89 | j["JobId"] = job.Id; 90 | List assets = new List(); 91 | foreach (var t in mediaTaskList) 92 | { 93 | t.outputAsset = job.OutputMediaAssets[t.index]; 94 | t.task = job.Tasks[t.index]; 95 | var jt = new JObject(); 96 | jt["AssetId"] = t.outputAsset.Id; 97 | jt["TasktId"] = t.task.Id; 98 | j[t.processorName] = jt; 99 | assets.Add((string)(t.outputAsset.Id)); 100 | } 101 | log.Info("test : " + job.Id); 102 | var array = JArray.FromObject(assets); 103 | j["AssetIds"] = array; 104 | 105 | log.Info("Job Id: " + job.Id); 106 | foreach (var t in mediaTaskList) 107 | { 108 | log.Info("Task : " + t.processorName + " (" + t.outputAsset.Id + ")"); 109 | } 110 | return req.CreateResponse(HttpStatusCode.OK, j); 111 | } 112 | 113 | public class MediaTask 114 | { 115 | public int index { get; set; } 116 | public string processorName { get; set; } 117 | public IAsset outputAsset { get; set; } 118 | public ITask task { get; set; } 119 | } 120 | 121 | static public IMediaProcessor GetLatestMediaProcessorByName(string mediaProcessorName) 122 | { 123 | var processor = _context.MediaProcessors.Where(p => p.Name == mediaProcessorName). 124 | ToList().OrderBy(p => new Version(p.Version)).LastOrDefault(); 125 | 126 | if (processor == null) 127 | throw new ArgumentException(string.Format("Unknown media processor", mediaProcessorName)); 128 | 129 | return processor; 130 | } 131 | 132 | static public IAsset AddTask(IJob job, IAsset sourceAsset, string processor, string presetString, bool isPresetFile, int priority = 10) 133 | { 134 | // Get a media processor reference, 135 | // and pass to it the name of the processor to use for the specific task. 136 | IMediaProcessor mediaProcessor = GetLatestMediaProcessorByName(processor); 137 | 138 | if (presetString == null) { 139 | switch (processor) { 140 | case "Media Encoder Standard": 141 | presetString = "encodingmbr.json"; 142 | isPresetFile = true; 143 | break; 144 | case "Azure Media Indexer 2 Preview": 145 | presetString = "Indexer-v2.json"; 146 | isPresetFile = true; 147 | break; 148 | default: 149 | break; 150 | } 151 | } 152 | 153 | string taskConfiguration = null; 154 | if (isPresetFile) 155 | { 156 | string repoPath = Environment.GetEnvironmentVariable("HOME", EnvironmentVariableTarget.Process); 157 | string presetPath; 158 | if (repoPath == null) 159 | { 160 | presetPath = @"Presets/" + presetString; 161 | } 162 | else 163 | { 164 | presetPath = Path.Combine(repoPath, @"site\wwwroot\Presets\" + presetString); 165 | } 166 | taskConfiguration = File.ReadAllText(presetPath); 167 | } 168 | else { 169 | taskConfiguration = presetString; 170 | } 171 | 172 | // Create a task with the encoding details, using a string preset. 173 | var task = job.Tasks.AddNew("Azure Functions:" + processor + " task", mediaProcessor, taskConfiguration, TaskOptions.None); 174 | task.Priority = priority; 175 | 176 | // Specify the input asset to be indexed. 177 | task.InputAssets.Add(sourceAsset); 178 | 179 | IAsset outputAsset = null; 180 | // Add an output asset to contain the results of the job. 181 | outputAsset = task.OutputAssets.AddNew(sourceAsset.Name + " - Media Processor - " + processor + " (by Functions Workflow)", AssetCreationOptions.None); 182 | 183 | return outputAsset; 184 | } 185 | 186 | public static string ReturnId(IJob job, int index) 187 | { 188 | return index > -1 ? job.OutputMediaAssets[index].Id : null; 189 | } 190 | 191 | public static string ReturnTaskId(IJob job, int index) 192 | { 193 | return index > -1 ? job.Tasks[index].Id : null; 194 | } -------------------------------------------------------------------------------- /portal/apache2.conf: -------------------------------------------------------------------------------- 1 | # This is the main Apache server configuration file. It contains the 2 | # configuration directives that give the server its instructions. 3 | # See http://httpd.apache.org/docs/2.4/ for detailed information about 4 | # the directives and /usr/share/doc/apache2/README.Debian about Debian specific 5 | # hints. 6 | # 7 | # 8 | # Summary of how the Apache 2 configuration works in Debian: 9 | # The Apache 2 web server configuration in Debian is quite different to 10 | # upstream's suggested way to configure the web server. This is because Debian's 11 | # default Apache2 installation attempts to make adding and removing modules, 12 | # virtual hosts, and extra configuration directives as flexible as possible, in 13 | # order to make automating the changes and administering the server as easy as 14 | # possible. 15 | 16 | # It is split into several files forming the configuration hierarchy outlined 17 | # below, all located in the /etc/apache2/ directory: 18 | # 19 | # /etc/apache2/ 20 | # |-- apache2.conf 21 | # | `-- ports.conf 22 | # |-- mods-enabled 23 | # | |-- *.load 24 | # | `-- *.conf 25 | # |-- conf-enabled 26 | # | `-- *.conf 27 | # `-- sites-enabled 28 | # `-- *.conf 29 | # 30 | # 31 | # * apache2.conf is the main configuration file (this file). It puts the pieces 32 | # together by including all remaining configuration files when starting up the 33 | # web server. 34 | # 35 | # * ports.conf is always included from the main configuration file. It is 36 | # supposed to determine listening ports for incoming connections which can be 37 | # customized anytime. 38 | # 39 | # * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ 40 | # directories contain particular configuration snippets which manage modules, 41 | # global configuration fragments, or virtual host configurations, 42 | # respectively. 43 | # 44 | # They are activated by symlinking available configuration files from their 45 | # respective *-available/ counterparts. These should be managed by using our 46 | # helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See 47 | # their respective man pages for detailed information. 48 | # 49 | # * The binary is called apache2. Due to the use of environment variables, in 50 | # the default configuration, apache2 needs to be started/stopped with 51 | # /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not 52 | # work with the default configuration. 53 | 54 | 55 | # Global configuration 56 | # 57 | 58 | # 59 | # ServerRoot: The top of the directory tree under which the server's 60 | # configuration, error, and log files are kept. 61 | # 62 | # NOTE! If you intend to place this on an NFS (or otherwise network) 63 | # mounted filesystem then please read the Mutex documentation (available 64 | # at ); 65 | # you will save yourself a lot of trouble. 66 | # 67 | # Do NOT add a slash at the end of the directory path. 68 | # 69 | #ServerRoot "/etc/apache2" 70 | 71 | # 72 | # The accept serialization lock file MUST BE STORED ON A LOCAL DISK. 73 | # 74 | Mutex file:${APACHE_LOCK_DIR} default 75 | 76 | # 77 | # PidFile: The file in which the server should record its process 78 | # identification number when it starts. 79 | # This needs to be set in /etc/apache2/envvars 80 | # 81 | PidFile ${APACHE_PID_FILE} 82 | 83 | # 84 | # Timeout: The number of seconds before receives and sends time out. 85 | # 86 | Timeout 300 87 | 88 | # 89 | # KeepAlive: Whether or not to allow persistent connections (more than 90 | # one request per connection). Set to "Off" to deactivate. 91 | # 92 | KeepAlive On 93 | 94 | # 95 | # MaxKeepAliveRequests: The maximum number of requests to allow 96 | # during a persistent connection. Set to 0 to allow an unlimited amount. 97 | # We recommend you leave this number high, for maximum performance. 98 | # 99 | MaxKeepAliveRequests 100 100 | 101 | # 102 | # KeepAliveTimeout: Number of seconds to wait for the next request from the 103 | # same client on the same connection. 104 | # 105 | KeepAliveTimeout 5 106 | 107 | 108 | # These need to be set in /etc/apache2/envvars 109 | User ${APACHE_RUN_USER} 110 | Group ${APACHE_RUN_GROUP} 111 | 112 | # 113 | # HostnameLookups: Log the names of clients or just their IP addresses 114 | # e.g., www.apache.org (on) or 204.62.129.132 (off). 115 | # The default is off because it'd be overall better for the net if people 116 | # had to knowingly turn this feature on, since enabling it means that 117 | # each client request will result in AT LEAST one lookup request to the 118 | # nameserver. 119 | # 120 | HostnameLookups Off 121 | 122 | # ErrorLog: The location of the error log file. 123 | # If you do not specify an ErrorLog directive within a 124 | # container, error messages relating to that virtual host will be 125 | # logged here. If you *do* define an error logfile for a 126 | # container, that host's errors will be logged there and not here. 127 | # 128 | ErrorLog ${APACHE_LOG_DIR}/error.log 129 | 130 | # 131 | # LogLevel: Control the severity of messages logged to the error_log. 132 | # Available values: trace8, ..., trace1, debug, info, notice, warn, 133 | # error, crit, alert, emerg. 134 | # It is also possible to configure the log level for particular modules, e.g. 135 | # "LogLevel info ssl:warn" 136 | # 137 | LogLevel warn 138 | 139 | # Include module configuration: 140 | IncludeOptional mods-enabled/*.load 141 | IncludeOptional mods-enabled/*.conf 142 | 143 | 144 | # Added ServerName 145 | ServerName localhost 146 | 147 | # ports.conf 148 | Listen {PORT} 149 | 150 | # Sets the default security model of the Apache2 HTTPD server. It does 151 | # not allow access to the root filesystem outside of /usr/share and /var/www. 152 | # The former is used by web applications packaged in Debian, 153 | # the latter may be used for local directories served by the web server. If 154 | # your system is serving content from a sub-directory in /srv you must allow 155 | # access here, or in any related virtual host. 156 | 157 | Options FollowSymLinks 158 | AllowOverride None 159 | Require all denied 160 | 161 | 162 | 163 | AllowOverride None 164 | Require all granted 165 | 166 | 167 | 168 | Options Indexes FollowSymLinks 169 | AllowOverride None 170 | Require all granted 171 | 172 | 173 | # 174 | # Options Indexes FollowSymLinks 175 | # AllowOverride None 176 | # Require all granted 177 | # 178 | 179 | 180 | 181 | 182 | # AccessFileName: The name of the file to look for in each directory 183 | # for additional configuration directives. See also the AllowOverride 184 | # directive. 185 | # 186 | AccessFileName .htaccess 187 | 188 | # 189 | # The following lines prevent .htaccess and .htpasswd files from being 190 | # viewed by Web clients. 191 | # 192 | 193 | Require all denied 194 | 195 | 196 | 197 | # 198 | # The following directives define some format nicknames for use with 199 | # a CustomLog directive. 200 | # 201 | # These deviate from the Common Log Format definitions in that they use %O 202 | # (the actual bytes sent including headers) instead of %b (the size of the 203 | # requested file), because the latter makes it impossible to detect partial 204 | # requests. 205 | # 206 | # Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. 207 | # Use mod_remoteip instead. 208 | # 209 | LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined 210 | LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined 211 | LogFormat "%h %l %u %t \"%r\" %>s %O" common 212 | LogFormat "%{Referer}i -> %U" referer 213 | LogFormat "%{User-agent}i" agent 214 | 215 | # Include of directories ignores editors' and dpkg's backup files, 216 | # see README.Debian for details. 217 | 218 | # Include generic snippets of statements 219 | IncludeOptional conf-enabled/*.conf 220 | 221 | # Include the virtual host configurations: 222 | IncludeOptional sites-enabled/*.conf 223 | 224 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 225 | -------------------------------------------------------------------------------- /portal/api/service-init-api.php: -------------------------------------------------------------------------------- 1 | get($db_name); 58 | if (!array_key_exists('id', $db_get_ret) ) { 59 | if ( array_key_exists('code', $db_get_ret) && $db_get_ret['code'] == 'NotFound' ) { 60 | $db_create_ret = $db->create(['id'=>$db_name]); 61 | if ( !array_key_exists('id', $db_create_ret) || $db_create_ret['id'] != $db_name ) { 62 | return false; 63 | } 64 | } 65 | } 66 | $coll = new \DreamFactory\DocumentDb\Resources\Collection($client, $db_name); 67 | $coll_get_ret=$coll->get($coll_name); 68 | if (!array_key_exists('id', $coll_get_ret) ) { 69 | if ( array_key_exists('code', $coll_get_ret) && $coll_get_ret['code'] == 'NotFound' ) { 70 | $coll_create_ret = $coll->create(['id'=>$coll_name]); 71 | if ( !array_key_exists('id', $coll_create_ret) || $coll_create_ret['id'] != $coll_name ) { 72 | return false; 73 | } 74 | } 75 | } 76 | return true; 77 | } 78 | 79 | function init_azuresearch($service_name, $api_key, $api_version, $index_name_prefix ) { 80 | // Get index list 81 | $index_names = get_index_list($service_name, $api_key, $api_version); 82 | if (!is_array($index_names)) { 83 | //print 'Error init_azuresearch:: Get Index List!\n'; 84 | return false; 85 | } 86 | // Create an index for Content only if it doesn't exist 87 | $INDEX_NAME_CONTENT='content'; 88 | if (!in_array('content', $index_names)) { 89 | $post_body ="{ 90 | \"name\": \"content\", 91 | \"fields\": [ 92 | {\"name\":\"id\", \"type\":\"Edm.String\", \"key\":true, \"retrievable\":true, \"searchable\":false, \"filterable\":false, \"sortable\":false, \"facetable\":false}, 93 | {\"name\":\"content_id\", \"type\":\"Edm.String\", \"retrievable\":true, \"searchable\":false, \"filterable\":true, \"sortable\":false, \"facetable\":false}, 94 | {\"name\":\"content_text\", \"type\":\"Edm.String\", \"retrievable\":true, \"searchable\":true, \"filterable\":false, \"sortable\":false, \"facetable\":false, \"analyzer\":\"en.microsoft\"} 95 | ], 96 | \"corsOptions\": { 97 | \"allowedOrigins\": [\"*\"], 98 | \"maxAgeInSeconds\": 300 99 | } 100 | }"; 101 | if (!create_index_schema($service_name, $api_key, $api_version, $post_body)) { 102 | //print 'Error init_azuresearch:: Create Index Schema: content!\n'; 103 | return false; 104 | 105 | } 106 | } 107 | // Create indexes for Captions 108 | $CAPTION_LANGS =array('en', 'hi', 'ja', 'zh-Hans'); 109 | foreach ($CAPTION_LANGS as $lang) { 110 | $lower_lang = strtolower($lang); 111 | $index_name = sprintf("%s-%s", $index_name_prefix, $lower_lang); 112 | // Create index only if it doesn't exists 113 | if (!in_array($index_name, $index_names)) { 114 | $post_body ="{ 115 | \"name\": \"$index_name\", 116 | \"fields\": [ 117 | {\"name\":\"id\", \"type\":\"Edm.String\", \"key\":true, \"retrievable\":true, \"searchable\":false, \"filterable\":false, \"sortable\":false, \"facetable\":false}, 118 | {\"name\":\"content_id\", \"type\":\"Edm.String\", \"retrievable\":true, \"searchable\":false, \"filterable\":true, \"sortable\":false, \"facetable\":false}, 119 | {\"name\":\"begin_sec\", \"type\":\"Edm.Int32\", \"retrievable\":true, \"searchable\":false, \"filterable\":false, \"sortable\":true, \"facetable\":false}, 120 | {\"name\":\"begin_str\", \"type\":\"Edm.String\", \"retrievable\":true, \"searchable\":false, \"filterable\":false, \"sortable\":false, \"facetable\":false}, 121 | {\"name\":\"end_str\", \"type\":\"Edm.String\", \"retrievable\":true, \"searchable\":false, \"filterable\":false, \"sortable\":false, \"facetable\":false}, 122 | {\"name\":\"caption_text\", \"type\":\"Edm.String\", \"retrievable\":true, \"searchable\":true, \"filterable\":false, \"sortable\":false, \"facetable\":false, \"analyzer\":\"$lower_lang.lucene\"} 123 | ], 124 | \"corsOptions\": { 125 | \"allowedOrigins\": [\"*\"], 126 | \"maxAgeInSeconds\": 300 127 | } 128 | }"; 129 | 130 | if (!create_index_schema($service_name, $api_key, $api_version, $post_body)) { 131 | //print "Error init_azuresearch:: Create Index Schema: $index_name!\n"; 132 | return false; 133 | } 134 | } 135 | } 136 | return true; 137 | } 138 | 139 | function get_index_list ($service_name, $api_key, $api_version) { 140 | $AZURESEARCH_URL_BASE= sprintf( "https://%s.search.windows.net/indexes", $service_name); 141 | $url = $AZURESEARCH_URL_BASE . '?api-version=' . $api_version; 142 | $opts = array( 143 | 'http'=>array( 144 | 'method'=>"GET", 145 | 'header'=>"Accept: application/json\r\n" . 146 | "api-key: $api_key\r\n", 147 | 'timeout' =>10 148 | ) 149 | ); 150 | $context = stream_context_create($opts); 151 | $data = file_get_contents($url, false, $context); 152 | if ($data === false) { 153 | return null; 154 | } 155 | $arr=json_decode($data,true); // return as assoc array 156 | $ret_arr = array(); 157 | if (!array_key_exists('value', $arr)) { 158 | return null; 159 | } 160 | foreach($arr['value'] as $i) { 161 | array_push($ret_arr, $i['name']); 162 | } 163 | return $ret_arr; 164 | } 165 | 166 | function create_index_schema ($service_name, $api_key, $api_version, $post_body ) { 167 | 168 | $AZURESEARCH_URL_BASE= sprintf( "https://%s.search.windows.net/indexes", $service_name); 169 | $url = $AZURESEARCH_URL_BASE . '?api-version=' . $api_version; 170 | $header = array( 171 | "Content-Type: application/json; charset=UTF-8", 172 | "Api-Key: ". $api_key, 173 | "Accept': application/json", 174 | "Accept-Charset: UTF-8" 175 | ); 176 | $opts = array( 177 | "http" => array( 178 | "method" => "POST", 179 | "header" => implode("\r\n", $header), 180 | "content" => $post_body 181 | ) 182 | ); 183 | 184 | $context = stream_context_create($opts); 185 | $data = file_get_contents($url, false, $context); 186 | if ($data === false) { 187 | return null; 188 | } 189 | return true; 190 | } 191 | ?> 192 | -------------------------------------------------------------------------------- /workflow/wevbtt-azsearch-indexer/run.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Azure Functions Queue Trigger 5 | - Get Webvtt from Queue, parse it into caption items, post them into Azure Search to make them searchable 6 | """ 7 | import os 8 | import sys 9 | import re 10 | import json 11 | if sys.version_info[0] == 3: 12 | text_type = str 13 | import http.client as httplib 14 | import urllib.request as urllib2 15 | else: 16 | text_type = unicode 17 | import httplib 18 | import urllib2 19 | 20 | #AZURE_SEARCH_SERVICE_NAME='gbbdemosearch' 21 | #AZURE_SEARCH_ADMIN_KEY='04AEE09F4C50C0863000AD280FCB0523' 22 | #AZURE_SEARCH_API_VER='2016-09-01' 23 | #AZURE_SEARCH_INDEX_PREFIX='caption' 24 | 25 | ### App Settings ### 26 | # AzureSearchServiceName : gbbdemosearch 27 | # AzureSearchAdminKey : 04AEE09F4C50C0863000AD280FCB0523 28 | # AzureSearchApiVer: 2016-09-01 29 | # AzureSearchIndexPrefix: caption 30 | AZURE_SEARCH_SERVICE_NAME=os.environ['AzureSearchServiceName'] 31 | AZURE_SEARCH_ADMIN_KEY=os.environ['AzureSearchAdminKey'] 32 | AZURE_SEARCH_API_VER=os.environ['AzureSearchApiVer'] 33 | AZURE_SEARCH_INDEX_PREFIX=os.environ['AzureSearchIndexPrefix'] 34 | 35 | SUPPORT_LANGS = ['en','hi','ja', 'zh-Hans'] 36 | 37 | def errorlog(s): 38 | #sys.stderr.write("{}\n".format(s)) 39 | print("ERROR: {}\n".format(s)) 40 | 41 | # Convert SS:SS:SS.SSS, SS:SS:SS, or SS:SS -> # of sec 42 | def timefmt2sec(s): 43 | r = re.compile("([0-9:]*)\.([0-9]*)") 44 | o = r.findall(s) 45 | if len(o) == 1 and len(o[0]) ==2: 46 | s = o[0][0] 47 | arr=s.split(':') 48 | if len(arr) == 3: 49 | return int(arr[0])* 3600 + int(arr[1])*60 + int(arr[2]) 50 | elif len(arr) == 2: 51 | return int(arr[0])*60 + int(arr[1]) 52 | else: 53 | return 0 54 | 55 | class azure_search_client: 56 | def __init__(self, api_url, api_key, api_version): 57 | self.api_url=api_url 58 | self.api_key=api_key 59 | self.api_version=api_version 60 | self.headers={ 61 | 'Content-Type': "application/json; charset=UTF-8", 62 | 'Api-Key': self.api_key, 63 | 'Accept': "application/json", 'Accept-Charset':"UTF-8" 64 | } 65 | 66 | def add_documents(self,index_name, documents, merge): 67 | #raise ConfigError, 'no index_name' if index_name.empty? 68 | #raise ConfigError, 'no documents' if documents.empty? 69 | action = 'mergeOrUpload' if merge else 'upload' 70 | for document in documents: 71 | document['@search.action'] = action 72 | 73 | # Create JSON string for request body 74 | reqobjects={} 75 | reqobjects['value'] = documents 76 | req_body = json.dumps(reqobjects) 77 | # HTTP request to Azure search REST API 78 | conn = httplib.HTTPSConnection(self.api_url) 79 | conn.request("POST", 80 | "/indexes/{0}/docs/index?api-version={1}".format(index_name, self.api_version), 81 | req_body, self.headers) 82 | response = conn.getresponse() 83 | print("status: {} {}".format(response.status, response.reason)) 84 | data = response.read() 85 | print("data: {}".format(data)) 86 | conn.close() 87 | 88 | 89 | def process_content_feeding(content_id, webvtt_url, index_name): 90 | documents = [] 91 | 92 | client=azure_search_client( 93 | "{0}.search.windows.net".format(AZURE_SEARCH_SERVICE_NAME), 94 | AZURE_SEARCH_ADMIN_KEY, 95 | AZURE_SEARCH_API_VER) 96 | 97 | webvtt_url = webvtt_url.replace(' ',"%20") 98 | f = urllib2.urlopen(webvtt_url) 99 | data = f.read().decode('utf-8') 100 | document_id = content_id[12:] 101 | document = { 102 | "id" : document_id, 103 | "content_id": content_id, 104 | "content_text": data 105 | } 106 | documents.append(document) 107 | client.add_documents(index_name, documents, 'upload') 108 | f.close 109 | 110 | 111 | def process_caption_feeding(content_id, webvtt_url, index_name): 112 | documents = [] 113 | line_index =0 114 | doc_index=0 115 | c = 0 116 | 117 | client=azure_search_client( 118 | "{0}.search.windows.net".format(AZURE_SEARCH_SERVICE_NAME), 119 | AZURE_SEARCH_ADMIN_KEY, 120 | AZURE_SEARCH_API_VER) 121 | 122 | webvtt_url = webvtt_url.replace(' ',"%20") 123 | f = urllib2.urlopen(webvtt_url) 124 | data = f.read().decode('utf-8') 125 | lines = data.split("\n") 126 | ## NOT procees webvtt that doesn't have more than 2 lines 127 | if len(lines) < 2: 128 | return 129 | ## Skip 1st 2 lines 130 | line_index += 2 131 | 132 | while line_index < len(lines): 133 | line = lines[line_index] 134 | print ("line: {}".format(line.encode('utf-8'))) 135 | ## Extract xxx and yyy from either xxx.xxx -> yyy.yyy 136 | r = re.compile("([0-9:.]*) --> ([0-9:.]*)") 137 | o = r.findall(line) 138 | if len(o) == 1 and len(o[0]) == 2: 139 | begin_str=o[0][0] 140 | end_str= o[0][1] 141 | else: 142 | # Skip until vaild time rage line comes up 143 | line_index += 1 144 | continue 145 | ## text 146 | line_index += 1 147 | line = lines[line_index] 148 | text = line 149 | # Empty line 150 | line_index += 1 151 | # Document ID 152 | document_id = "{0}-{1}".format(content_id[12:],str(doc_index)) 153 | document = { 154 | "id" : document_id, 155 | "content_id": content_id, 156 | "begin_sec": timefmt2sec(begin_str), 157 | "begin_str": begin_str, 158 | "end_str": end_str, 159 | "caption_text": text 160 | } 161 | print ("docment: content_id:{} begin_sec:{} begin_str:{} end_str:{} caption_text:{}" 162 | .format(content_id,timefmt2sec(begin_str),begin_str,end_str,text.encode('utf-8'))) 163 | documents.append(document) 164 | c +=1 165 | doc_index +=1 166 | if (c > 999): 167 | client.add_documents(index_name, documents, 'upload') 168 | c =0 169 | documents = [] 170 | line_index += 1 171 | 172 | f.close 173 | if (len(documents) > 0): 174 | client.add_documents(index_name, documents, 'upload') 175 | 176 | def functions_main(): 177 | ### This is a function's starting point 178 | 179 | # GET WEBVTT FROM COSMOSDB using Azure Function Queue & CosmosDB 180 | # bindling mechanism. Mechanism behind the binding is that 181 | # when an assetid data comes in , queue trigger does: 182 | # 183 | # (1) Pop the assetid from 184 | # (2) Get contents data stored in CosmosDB by issuing a query: SELECT * from c where c.id = 185 | # (3) The content data obtained above is stored as ENV value with key name 'inputDocument' 186 | # 187 | cosmosdb_data = open(os.environ['inputDocument']).read() 188 | metas=json.loads(cosmosdb_data) 189 | if len(metas) < 1: 190 | errorlog("No meta obtained via Azure Function Queue & CosmosDB binding") 191 | sys.exit(0) 192 | meta = metas[0] 193 | if not 'webvtt_url' in meta: 194 | errorlog("Content doesn't containe webvtt_url: assetid={}".format(meta['id'])) 195 | sys.exit(0) 196 | webvtt_url = meta['webvtt_url'] 197 | print("url->{}".format(meta['webvtt_url'])) 198 | subtitle_urls = meta['subtitle_urls'] 199 | lang = "en" 200 | index_name = "content" 201 | # FEEDING FOR CONTENT INDEX 202 | process_content_feeding(meta['id'], webvtt_url, index_name) 203 | 204 | index_name = "{}-{}".format(AZURE_SEARCH_INDEX_PREFIX, lang) 205 | # FEEDING FOR CAPTION(english) INDEX 206 | process_caption_feeding(meta['id'], webvtt_url, index_name) 207 | 208 | # FEEDING FOR CAPTION(other supportable langs) INDEX 209 | # Loop subtitle_urls 210 | for subtitle_url in subtitle_urls: 211 | if not subtitle_url["lang"] in SUPPORT_LANGS: 212 | continue 213 | sub_lang = subtitle_url["lang"].lower() 214 | sub_webvtt_url = subtitle_url["webvtt_url"] 215 | if sub_lang != lang: 216 | sub_index_name = "{}-{}".format(AZURE_SEARCH_INDEX_PREFIX, sub_lang) 217 | process_caption_feeding(meta['id'], sub_webvtt_url, sub_index_name) 218 | 219 | 220 | functions_main() 221 | 222 | #if __name__ == "__main__": 223 | # #webvtt_url = "http://gbbdemoams.streaming.mediaservices.windows.net/11b7ed07-fe61-4bd0-8e1c-e4f7d3e1763d/Build2017_CognitiveServicesDemo_NoPlaceToHyde_aud_SpReco.vtt" 224 | # #content_id = "nb:cid:UUID:66a56dd2-d65c-4f4e-9dbc-2208c9e00846" 225 | # #index_name = "caption-en" 226 | # webvtt_url = "http://gbbdemoams.streaming.mediaservices.windows.net/b7e28e15-d1d6-458f-9388-b0d03dea2afd/subtitle-hi.vtt" 227 | # content_id = "nb:cid:UUID:66a56dd2-d65c-4f4e-9dbc-2208c9e00846" 228 | # index_name = "caption-hi" 229 | # process_caption_feeding(content_id, webvtt_url, index_name) 230 | -------------------------------------------------------------------------------- /portal/video.php: -------------------------------------------------------------------------------- 1 | "English", 13 | "zh-Hans" => "Chinese", 14 | "hi" => "Hindi", 15 | "ja" => "Japanese" 16 | ); 17 | 18 | $req=$_REQUEST; 19 | $params = array(); 20 | if( is_array($req) ) { 21 | foreach( $req as $name => $value) { 22 | $params[$name] = $value; 23 | } 24 | } 25 | if ( !array_key_exists('cid', $params) ) { 26 | print "Error!"; 27 | exit; 28 | } 29 | 30 | $content_id = $params['cid']; 31 | //$_content_id = "nb:cid:UUID:66a56dd2-d65c-4f4e-9dbc-2208c9e00846"; 32 | 33 | $client = new \DreamFactory\DocumentDb\Client($docdb_host, $docdb_master_key); 34 | 35 | // Getting content information from content id 36 | $contentdb = new \DreamFactory\DocumentDb\Resources\Document($client, $docdb_db_content, $docdb_coll_content); 37 | $res = $contentdb->query('SELECT * FROM c WHERE c.id = @id', [['name' => '@id', 'value' => $content_id]]); 38 | #var_dump($res); 39 | 40 | $http_code = get_http_code($res); 41 | if ($http_code != 200 ){ 42 | print "ERROR: Loading Content!"; 43 | exit; 44 | } 45 | $contents_arr = $res['Documents']; 46 | $c = $contents_arr[0]; 47 | $content_name = get_value('name', $c, $c['asset_name']); 48 | $content_lang = get_value('lang',$c, "en"); 49 | $content_url = get_value('manifest_url',$c, ""); 50 | $caption_webvtt_url = get_value('webvtt_url',$c, ""); 51 | $subtitle_urls = get_value('subtitle_urls',$c, array()); 52 | $content_note = get_value('note',$c, ""); 53 | 54 | //var_dump($subtitle_urls); 55 | //echo "num=" . count($subtitle_urls); 56 | function get_value($key, $arr, $default_value) { 57 | if (array_key_exists($key, $arr)) { 58 | return $arr[$key]; 59 | } 60 | return $default_value; 61 | } 62 | 63 | function get_http_code($res) { 64 | if( is_array($res) and array_key_exists('_curl_info', $res)) { 65 | $curl_info=$res['_curl_info']; 66 | if ( array_key_exists('http_code', $curl_info) ) { 67 | return $curl_info['http_code']; 68 | } 69 | } 70 | return 500; 71 | } 72 | 73 | ?> 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Azure Media & AI Demo 84 | 85 | 86 | 87 | 88 | 89 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 111 | 112 |


113 | 114 |
115 |
116 |
117 |
118 | 119 |
120 |
121 |
122 | 123 |

Media Content

124 |
125 | 126 | 127 | 168 | 169 |

Content Information:

170 |
    171 |
  • Title:
  • 172 |
  • Note:
  • 173 |
174 | 175 |
176 |
177 |
178 |

Captions Timeline

179 |
180 |
181 | 182 | %s", $s['lang'], $content_id, $s['lang'], $lang_name_map[$s['lang']]); 186 | } 187 | } 188 | ?> 189 |
190 |
191 | 192 | 193 | 194 | 195 | 196 |
197 |
198 | 199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 | 211 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /azuredeploy-functions.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionsAppName": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "The name of the Function application to be deployed. The name will automatically be made unique on deployment, all lowercase letters or numbers with no spaces." 9 | } 10 | }, 11 | "functionKey": { 12 | "type": "string", 13 | "defaultValue": "j0txf1f8msjytzvpe40nxbpxdcxtqcgxy0nt", 14 | "metadata": { 15 | "description": "Function App Key to use for authorization of Webhook. You should replace this with the key you want to use for your Function application authorization" 16 | } 17 | }, 18 | "sourceCodeRepositoryURL": { 19 | "type": "string", 20 | "defaultValue": "ENTER PATH to YOUR FORK of https://github.com/shigeyf/ai-digitalmedia", 21 | "metadata": { 22 | "description": "Source code repository URL. Is is REQUIRED that you first fork the samples repository and point this to your fork. If you are using your own fork, you may see an error in deployment related to GitHub auth. We require this for your own good, as we may update and break your application or testing as we deploy new updates to the public samples repository." 23 | } 24 | }, 25 | "sourceCodeBranch": { 26 | "type": "string", 27 | "defaultValue": "master", 28 | "metadata": { 29 | "description": "Sourcecode Repo branch. This should be set to Master for the public sample. You can adjust this to point to your own fork branch as recommended" 30 | } 31 | }, 32 | "mediaServicesAccountAzureActiveDirectoryTenantDomain": { 33 | "type": "string", 34 | "defaultValue": "ENTER YOUR TENANT DOMAIN, Example: microsoft.onmicrosoft.com", 35 | "metadata": { 36 | "description": "Azure Active Directory tenant domain of your media services account." 37 | } 38 | }, 39 | "mediaServicesAccountRestApiEndpoint": { 40 | "type": "string", 41 | "defaultValue": "Example: https://accountname.restv2.region.media.azure.net/api/", 42 | "metadata": { 43 | "description": "REST API endpoint of your media services account." 44 | } 45 | }, 46 | "mediaServicesAccountServicePrincipalClientId": { 47 | "type": "string", 48 | "defaultValue": "", 49 | "metadata": { 50 | "description": "Service Principal Client ID to access your media services account. See https://docs.microsoft.com/en-us/azure/media-services/media-services-portal-get-started-with-aad#service-principal-authentication" 51 | } 52 | }, 53 | "mediaServicesAccountServicePrincipalClientSecret": { 54 | "type": "string", 55 | "defaultValue": "", 56 | "metadata": { 57 | "description": "Service Principal Client Secret to access your media services account. See https://docs.microsoft.com/en-us/azure/media-services/media-services-portal-get-started-with-aad#service-principal-authentication" 58 | } 59 | }, 60 | "mediaServicesStorageAccountName": { 61 | "type": "string", 62 | "defaultValue": "", 63 | "metadata": { 64 | "description": "Storage Accouunt Name of the Media Services account" 65 | } 66 | }, 67 | "cosmosdbAccountName": { 68 | "type": "string", 69 | "defaultValue": "", 70 | "metadata": { 71 | "description": "Cosmos DB Service Name" 72 | } 73 | }, 74 | "searchServiceName": { 75 | "type": "string", 76 | "defaultValue": "", 77 | "metadata": { 78 | "description": "Azure Search Service Name" 79 | } 80 | } 81 | }, 82 | "variables": { 83 | "project": "workflow", 84 | "functionsAppName": "[toLower(parameters('functionsAppName'))]", 85 | "storageAccountid": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", 86 | "storageAccountName": "[concat(toLower(parameters('functionsAppName')), uniqueString(resourceGroup().id))]", 87 | "mediaServicesStorageAccountid": "[resourceId('Microsoft.Storage/storageAccounts', parameters('mediaServicesStorageAccountName'))]", 88 | "searchServicesId": "[resourceId('Microsoft.Search/searchServices', parameters('searchServiceName'))]", 89 | "cosmosdbAccountid": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosdbAccountName'))]", 90 | "hostingPlanName": "[concat(variables('functionsAppName'),'plan')]" 91 | }, 92 | "resources": [ 93 | { 94 | "type": "Microsoft.Storage/storageAccounts", 95 | "name": "[variables('storageAccountName')]", 96 | "apiVersion": "2016-01-01", 97 | "location": "[resourceGroup().location]", 98 | "comments": "This storage account is used to create the Media Services account.", 99 | "sku": { 100 | "name": "Standard_LRS" 101 | }, 102 | "kind": "Storage" 103 | }, 104 | { 105 | "type": "Microsoft.Web/serverfarms", 106 | "name": "[variables('hostingPlanName')]", 107 | "apiVersion": "2015-08-01", 108 | "location": "[resourceGroup().location]", 109 | "comments": "This hosting plan is created to deploy the function app and set the billing sku tier", 110 | "sku": { 111 | "name": "Y1", 112 | "tier": "Dynamic", 113 | "size": "Y1", 114 | "family": "Y", 115 | "capacity": 0 116 | }, 117 | "kind": "functionapp", 118 | "properties": { 119 | "name": "[variables('hostingPlanName')]", 120 | "computeMode": "0" 121 | } 122 | }, 123 | { 124 | "type": "Microsoft.Web/sites", 125 | "name": "[variables('functionsAppName')]", 126 | "apiVersion": "2015-08-01", 127 | "location": "[resourceGroup().location]", 128 | "comments": "This function app depends on the media services account and storage account and will pull down the source code from Github", 129 | "dependsOn": [ 130 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", 131 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]" 132 | ], 133 | "kind": "functionapp", 134 | "properties": { 135 | "serverFarmId": "[variables('hostingPlanName')]", 136 | "siteConfig": { 137 | "appSettings": [ 138 | { 139 | "name": "FUNCTIONS_EXTENSION_VERSION", 140 | "value": "~1" 141 | } 142 | ] 143 | } 144 | }, 145 | "resources": [ 146 | { 147 | "type": "sourcecontrols", 148 | "name": "web", 149 | "apiVersion": "2015-08-01", 150 | "comments": "This section sets up source control for continuous integration on the Function app and pulls the source code down from Github.", 151 | "dependsOn": [ 152 | "[resourceId('Microsoft.Web/sites/', variables('functionsAppName'))]", 153 | "[concat(resourceId('Microsoft.Web/sites/', variables('functionsAppName')),'/config/appsettings')]" 154 | ], 155 | "properties": { 156 | "RepoUrl": "[parameters('sourceCodeRepositoryURL')]", 157 | "branch": "[parameters('sourceCodeBranch')]", 158 | "IsManualIntegration": "false" 159 | } 160 | }, 161 | { 162 | "type": "config", 163 | "name": "appsettings", 164 | "apiVersion": "2015-08-01", 165 | "comments": "These are the default appsettings configured on the functions app.", 166 | "dependsOn": [ 167 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 168 | "[resourceId('Microsoft.Web/Sites', variables('functionsAppName'))]", 169 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 170 | ], 171 | "properties": { 172 | "Project": "[variables('project')]", 173 | "FUNCTIONS_EXTENSION_VERSION": "~1", 174 | "WEBSITE_NODE_DEFAULT_VERSION": "6.5.0", 175 | "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]", 176 | "WEBSITE_CONTENTSHARE": "[variables('functionsAppName')]", 177 | "WEBSITE_USE_PLACEHOLDER": "0", 178 | "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]", 179 | "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]", 180 | "AMSAADTenantDomain": "[parameters('mediaServicesAccountAzureActiveDirectoryTenantDomain')]", 181 | "AMSRESTAPIEndpoint": "[parameters('mediaServicesAccountRestApiEndpoint')]", 182 | "AMSClientId": "[parameters('mediaServicesAccountServicePrincipalClientId')]", 183 | "AMSClientSecret": "[parameters('mediaServicesAccountServicePrincipalClientSecret')]", 184 | "AMSStorageAccountName": "[parameters('mediaServicesStorageAccountName')]", 185 | "AMSStorageAccountKey": "[listKeys(variables('mediaServicesStorageAccountid'),'2015-05-01-preview').key1]", 186 | "StorageConnection": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]", 187 | "CosmosDB_Connection": "[concat('AccountEndpoint=https://',parameters('cosmosdbAccountName'),'.documents.azure.com:443/;AccountKey=',listKeys(variables('cosmosdbAccountid'),'2015-04-08').primaryMasterKey)]", 188 | "IndexFeed_Storage": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('mediaServicesStorageAccountName'),';AccountKey=',listKeys(variables('mediaServicesStorageAccountid'),'2015-05-01-preview').key1)]", 189 | "AzureSearchServiceName": "[parameters('searchServiceName')]", 190 | "AzureSearchAdminKey": "[listAdminKeys(variables('searchServicesId'),'2015-08-19').PrimaryKey]", 191 | "AzureSearchApiVer": "2016-09-01", 192 | "AzureSearchIndexPrefix": "caption", 193 | "SourceStorageAccountName": "[variables('storageAccountName')]", 194 | "SourceStorageAccountKey": "[listKeys(variables('storageAccountid'),'2015-05-01-preview').key1]", 195 | "SigningKey": "wOlDEUJ4/VN1No8HxVxpsRvej0DZrO5DXvImGLjFhfctPGFiMkUA0Cj8HSfJW7lePX9XsfHAMhw30p0yYqG+1A==", 196 | "WebHookEndpoint": "[concat(resourceId('Microsoft.Web/sites', variables('functionsAppName')),'/api/Notification_Webhook_Function?code=', parameters('functionKey'))]" 197 | } 198 | } 199 | ] 200 | } 201 | ], 202 | "outputs": {} 203 | } -------------------------------------------------------------------------------- /workflow/translate-webvtt-mediaasset/run.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "Microsoft.WindowsAzure.Storage" 3 | #r "System.Runtime.Serialization" 4 | #r "System.Web" 5 | 6 | using System; 7 | using System.IO; 8 | using System.Net; 9 | using System.Net.Http; 10 | using System.Runtime.Serialization; 11 | using System.Text; 12 | using System.Text.RegularExpressions; 13 | using System.Threading; 14 | using System.Web; 15 | using Newtonsoft.Json; 16 | using Newtonsoft.Json.Linq; 17 | using Microsoft.WindowsAzure.MediaServices.Client; 18 | using Microsoft.WindowsAzure.Storage; 19 | using Microsoft.WindowsAzure.Storage.Blob; 20 | using Microsoft.WindowsAzure.Storage.Auth; 21 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 22 | 23 | private static CloudMediaContext _context = null; 24 | private static readonly string _amsAADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain"); 25 | private static readonly string _amsRestApiEndpoint = Environment.GetEnvironmentVariable("AMSRestApiEndpoint"); 26 | private static readonly string _amsClientId = Environment.GetEnvironmentVariable("AMSClientId"); 27 | private static readonly string _amsClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret"); 28 | private static readonly string _amsStorageAccountName = Environment.GetEnvironmentVariable("AMSStorageAccountName"); 29 | private static readonly string _amsStorageAccountKey = Environment.GetEnvironmentVariable("AMSStorageAccountKey"); 30 | 31 | public static async Task Run(HttpRequestMessage req, TraceWriter log) 32 | { 33 | log.Info($"Webhook was triggered!"); 34 | string jsonContent = await req.Content.ReadAsStringAsync(); 35 | dynamic data = JsonConvert.DeserializeObject(jsonContent); 36 | log.Info("Request : " + jsonContent); 37 | 38 | // Validate input objects 39 | if (data["Azure Media Indexer 2 Preview"] == null) 40 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass WebVTT AssetId in the input object" }); 41 | log.Info("Input - Azure Media Indexer 2 Preview : " + data["Azure Media Indexer 2 Preview"]); 42 | if (data["Azure Media Indexer 2 Preview"].AssetId == null) 43 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass WebVTT AssetId in the input object" }); 44 | log.Info("Input - WebVTT AssetId : " + data["Azure Media Indexer 2 Preview"].AssetId); 45 | if (data.SourceLanguage == null) 46 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass SourceLanguage in the input object" }); 47 | log.Info("Input - SourceLanguage : " + data.SourceLanguage); 48 | if (data.TranslatedLanguages == null) 49 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Please pass TranslatedLanguages in the input object" }); 50 | log.Info("Input - TranslatedLanguages : " + data.TranslatedLanguages); 51 | 52 | string assetid = data["Azure Media Indexer 2 Preview"].AssetId; 53 | string srcLang = data.SourceLanguage; 54 | string[] transLanguages = data.TranslatedLanguages.ToObject(); 55 | 56 | try 57 | { 58 | // Load AMS account context 59 | log.Info($"Using Azure Media Service Rest API Endpoint : {_amsRestApiEndpoint}"); 60 | AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_amsAADTenantDomain, 61 | new AzureAdClientSymmetricKey(_amsClientId, _amsClientSecret), 62 | AzureEnvironments.AzureCloudEnvironment); 63 | AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials); 64 | _context = new CloudMediaContext(new Uri(_amsRestApiEndpoint), tokenProvider); 65 | 66 | // Get the Asset 67 | var asset = _context.Assets.Where(a => a.Id == assetid).FirstOrDefault(); 68 | if (asset == null) 69 | { 70 | log.Info("Asset not found - " + assetid); 71 | return req.CreateResponse(HttpStatusCode.BadRequest, new { error = "Asset not found" }); 72 | } 73 | log.Info("Asset found, AssetId : " + asset.Id); 74 | 75 | // Add AssetFiles to the Asset 76 | CloudBlobContainer destinationBlobContainer = GetCloudBlobContainer(_amsStorageAccountName, _amsStorageAccountKey, asset.Uri.Segments[1]); 77 | string blobPrefix = null; 78 | bool useFlatBlobListing = true; 79 | var blobList = destinationBlobContainer.ListBlobs(blobPrefix, useFlatBlobListing, BlobListingDetails.None); 80 | CloudBlockBlob blob = null; 81 | foreach (var b in blobList) 82 | { 83 | string blobUri = (b as CloudBlob).Uri.ToString(); 84 | if (System.Text.RegularExpressions.Regex.IsMatch(blobUri, @"aud_SpReco[.]vtt$")) 85 | { 86 | log.Info("Found WebVTT blob : " + blobUri); 87 | blob = (CloudBlockBlob)b; 88 | } 89 | } 90 | if (blob != null) 91 | { 92 | string filename = "subtitle"; 93 | Translator(asset, blob, filename, destinationBlobContainer, srcLang, transLanguages, log); 94 | } 95 | } 96 | catch (Exception ex) 97 | { 98 | log.Info("Exception " + ex); 99 | return req.CreateResponse(HttpStatusCode.BadRequest); 100 | } 101 | 102 | JObject j = new JObject(); 103 | return req.CreateResponse(HttpStatusCode.OK, j); 104 | } 105 | 106 | static public CloudBlobContainer GetCloudBlobContainer(string storageAccountName, string storageAccountKey, string containerName) 107 | { 108 | CloudStorageAccount sourceStorageAccount = new CloudStorageAccount(new StorageCredentials(storageAccountName, storageAccountKey), true); 109 | CloudBlobClient sourceCloudBlobClient = sourceStorageAccount.CreateCloudBlobClient(); 110 | return sourceCloudBlobClient.GetContainerReference(containerName); 111 | } 112 | 113 | static public void Translator(IAsset asset, CloudBlockBlob myBlob, string fileName, CloudBlobContainer outContainer, string srcLang, string[] languages, TraceWriter log) 114 | { 115 | var authTokenSource = new AzureAuthToken("9a9589abaa944084bc3dfab3475ff360"); 116 | string authToken; 117 | try 118 | { 119 | authToken = authTokenSource.GetAccessToken(); 120 | } 121 | catch (HttpRequestException ex) 122 | { 123 | if (authTokenSource.RequestStatusCode == HttpStatusCode.Unauthorized) 124 | { 125 | log.Info("Request to token service is not authorized (401). Check that the Azure subscription key is valid."); 126 | } 127 | if (authTokenSource.RequestStatusCode == HttpStatusCode.Forbidden) 128 | { 129 | log.Info("Request to token service is not authorized (403). For accounts in the free-tier, check that the account quota is not exceeded."); 130 | } 131 | throw ex; 132 | } 133 | 134 | try { 135 | //string[] languages = new string[] { "ms", "th", "vi", "zh-Hans", "zh-Hant" }; 136 | foreach (string lang in languages) 137 | { 138 | string text = ""; 139 | using (var stream = myBlob.OpenRead()) 140 | { 141 | using (StreamReader reader = new StreamReader(stream)) 142 | { 143 | while (!reader.EndOfStream) 144 | { 145 | text = text + Translate(reader.ReadLine(), srcLang, lang, authToken) + "\r\n"; 146 | } 147 | } 148 | } 149 | 150 | string outputFile = fileName + @"-" + lang + @".vtt"; 151 | outContainer.CreateIfNotExists(); 152 | CloudBlockBlob blob = outContainer.GetBlockBlobReference(outputFile); 153 | blob.DeleteIfExists(); 154 | 155 | var options = new BlobRequestOptions() 156 | { 157 | ServerTimeout = TimeSpan.FromMinutes(10) 158 | }; 159 | using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(text), false)) 160 | { 161 | blob.UploadFromStream(stream, null, options); 162 | } 163 | log.Info("Uploaded : " + outputFile); 164 | 165 | IAssetFile assetFile = asset.AssetFiles.Create(outputFile); 166 | blob.FetchAttributes(); 167 | assetFile.ContentFileSize = blob.Properties.Length; 168 | assetFile.IsPrimary = false; 169 | assetFile.Update(); 170 | log.Info("Asset file registered : " + assetFile.Name); 171 | } 172 | } 173 | catch(Exception ex) 174 | { 175 | log.Error("ERROR: failed."); 176 | log.Info($"StackTrace : {ex.StackTrace}"); 177 | throw ex; 178 | } 179 | } 180 | 181 | static string Translate(string text, string fromLang, string toLang, string authToken) 182 | { 183 | if (text == "WEBVTT" || text == "") return text; 184 | if (System.Text.RegularExpressions.Regex.IsMatch(text, @"[0-9][0-9]:[0-9][0-9]:[0-9][0-9][.][0-9][0-9][0-9][ ]-->[ ][0-9][0-9]:[0-9][0-9]:[0-9][0-9][.][0-9][0-9][0-9]")) return text; 185 | if (System.Text.RegularExpressions.Regex.IsMatch(text, @"NOTE[ ]Confidence[:]*")) return text; 186 | 187 | string translation; 188 | string uri = "https://api.microsofttranslator.com/v2/Http.svc/Translate?text=" + HttpUtility.UrlEncode(text) + "&from=" + fromLang + "&to=" + toLang; 189 | 190 | HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri); 191 | httpWebRequest.Headers.Add("Authorization", authToken); 192 | 193 | using (WebResponse response = httpWebRequest.GetResponse()) 194 | using (Stream stream = response.GetResponseStream()) 195 | { 196 | DataContractSerializer dcs = new DataContractSerializer(Type.GetType("System.String")); 197 | translation = (string)dcs.ReadObject(stream); 198 | } 199 | return translation; 200 | } 201 | 202 | 203 | public class AzureAuthToken 204 | { 205 | /// URL of the token service 206 | private static readonly Uri ServiceUrl = new Uri("https://api.cognitive.microsoft.com/sts/v1.0/issueToken"); 207 | 208 | /// Name of header used to pass the subscription key to the token service 209 | private const string OcpApimSubscriptionKeyHeader = "Ocp-Apim-Subscription-Key"; 210 | 211 | /// After obtaining a valid token, this class will cache it for this duration. 212 | /// Use a duration of 5 minutes, which is less than the actual token lifetime of 10 minutes. 213 | private static readonly TimeSpan TokenCacheDuration = new TimeSpan(0, 5, 0); 214 | 215 | /// Cache the value of the last valid token obtained from the token service. 216 | private string _storedTokenValue = string.Empty; 217 | 218 | /// When the last valid token was obtained. 219 | private DateTime _storedTokenTime = DateTime.MinValue; 220 | 221 | /// Gets the subscription key. 222 | public string SubscriptionKey { get; } 223 | /// Gets the HTTP status code for the most recent request to the token service. 224 | public HttpStatusCode RequestStatusCode { get; private set; } 225 | 226 | /// 227 | /// Creates a client to obtain an access token. 228 | /// 229 | /// Subscription key to use to get an authentication token. 230 | public AzureAuthToken(string key) 231 | { 232 | if (string.IsNullOrEmpty(key)) 233 | { 234 | throw new ArgumentNullException(nameof(key), "A subscription key is required"); 235 | } 236 | this.SubscriptionKey = key; 237 | this.RequestStatusCode = HttpStatusCode.InternalServerError; 238 | } 239 | 240 | /// 241 | /// Gets a token for the specified subscription. 242 | /// 243 | /// The encoded JWT token prefixed with the string "Bearer ". 244 | /// 245 | /// This method uses a cache to limit the number of request to the token service. 246 | /// A fresh token can be re-used during its lifetime of 10 minutes. After a successful 247 | /// request to the token service, this method caches the access token. Subsequent 248 | /// invocations of the method return the cached token for the next 5 minutes. After 249 | /// 5 minutes, a new token is fetched from the token service and the cache is updated. 250 | /// 251 | public async Task GetAccessTokenAsync() 252 | { 253 | if (string.IsNullOrWhiteSpace(this.SubscriptionKey)) 254 | { 255 | return string.Empty; 256 | } 257 | // Re-use the cached token if there is one. 258 | if ((DateTime.Now - _storedTokenTime) < TokenCacheDuration) 259 | { 260 | return _storedTokenValue; 261 | } 262 | 263 | using (var client = new HttpClient()) 264 | using (var request = new HttpRequestMessage()) 265 | { 266 | request.Method = HttpMethod.Post; 267 | request.RequestUri = ServiceUrl; 268 | request.Content = new StringContent(string.Empty); 269 | request.Headers.TryAddWithoutValidation(OcpApimSubscriptionKeyHeader, this.SubscriptionKey); 270 | client.Timeout = TimeSpan.FromSeconds(2); 271 | var response = await client.SendAsync(request); 272 | this.RequestStatusCode = response.StatusCode; 273 | response.EnsureSuccessStatusCode(); 274 | var token = await response.Content.ReadAsStringAsync(); 275 | _storedTokenTime = DateTime.Now; 276 | _storedTokenValue = "Bearer " + token; 277 | return _storedTokenValue; 278 | } 279 | } 280 | 281 | /// 282 | /// Gets a token for the specified subscription. Synchronous version. 283 | /// Use of async version preferred 284 | /// 285 | /// The encoded JWT token prefixed with the string "Bearer ". 286 | /// 287 | /// This method uses a cache to limit the number of request to the token service. 288 | /// A fresh token can be re-used during its lifetime of 10 minutes. After a successful 289 | /// request to the token service, this method caches the access token. Subsequent 290 | /// invocations of the method return the cached token for the next 5 minutes. After 291 | /// 5 minutes, a new token is fetched from the token service and the cache is updated. 292 | /// 293 | public string GetAccessToken() 294 | { 295 | // Re-use the cached token if there is one. 296 | if ((DateTime.Now - _storedTokenTime) < TokenCacheDuration) 297 | { 298 | return _storedTokenValue; 299 | } 300 | string accessToken = null; 301 | var task = Task.Run(async () => 302 | { 303 | accessToken = await this.GetAccessTokenAsync(); 304 | }); 305 | while (!task.IsCompleted) 306 | { 307 | System.Threading.Thread.Yield(); 308 | } 309 | if (task.IsFaulted) 310 | { 311 | throw task.Exception; 312 | } 313 | if (task.IsCanceled) 314 | { 315 | throw new Exception("Timeout obtaining access token."); 316 | } 317 | return accessToken; 318 | } 319 | } -------------------------------------------------------------------------------- /portal/api/azure-documentdb-php-sdk/vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath.'\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 383 | $length = $this->prefixLengthsPsr4[$first][$search]; 384 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | --------------------------------------------------------------------------------