Skip to content

Commit

Permalink
homarus (#52)
Browse files Browse the repository at this point in the history
* homarus

* Delete config.yaml

config.yaml will be created by ansible

* changes requested by Danny 1

* update example config

* update README

* add basic test setup
  • Loading branch information
Natkeeran authored and dannylamb committed Nov 20, 2018
1 parent a711341 commit edf356e
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 0 deletions.
58 changes: 58 additions & 0 deletions Homarus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Homarus

## Introduction

[FFmpeg](https://www.ffmpeg.org/) as a microservice.

## Installation
- Install `ffmpeg`. On Ubuntu, this can be done with `sudo apt-get install ffmpeg`.
- Clone this repository somewhere in your web root (example: `/var/www/html/Crayfish/Homarus`).
- Copy `/var/www/html/Crayfish/Homarus/cfg/config.default.yml` to `/var/www/html/Crayfish/Homarus/cfg/config.yml`
- Copy `/var/www/html/Crayfish/Hypercube/syn-settings.xml` to `/var/www/html/Crayfish/Homarus/syn-settings.xml`
- Install `composer`. [Install instructions here.][4]
- `$ cd /path/to/Homarus` and run `$ composer install`
- Then either
- For production, configure your web server appropriately (e.g. add a VirtualHost for Homarus in Apache) OR
- For development, run the PHP built-in webserver `$ php -S localhost:8888 -t src` from Homarus root.


### Apache2

To use Homarus with Apache you need to configure your Virtualhost with a few options:
- Redirect all requests to the Homarus index.php file
- Make sure Hypercube has access to Authorization headers

Here is an example configuration for Apache 2.4:
```apache
Alias "/homarus" "/var/www/html/Crayfish/Homarus/src"
<Directory "/var/www/html/Crayfish/Homarus/src">
FallbackResource /homarus/index.php
Require all granted
DirectoryIndex index.php
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
</Directory>
```

This will put the Homarus at the /homarus endpoint on the webserver.

## Configuration

If your ffmpeg installation is not on your path, then you can configure homarus to use a specific executable by editing `executable` entry in [config.yaml](./cfg/config.example.yaml).

You also will need to set the `fedora base url` entry to point to your Fedora installation.

## Usage
This will return the an avi file for the test video file in Fedora.
```
curl -H "Authorization: Bearer islandora" -H "Accept: video/x-msvideo" -H "Apix-Ldp-Resource:http://localhost:8080/fcrepo/rest/testvideo" http://localhost:8000/homarus/convert --output output.avi
```

## Maintainers

Current maintainers:

* [Natkeeran](https://github.com/Natkeeran)

## License

[MIT](https://opensource.org/licenses/MIT)
37 changes: 37 additions & 0 deletions Homarus/cfg/config.example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
homarus:
# path to the ffmpeg executable
executable: ffmpeg
mime_types:
valid:
- video/mp4
- video/x-msvideo
- video/ogg
default_video: video/mp4
mime_to_format:
valid:
- video/mp4_mp4
- video/x-msvideo_avi
- video/ogg_ogg

fedora_resource:
base_url: http://localhost:8080/fcrepo/rest

log:
# Valid log levels are:
# DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY, NONE
# log level none won't open logfile
level: DEBUG
file: /var/log/islandora/homarus.log

syn:
# toggles JWT security for service
enable: True
# Path to the syn config file for authentication.
# example can be found here:
# https://github.com/Islandora-CLAW/Syn/blob/master/conf/syn-settings.example$
config: ../syn-settings.xml




43 changes: 43 additions & 0 deletions Homarus/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "islandora/homarus",
"description": "FFmpeg as a web service",
"type": "project",
"require": {
"islandora/crayfish-commons": "dev-master"
},
"license": "MIT",
"authors": [
{
"name": "Islandora Foundation",
"email": "[email protected]",
"role": "Owner"
},
{
"name": "Natkeeran L.Kanthan",
"email": "[email protected]",
"role": "Maintainer"
}
],
"autoload": {
"psr-4": {
"Islandora\\Homarus\\": "src/"
}
},
"scripts": {
"check": [
"phpcs --standard=PSR2 src tests",
"phpcpd --names *.php src"
],
"test": [
"@check",
"phpunit"
]
},
"require-dev": {
"symfony/browser-kit": "^3.0",
"symfony/css-selector": "^3.0",
"phpunit/phpunit": "^5.0",
"squizlabs/php_codesniffer": "^2.0",
"sebastian/phpcpd": "^3.0"
}
}
30 changes: 30 additions & 0 deletions Homarus/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Homarus Tests">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-clover" target="clover.xml"/>
</logging>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<exclude>
<file>./src/index.php</file>
<file>./src/app.php</file>
</exclude>
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
144 changes: 144 additions & 0 deletions Homarus/src/Controller/HomarusController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
namespace Islandora\Homarus\Controller;

use GuzzleHttp\Psr7\StreamWrapper;
use Islandora\Crayfish\Commons\CmdExecuteService;
use Islandora\Crayfish\Commons\ApixFedoraResourceRetriever;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
* Class HomarusController
* @package Islandora\Homarus\Controller
* @param $log
*/
class HomarusController {

/**
* @var \Islandora\Crayfish\Commons\CmdExecuteService
*/
protected $cmd;


/**
* @var \Monolog\Logger
*/
protected $log;

/**
* Controller constructor.
* @param \Islandora\Crayfish\Commons\CmdExecuteService $cmd
* @param array $formats
* @param string $default_format
* @param string $executable
* @param $log
*/
public function __construct(
CmdExecuteService $cmd,
$formats,
$default_format,
$executable,
$log,
$mime_to_format
) {
$this->cmd = $cmd;
$this->formats = $formats;
$this->default_format = $default_format;
$this->executable = $executable;
$this->log = $log;
$this->mime_to_format = $mime_to_format;
}

/**
* @param \Symfony\Component\HttpFoundation\Request $request
* @return \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\StreamedResponse
*/
public function convert(Request $request) {
$this->log->info('Ffmpeg Convert request.');

// Short circuit if there's no Apix-Ldp-Resource header.
if (!$request->headers->has("Apix-Ldp-Resource"))
{
$this->log->debug("Malformed request, no Apix-Ldp-Resource header present");
return new Response(
"Malformed request, no Apix-Ldp-Resource header present",
400
);
} else {
$source = $request->headers->get('Apix-Ldp-Resource');
}

// Find the format
$content_types = $request->getAcceptableContentTypes();
$content_type = $this->get_content_type($content_types);
$format = $this->get_ffmpeg_format($content_type);

$cmd_params = "";
if($format == "mp4") {
$cmd_params = " -vcodec libx264 -preset medium -acodec aac -strict -2 -ab 128k -ac 2 -async 1 -movflags frag_keyframe+empty_moov ";
}

// Arguments to ffmpeg command are sent as a custom header
$args = $request->headers->get('X-Islandora-Args');
$this->log->debug("X-Islandora-Args:", ['args' => $args]);

$cmd_string = "$this->executable -i $source $cmd_params -f $format -";
$this->log->info('Ffempg Command:', ['cmd' => $cmd_string]);

// Return response.
try {
return new StreamedResponse(
$this->cmd->execute($cmd_string, $source),
200,
['Content-Type' => $content_type]
);
} catch (\RuntimeException $e) {
$this->log->error("RuntimeException:", ['exception' => $e]);
return new Response($e->getMessage(), 500);
}
}


private function get_content_type($content_types) {
$content_type = null;
foreach ($content_types as $type) {
if (in_array($type, $this->formats)) {
$content_type = $type;
break;
}
}

if ($content_type === null) {
$content_type = $this->default_format;
$this->log->info('Falling back to default content type');
}
return $content_type;
}

private function get_ffmpeg_format($content_type){
foreach ($this->mime_to_format as $format) {
if (strpos($format, $content_type) !== false) {
$this->log->info("does it get here");
$format_info = explode("_", $format);
break;
}
}
return $format_info[1];
}

/**
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function convertOptions()
{
return new BinaryFileResponse(
__DIR__ . "/../../static/convert.ttl",
200,
['Content-Type' => 'text/turtle']
);
}

}
30 changes: 30 additions & 0 deletions Homarus/src/app.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';

use Islandora\Crayfish\Commons\Provider\IslandoraServiceProvider;
use Islandora\Crayfish\Commons\Provider\YamlConfigServiceProvider;
use Islandora\Homarus\Controller\HomarusController;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;


$app = new Application();

$app->register(new IslandoraServiceProvider());
$app->register(new YamlConfigServiceProvider(__DIR__ . '/../cfg/config.yaml'));

$app['homarus.controller'] = function ($app) {
return new HomarusController(
$app['crayfish.cmd_execute_service'],
$app['crayfish.homarus.mime_types.valid'],
$app['crayfish.homarus.mime_types.default_video'],
$app['crayfish.homarus.executable'],
$app['monolog'],
$app['crayfish.homarus.mime_to_format']
);
};

$app->options('/convert', "homarus.controller:convertOptions");
$app->get('/convert', "homarus.controller:convert");

return $app;
4 changes: 4 additions & 0 deletions Homarus/src/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

$app = require_once(__DIR__ . '/app.php');
$app->run();
18 changes: 18 additions & 0 deletions Homarus/static/convert.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@prefix apix:<http://fedora.info/definitions/v4/api-extension#> .
@prefix owl:<http://www.w3.org/2002/07/owl#> .
@prefix ebucore:<http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#> .
@prefix ldp:<http://www.w3.org/ns/ldp#> .
@prefix islandora:<http://islandora.ca/CLAW#> .
@prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#> .

<> a apix:Extension;
rdfs:label "Ffmpeg Service";
rdfs:comment "ffmpeg as a microservice";
apix:exposesService islandora:ConvertService;
apix:exposesServiceAt "svc:convert";
apix:bindsTo <#class> .

<#class> owl:intersectionOf (
ldp:NonRDFSource
[ a owl:Restriction; owl:onProperty ebucore:hasMimeType; owl:hasValue "video/mp4", "video/x-msvideo", "video/ogg" ]
) .
Loading

0 comments on commit edf356e

Please sign in to comment.