mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-11-05 03:03:31 +01:00
The movie source already honoured shared_mount_prefixes, but subtitles stored on another server were always fetched through that server's getFile API, even when the same path was mounted locally. Use isLocallyMountedPath() for subtitles too, so a file under a configured prefix that exists on this node is passed to ffmpeg directly; anything else still goes over HTTP. Refs: #167
148 lines
5.1 KiB
PHP
148 lines
5.1 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use XcVm\Core\Config\SettingsManager;
|
|
use XcVm\Domain\Stream\StreamProcess;
|
|
|
|
/**
|
|
* Unit tests for StreamProcess::buildSubtitleImport() — the VOD subtitle
|
|
* import/metadata builder extracted from startMovie.
|
|
*
|
|
* Guards the fix for the nested-loop $i reuse that imported only the first
|
|
* subtitle while emitting -map metadata for every file.
|
|
*/
|
|
final class StreamProcessSubtitleTest extends TestCase {
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
if (!defined('SERVER_ID')) {
|
|
define('SERVER_ID', 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoke the private static buildSubtitleImport() via reflection.
|
|
*
|
|
* @return array{0:string,1:string} [$import, $metadata]
|
|
*/
|
|
private function build(string $json, array $servers = []): array {
|
|
$m = new ReflectionMethod(StreamProcess::class, 'buildSubtitleImport');
|
|
$m->setAccessible(true);
|
|
return $m->invoke(null, $json, $servers);
|
|
}
|
|
|
|
public function testNoSubtitlesReturnsEmptyStrings(): void {
|
|
[$import, $meta] = $this->build('');
|
|
$this->assertSame('', $import);
|
|
$this->assertSame('', $meta);
|
|
|
|
[$import, $meta] = $this->build('{"files":[]}');
|
|
$this->assertSame('', $import);
|
|
$this->assertSame('', $meta);
|
|
}
|
|
|
|
public function testSingleLocalSubtitleImportedAndMapped(): void {
|
|
$json = json_encode([
|
|
'location' => SERVER_ID,
|
|
'files' => ['/subs/en.srt'],
|
|
'charset' => ['UTF-8'],
|
|
'names' => ['English'],
|
|
]);
|
|
[$import, $meta] = $this->build($json);
|
|
|
|
$this->assertSame(1, substr_count($import, '-sub_charenc'));
|
|
$this->assertStringContainsString('/subs/en.srt', $import);
|
|
$this->assertSame(1, substr_count($meta, '-map '));
|
|
$this->assertStringContainsString('-map 1 ', $meta);
|
|
$this->assertStringContainsString('title=English', $meta);
|
|
}
|
|
|
|
/**
|
|
* The bug fix: every subtitle must be imported AND mapped. The old nested
|
|
* loop imported only the first file while mapping all of them, so this
|
|
* previously failed (import count was 1, not 3).
|
|
*/
|
|
public function testAllSubtitlesAreImportedAndMapped(): void {
|
|
$json = json_encode([
|
|
'location' => SERVER_ID,
|
|
'files' => ['/subs/en.srt', '/subs/fr.srt', '/subs/de.srt'],
|
|
'charset' => ['UTF-8', 'UTF-8', 'UTF-8'],
|
|
'names' => ['English', 'French', 'German'],
|
|
]);
|
|
[$import, $meta] = $this->build($json);
|
|
|
|
// All three imported (the old nested-loop code produced only one).
|
|
$this->assertSame(3, substr_count($import, '-sub_charenc'), 'all subtitles imported');
|
|
$this->assertStringContainsString('/subs/en.srt', $import);
|
|
$this->assertStringContainsString('/subs/fr.srt', $import);
|
|
$this->assertStringContainsString('/subs/de.srt', $import);
|
|
|
|
// Each mapped to its own input index 1..N.
|
|
$this->assertSame(3, substr_count($meta, '-map '));
|
|
$this->assertStringContainsString('-map 1 ', $meta);
|
|
$this->assertStringContainsString('-map 2 ', $meta);
|
|
$this->assertStringContainsString('-map 3 ', $meta);
|
|
}
|
|
|
|
public function testRemoteSubtitleUsesServerApiUrl(): void {
|
|
$servers = [2 => ['api_url' => 'http://node2/api?key=abc']];
|
|
$json = json_encode([
|
|
'location' => 2,
|
|
'files' => ['remote.srt'],
|
|
'charset' => ['UTF-8'],
|
|
'names' => ['Remote'],
|
|
]);
|
|
[$import] = $this->build($json, $servers);
|
|
|
|
$this->assertStringContainsString('http://node2/api?key=abc', $import);
|
|
$this->assertStringContainsString('action=getFile', $import);
|
|
$this->assertStringContainsString('filename=', $import);
|
|
}
|
|
|
|
/** #167: a remote subtitle under a shared mount prefix is read from the mount, not over HTTP. */
|
|
public function testRemoteSubtitleOnSharedMountIsReadLocally(): void {
|
|
$rMount = sys_get_temp_dir() . '/xcvm_shared_' . getmypid() . '/';
|
|
@mkdir($rMount);
|
|
file_put_contents($rMount . 'en.srt', '');
|
|
SettingsManager::set(['shared_mount_prefixes' => [$rMount]]);
|
|
try {
|
|
$servers = [2 => ['api_url' => 'http://node2/api?key=abc']];
|
|
$json = json_encode([
|
|
'location' => 2,
|
|
'files' => [$rMount . 'en.srt', '/elsewhere/fr.srt'],
|
|
'charset' => ['UTF-8', 'UTF-8'],
|
|
'names' => ['English', 'French'],
|
|
]);
|
|
[$import] = $this->build($json, $servers);
|
|
|
|
$this->assertStringContainsString('-i ' . escapeshellarg($rMount . 'en.srt'), $import);
|
|
$this->assertSame(1, substr_count($import, 'action=getFile'), 'only the file outside the mount goes over HTTP');
|
|
} finally {
|
|
SettingsManager::set([]);
|
|
@unlink($rMount . 'en.srt');
|
|
@rmdir($rMount);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The remote filename is URL-encoded from the raw path — not from the shell-
|
|
* quoted one, which put encoded quotes (%27) into the name the remote server
|
|
* looked up.
|
|
*/
|
|
public function testRemoteSubtitleFilenameCarriesNoShellQuotes(): void {
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
$this->markTestSkipped('escapeshellarg() rewrites % on Windows; the panel runs on Linux');
|
|
}
|
|
$servers = [2 => ['api_url' => 'http://node2/api?key=abc']];
|
|
$json = json_encode([
|
|
'location' => 2,
|
|
'files' => ['/subs/My Movie.srt'],
|
|
'charset' => ['UTF-8'],
|
|
'names' => ['Remote'],
|
|
]);
|
|
[$import] = $this->build($json, $servers);
|
|
|
|
$this->assertStringContainsString('filename=' . urlencode('/subs/My Movie.srt'), $import);
|
|
$this->assertStringNotContainsString('%27', $import);
|
|
}
|
|
}
|