video: configure external go-vod too
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/563/head
parent
cb8ebcb6a8
commit
ac0cc6460b
187
lib/BinExt.php
187
lib/BinExt.php
|
@ -5,6 +5,7 @@ namespace OCA\Memories;
|
||||||
class BinExt
|
class BinExt
|
||||||
{
|
{
|
||||||
public const EXIFTOOL_VER = '12.58';
|
public const EXIFTOOL_VER = '12.58';
|
||||||
|
public const GOVOD_VER = '0.0.34';
|
||||||
|
|
||||||
/** Test configured exiftool binary */
|
/** Test configured exiftool binary */
|
||||||
public static function testExiftool(): bool
|
public static function testExiftool(): bool
|
||||||
|
@ -76,6 +77,192 @@ class BinExt
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the upstream URL for a video.
|
||||||
|
*/
|
||||||
|
public static function getGoVodUrl(string $client, string $path, string $profile): string
|
||||||
|
{
|
||||||
|
$path = rawurlencode($path);
|
||||||
|
|
||||||
|
$bind = Util::getSystemConfig('memories.vod.bind');
|
||||||
|
$connect = Util::getSystemConfig('memories.vod.connect', $bind);
|
||||||
|
|
||||||
|
return "http://{$connect}/{$client}{$path}/{$profile}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getGoVodConfig($local = false)
|
||||||
|
{
|
||||||
|
// Get config from system values
|
||||||
|
$env = [
|
||||||
|
'vaapi' => Util::getSystemConfig('memories.vod.vaapi'),
|
||||||
|
'vaapiLowPower' => Util::getSystemConfig('memories.vod.vaapi.low_power'),
|
||||||
|
|
||||||
|
'nvenc' => Util::getSystemConfig('memories.vod.nvenc', false),
|
||||||
|
'nvencTemporalAQ' => Util::getSystemConfig('memories.vod.nvenc.temporal_aq'),
|
||||||
|
'nvencScale' => Util::getSystemConfig('memories.vod.nvenc.scale'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$local) {
|
||||||
|
return $env;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get temp directory
|
||||||
|
$tmpPath = Util::getSystemConfig('memories.vod.tempdir', sys_get_temp_dir().'/go-vod/');
|
||||||
|
|
||||||
|
// Make sure path ends with slash
|
||||||
|
if ('/' !== substr($tmpPath, -1)) {
|
||||||
|
$tmpPath .= '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add instance ID to path
|
||||||
|
$tmpPath .= Util::getSystemConfig('instanceid', 'default');
|
||||||
|
|
||||||
|
return array_merge($env, [
|
||||||
|
'bind' => Util::getSystemConfig('memories.vod.bind'),
|
||||||
|
'ffmpeg' => Util::getSystemConfig('memories.vod.ffmpeg'),
|
||||||
|
'ffprobe' => Util::getSystemConfig('memories.vod.ffprobe'),
|
||||||
|
'tempdir' => $tmpPath,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If local, restart the go-vod instance.
|
||||||
|
* If external, configure the go-vod instance.
|
||||||
|
*/
|
||||||
|
public static function startGoVod()
|
||||||
|
{
|
||||||
|
// Check if external
|
||||||
|
if (Util::getSystemConfig('memories.vod.external')) {
|
||||||
|
self::configureGoVod();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get transcoder path
|
||||||
|
$transcoder = Util::getSystemConfig('memories.vod.path');
|
||||||
|
if (empty($transcoder)) {
|
||||||
|
throw new \Exception('Transcoder not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure transcoder exists
|
||||||
|
if (!file_exists($transcoder)) {
|
||||||
|
throw new \Exception("Transcoder not found; ({$transcoder})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make transcoder executable
|
||||||
|
if (!is_executable($transcoder)) {
|
||||||
|
@chmod($transcoder, 0755);
|
||||||
|
if (!is_executable($transcoder)) {
|
||||||
|
throw new \Exception("Transcoder not executable (chmod 755 {$transcoder})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get local config
|
||||||
|
$env = self::getGoVodConfig(true);
|
||||||
|
$tmpPath = $env['tempdir'];
|
||||||
|
|
||||||
|
// (Re-)create temp dir
|
||||||
|
shell_exec("rm -rf '{$tmpPath}' && mkdir -p '{$tmpPath}' && chmod 755 '{$tmpPath}'");
|
||||||
|
|
||||||
|
// Check temp directory exists
|
||||||
|
if (!is_dir($tmpPath)) {
|
||||||
|
throw new \Exception("Temp directory could not be created ({$tmpPath})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check temp directory is writable
|
||||||
|
if (!is_writable($tmpPath)) {
|
||||||
|
throw new \Exception("Temp directory is not writable ({$tmpPath})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write config to file
|
||||||
|
$logFile = $tmpPath.'.log';
|
||||||
|
$configFile = $tmpPath.'.json';
|
||||||
|
file_put_contents($configFile, json_encode($env, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
// Kill the transcoder in case it's running
|
||||||
|
\OCA\Memories\Util::pkill($transcoder);
|
||||||
|
|
||||||
|
// Start transcoder
|
||||||
|
shell_exec("nohup {$transcoder} {$configFile} >> '{$logFile}' 2>&1 & > /dev/null");
|
||||||
|
|
||||||
|
// wait for 500ms
|
||||||
|
usleep(500000);
|
||||||
|
|
||||||
|
return $logFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test go-vod and (re)-start if it is not external.
|
||||||
|
*/
|
||||||
|
public static function testStartGoVod(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return self::testGoVod();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// silently try to restart
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to (re)start go-vod
|
||||||
|
// If it is external, this only attempts to reconfigure
|
||||||
|
self::startGoVod();
|
||||||
|
|
||||||
|
// Test again
|
||||||
|
return self::testGoVod();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test the go-vod instance that is running */
|
||||||
|
public static function testGoVod(): bool
|
||||||
|
{
|
||||||
|
// TODO: check data mount; ignoring the result of the file for now
|
||||||
|
$testfile = realpath(__DIR__.'/../exiftest.jpg');
|
||||||
|
|
||||||
|
// Make request
|
||||||
|
$url = self::getGoVodUrl('test', $testfile, 'test');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = new \GuzzleHttp\Client();
|
||||||
|
$res = $client->request('GET', $url);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception('failed to connect to go-vod: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse body
|
||||||
|
$json = json_decode($res->getBody(), true);
|
||||||
|
if (!$json) {
|
||||||
|
throw new \Exception('failed to parse go-vod response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check version
|
||||||
|
$version = $json['version'];
|
||||||
|
$target = self::GOVOD_VER;
|
||||||
|
if (!version_compare($version, $target, '=')) {
|
||||||
|
throw new \Exception("version does not match {$version} <==> {$target}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POST a new configuration to go-vod */
|
||||||
|
public static function configureGoVod()
|
||||||
|
{
|
||||||
|
// Get config
|
||||||
|
$config = self::getGoVodConfig();
|
||||||
|
|
||||||
|
// Make request
|
||||||
|
$url = self::getGoVodUrl('config', '/config', 'config');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = new \GuzzleHttp\Client();
|
||||||
|
$client->request('POST', $url, [
|
||||||
|
'json' => $config,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception('failed to connect to go-vod: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Detect the go-vod binary to use */
|
/** Detect the go-vod binary to use */
|
||||||
public static function detectGoVod()
|
public static function detectGoVod()
|
||||||
{
|
{
|
||||||
|
|
|
@ -92,7 +92,11 @@ class OtherController extends GenericApiController
|
||||||
|
|
||||||
// If changing vod settings, kill any running go-vod instances
|
// If changing vod settings, kill any running go-vod instances
|
||||||
if (0 === strpos($key, 'memories.vod.')) {
|
if (0 === strpos($key, 'memories.vod.')) {
|
||||||
Util::pkill('go-vod');
|
try {
|
||||||
|
BinExt::startGoVod();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
error_log('Failed to start go-vod: '.$e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JSONResponse([], Http::STATUS_OK);
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
|
@ -129,6 +133,14 @@ class OtherController extends GenericApiController
|
||||||
|
|
||||||
// Check go-vod binary
|
// Check go-vod binary
|
||||||
$status['govod'] = $this->getExecutableStatus(Util::getSystemConfig('memories.vod.path'));
|
$status['govod'] = $this->getExecutableStatus(Util::getSystemConfig('memories.vod.path'));
|
||||||
|
if ('ok' === $status['govod'] || Util::getSystemConfig('memories.vod.external')) {
|
||||||
|
try {
|
||||||
|
BinExt::testStartGoVod();
|
||||||
|
$status['govod'] = 'test_ok';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$status['govod'] = 'test_fail:'.$e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for VA-API device
|
// Check for VA-API device
|
||||||
$devPath = '/dev/dri/renderD128';
|
$devPath = '/dev/dri/renderD128';
|
||||||
|
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Memories\Controller;
|
namespace OCA\Memories\Controller;
|
||||||
|
|
||||||
|
use OCA\Memories\BinExt;
|
||||||
use OCA\Memories\Exceptions;
|
use OCA\Memories\Exceptions;
|
||||||
use OCA\Memories\Exif;
|
use OCA\Memories\Exif;
|
||||||
use OCA\Memories\Util;
|
use OCA\Memories\Util;
|
||||||
|
@ -218,123 +219,18 @@ class VideoController extends GenericApiController
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the transcoder.
|
|
||||||
*
|
|
||||||
* @return string Path to log file
|
|
||||||
*/
|
|
||||||
public static function startGoVod()
|
|
||||||
{
|
|
||||||
$config = \OC::$server->get(\OCP\IConfig::class);
|
|
||||||
|
|
||||||
// Get transcoder path
|
|
||||||
$transcoder = $config->getSystemValue('memories.vod.path', false);
|
|
||||||
if (!$transcoder) {
|
|
||||||
throw new \Exception('Transcoder not configured');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure transcoder exists
|
|
||||||
if (!file_exists($transcoder)) {
|
|
||||||
throw new \Exception("Transcoder not found; run occ memories video-setup! ({$transcoder})");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make transcoder executable
|
|
||||||
if (!is_executable($transcoder)) {
|
|
||||||
@chmod($transcoder, 0755);
|
|
||||||
if (!is_executable($transcoder)) {
|
|
||||||
throw new \Exception("Transcoder not executable (chmod 755 {$transcoder})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kill the transcoder in case it's running
|
|
||||||
\OCA\Memories\Util::pkill($transcoder);
|
|
||||||
|
|
||||||
// Start transcoder
|
|
||||||
[$configFile, $logFile] = self::makeGoVodConfig($config);
|
|
||||||
shell_exec("nohup {$transcoder} {$configFile} >> '{$logFile}' 2>&1 & > /dev/null");
|
|
||||||
|
|
||||||
// wait for 1s
|
|
||||||
sleep(1);
|
|
||||||
|
|
||||||
return $logFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the upstream URL for a video.
|
|
||||||
*/
|
|
||||||
public static function getGoVodUrl(string $client, string $path, string $profile): string
|
|
||||||
{
|
|
||||||
$config = \OC::$server->get(\OCP\IConfig::class);
|
|
||||||
$path = rawurlencode($path);
|
|
||||||
|
|
||||||
$bind = $config->getSystemValue('memories.vod.bind', '127.0.0.1:47788');
|
|
||||||
$connect = $config->getSystemValue('memories.vod.connect', $bind);
|
|
||||||
|
|
||||||
return "http://{$connect}/{$client}{$path}/{$profile}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the goVod config JSON.
|
|
||||||
*/
|
|
||||||
public static function getGoVodConfig(\OCP\IConfig $config): array
|
|
||||||
{
|
|
||||||
// Migrate legacy config: remove in 2024
|
|
||||||
self::migrateLegacyConfig($config);
|
|
||||||
|
|
||||||
// Get temp directory
|
|
||||||
$defaultTmp = sys_get_temp_dir().'/go-vod/';
|
|
||||||
$tmpPath = $config->getSystemValue('memories.vod.tempdir', $defaultTmp);
|
|
||||||
|
|
||||||
// Make sure path ends with slash
|
|
||||||
if ('/' !== substr($tmpPath, -1)) {
|
|
||||||
$tmpPath .= '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add instance ID to path
|
|
||||||
$tmpPath .= $config->getSystemValue('instanceid', 'default');
|
|
||||||
|
|
||||||
// (Re-)create temp dir
|
|
||||||
shell_exec("rm -rf '{$tmpPath}' && mkdir -p '{$tmpPath}' && chmod 755 '{$tmpPath}'");
|
|
||||||
|
|
||||||
// Check temp directory exists
|
|
||||||
if (!is_dir($tmpPath)) {
|
|
||||||
throw new \Exception("Temp directory could not be created ({$tmpPath})");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check temp directory is writable
|
|
||||||
if (!is_writable($tmpPath)) {
|
|
||||||
throw new \Exception("Temp directory is not writable ({$tmpPath})");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get config from system values
|
|
||||||
return [
|
|
||||||
'bind' => $config->getSystemValue('memories.vod.bind', '127.0.0.1:47788'),
|
|
||||||
'ffmpeg' => $config->getSystemValue('memories.vod.ffmpeg', 'ffmpeg'),
|
|
||||||
'ffprobe' => $config->getSystemValue('memories.vod.ffprobe', 'ffprobe'),
|
|
||||||
'tempdir' => $tmpPath,
|
|
||||||
|
|
||||||
'vaapi' => $config->getSystemValue('memories.vod.vaapi', false),
|
|
||||||
'vaapiLowPower' => $config->getSystemValue('memories.vod.vaapi.low_power', false),
|
|
||||||
|
|
||||||
'nvenc' => $config->getSystemValue('memories.vod.nvenc', false),
|
|
||||||
'nvencTemporalAQ' => $config->getSystemValue('memories.vod.nvenc.temporal_aq', false),
|
|
||||||
'nvencScale' => $config->getSystemValue('memories.vod.nvenc.scale', 'npp'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getUpstream(string $client, string $path, string $profile)
|
private function getUpstream(string $client, string $path, string $profile)
|
||||||
{
|
{
|
||||||
$returnCode = $this->getUpstreamInternal($client, $path, $profile);
|
$returnCode = $this->getUpstreamInternal($client, $path, $profile);
|
||||||
$isExternal = $this->config->getSystemValue('memories.vod.external', false);
|
|
||||||
|
|
||||||
// If status code was 0, it's likely the server is down
|
// If status code was 0, it's likely the server is down
|
||||||
// Make one attempt to start after killing whatever is there
|
// Make one attempt to start after killing whatever is there
|
||||||
if (0 !== $returnCode || $isExternal) {
|
if (0 !== $returnCode && 503 !== $returnCode) {
|
||||||
return $returnCode;
|
return $returnCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start goVod and get log file
|
// Start goVod and get log file
|
||||||
$logFile = self::startGoVod();
|
$logFile = BinExt::startGoVod();
|
||||||
|
|
||||||
$returnCode = $this->getUpstreamInternal($client, $path, $profile);
|
$returnCode = $this->getUpstreamInternal($client, $path, $profile);
|
||||||
if (0 === $returnCode) {
|
if (0 === $returnCode) {
|
||||||
|
@ -348,7 +244,7 @@ class VideoController extends GenericApiController
|
||||||
{
|
{
|
||||||
// Make sure query params are repeated
|
// Make sure query params are repeated
|
||||||
// For example, in folder sharing, we need the params on every request
|
// For example, in folder sharing, we need the params on every request
|
||||||
$url = self::getGoVodUrl($client, $path, $profile);
|
$url = BinExt::getGoVodUrl($client, $path, $profile);
|
||||||
if ($params = $_SERVER['QUERY_STRING']) {
|
if ($params = $_SERVER['QUERY_STRING']) {
|
||||||
$url .= "?{$params}";
|
$url .= "?{$params}";
|
||||||
}
|
}
|
||||||
|
@ -410,7 +306,7 @@ class VideoController extends GenericApiController
|
||||||
*/
|
*/
|
||||||
private static function postFile(string $client, $blob)
|
private static function postFile(string $client, $blob)
|
||||||
{
|
{
|
||||||
$url = self::getGoVodUrl($client, '/create', 'ignore');
|
$url = BinExt::getGoVodUrl($client, '/create', 'ignore');
|
||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
@ -429,60 +325,4 @@ class VideoController extends GenericApiController
|
||||||
|
|
||||||
return json_decode($response, true);
|
return json_decode($response, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the goVod config JSON and put it to a file.
|
|
||||||
*
|
|
||||||
* @return array [config file, log file]
|
|
||||||
*/
|
|
||||||
private static function makeGoVodConfig(\OCP\IConfig $config): array
|
|
||||||
{
|
|
||||||
$env = self::getGoVodConfig($config);
|
|
||||||
$tmpPath = $env['tempdir'];
|
|
||||||
|
|
||||||
// Write config to file
|
|
||||||
$logFile = $tmpPath.'.log';
|
|
||||||
$configFile = $tmpPath.'.json';
|
|
||||||
file_put_contents($configFile, json_encode($env, JSON_PRETTY_PRINT));
|
|
||||||
|
|
||||||
// Log file is not in config
|
|
||||||
// go-vod just writes to stdout/stderr
|
|
||||||
return [$configFile, $logFile];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate legacy config to new.
|
|
||||||
*
|
|
||||||
* Remove in year 2024
|
|
||||||
*/
|
|
||||||
private static function migrateLegacyConfig(\OCP\IConfig $config)
|
|
||||||
{
|
|
||||||
if (null === $config->getSystemValue('memories.no_transcode', null)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mapping
|
|
||||||
$legacyConfig = [
|
|
||||||
'memories.no_transcode' => 'memories.vod.disable',
|
|
||||||
'memories.transcoder' => 'memories.vod.path',
|
|
||||||
'memories.ffmpeg_path' => 'memories.vod.ffmpeg',
|
|
||||||
'memories.ffprobe_path' => 'memories.vod.ffprobe',
|
|
||||||
'memories.qsv' => 'memories.vod.vaapi',
|
|
||||||
'memories.nvenc' => 'memories.vod.nvenc',
|
|
||||||
'memories.tmp_path' => 'memories.vod.tempdir',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($legacyConfig as $old => $new) {
|
|
||||||
if (null !== $config->getSystemValue($old, null)) {
|
|
||||||
$config->setSystemValue($new, $config->getSystemValue($old));
|
|
||||||
$config->deleteSystemValue($old);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate bind address
|
|
||||||
if ($port = null !== $config->getSystemValue('memories.govod_port', null)) {
|
|
||||||
$config->setSystemValue('memories.vod.bind', "127.0.0.1:{$port}");
|
|
||||||
$config->deleteSystemValue('memories.govod_port');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue