video: configure external go-vod too

Signed-off-by: Varun Patil <varunpatil@ucla.edu>
pull/563/head
Varun Patil 2023-04-10 16:45:04 -07:00
parent cb8ebcb6a8
commit ac0cc6460b
3 changed files with 205 additions and 166 deletions

View File

@ -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()
{ {

View File

@ -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';

View File

@ -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');
}
}
} }