Fix story video upload probe path (#7203)

apiV1Add passed an absolute path to FFMpeg::open(), which laravel-ffmpeg
resolves relative to the local disk root, producing a doubled path and an
ffprobe 'Unable to probe / No such file or directory' failure.

Open the file via the disk instead: FFMpeg::fromDisk('local')->open($path)
for local storage, and an on-demand local disk rooted at the temp dir for
cloud storage.

Adds feature tests that pin the disk-relative path resolution.
pull/7213/head
Your Name 2 weeks ago
parent cd9ae147ae
commit 8aa3144745

@ -91,16 +91,19 @@ class StoryComposeController extends Controller
if ($story->type === 'video') {
if ($localFs) {
$videoPath = storage_path('app/'.$path);
$media = FFMpeg::fromDisk('local')->open($path);
} else {
$tempPath = sys_get_temp_dir().'/'.Str::random(40).'.mp4';
$tempName = Str::random(40).'.mp4';
$tempPath = sys_get_temp_dir().'/'.$tempName;
file_put_contents($tempPath, $disk->get($path));
$videoPath = $tempPath;
$media = FFMpeg::fromDisk(Storage::build([
'driver' => 'local',
'root' => sys_get_temp_dir(),
]))->open($tempName);
}
try {
$video = FFMpeg::open($videoPath);
$duration = $video->getDurationInSeconds();
$duration = $media->getDurationInSeconds();
$res['media_duration'] = $duration;
if ($duration > 500) {

@ -0,0 +1,84 @@
<?php
use App\Models\Story;
use App\Models\User;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
uses(LazilyRefreshDatabase::class);
beforeEach(function () {
Storage::fake('local');
config(['instance.stories.enabled' => true]);
config(['filesystems.default' => 'local']);
});
it('opens the uploaded story video via the local disk with a disk-relative path', function () {
$user = User::factory()->create();
$user->refresh();
$this->actingAs($user);
$captured = [];
// Swap the FFMpeg facade for a spy so we never shell out to ffprobe.
// fromDisk()/open() return the spy itself (fluent chain), and we record
// the disk + path the controller asks for.
$spy = Mockery::mock();
$spy->shouldReceive('fromDisk')->andReturnUsing(function ($disk) use (&$captured, $spy) {
$captured['disk'] = $disk;
return $spy;
});
$spy->shouldReceive('open')->andReturnUsing(function ($path) use (&$captured, $spy) {
$captured['path'] = $path;
return $spy;
});
$spy->shouldReceive('getDurationInSeconds')->andReturn(10);
\ProtoneMedia\LaravelFFMpeg\Support\FFMpeg::swap($spy);
$response = $this->postJson('/api/web/stories/v1/add', [
'file' => UploadedFile::fake()->create('story.mp4', 500, 'video/mp4'),
]);
$response->assertOk();
expect($response->json('media_type'))->toBe('video');
expect($response->json('media_duration'))->toBe(10);
// Opened from the local disk...
expect($captured['disk'])->toBe('local');
// ...with a disk-relative path (the regression passed an absolute path).
$path = $captured['path'];
expect($path)->not->toStartWith('/');
expect($path)->not->toContain(storage_path());
expect($path)->toStartWith('public/_esm.t3/');
// And that disk-relative path resolves to the file that was actually
// stored, so ffprobe would find it.
Storage::disk('local')->assertExists($path);
$story = Story::whereProfileId($user->profile_id)->firstOrFail();
expect($story->type)->toBe('video');
expect($story->path)->toBe($path);
});
it('rejects a story video longer than the allowed duration', function () {
$user = User::factory()->create();
$user->refresh();
$this->actingAs($user);
$spy = Mockery::mock();
$spy->shouldReceive('fromDisk')->andReturnSelf();
$spy->shouldReceive('open')->andReturnSelf();
$spy->shouldReceive('getDurationInSeconds')->andReturn(501);
\ProtoneMedia\LaravelFFMpeg\Support\FFMpeg::swap($spy);
$response = $this->postJson('/api/web/stories/v1/add', [
'file' => UploadedFile::fake()->create('story.mp4', 500, 'video/mp4'),
]);
$response->assertStatus(422);
expect(Story::whereProfileId($user->profile_id)->count())->toBe(0);
});
Loading…
Cancel
Save