Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check moov atom position #1056

Merged
merged 15 commits into from
Jan 28, 2025
1 change: 1 addition & 0 deletions app/Http/Controllers/Views/Videos/VideoController.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function show(Request $request, $id)
'codec' => Video::ERROR_CODEC,
'malformed' => VIDEO::ERROR_MALFORMED,
'too-large' => VIDEO::ERROR_TOO_LARGE,
'moov-atom' => VIDEO::ERROR_INVALID_MOOV_POS,
]);

$fileIds = $volume->orderedFiles()->pluck('uuid', 'id');
Expand Down
20 changes: 20 additions & 0 deletions app/Jobs/ProcessNewVideo.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ public function handleFile($file, $path)
return;
}

if ($this->hasInvalidMoovAtomPosition($path)) {
$this->video->error = Video::ERROR_INVALID_MOOV_POS;
$this->video->save();
return;
}

$this->video->size = File::size($path);
$this->video->duration = $this->getVideoDuration($path);

Expand Down Expand Up @@ -227,6 +233,20 @@ protected function getVideoDimensions($url)
return $this->ffprobe->streams($url)->videos()->first()->getDimensions();
}

protected function hasInvalidMoovAtomPosition($sourcePath)
{
// Webm and mpeg videos don't have a moov atom
if (in_array($this->video->mimeType, ['video/mpeg', 'video/webm'])) {
return false;
}

$process = Process::forever()
->run("ffprobe -v trace -i '{$sourcePath}' 2>&1 | grep -o -e type:\'mdat\' -e type:\'moov\'")
->throw();
$output = explode("\n", $process->output());
return !str_contains($output[0], 'moov');
}

/**
* Extract images from video.
*
Expand Down
7 changes: 7 additions & 0 deletions app/Video.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ class Video extends VolumeFile
*/
const ERROR_TOO_LARGE = 5;

/**
* Error if moov atom is not located at beginning.
*
* @var int
*/
const ERROR_INVALID_MOOV_POS = 6;

/**
* The attributes that are mass assignable.
*
Expand Down
6 changes: 6 additions & 0 deletions resources/assets/js/videos/videoContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class VideoMimeTypeError extends VideoError {}
class VideoCodecError extends VideoError {}
class VideoMalformedError extends VideoError {}
class VideoTooLargeError extends VideoError {}
class VideoMoovAtomError extends VideoError {}

// Used to round and parse the video current time from the URL, as it is stored as an int
// there (without decimal dot).
Expand Down Expand Up @@ -156,6 +157,9 @@ export default {
hasTooLargeError() {
return this.error instanceof VideoTooLargeError;
},
hasMoovAtomError() {
return this.error instanceof VideoMoovAtomError;
},
errorClass() {
if (this.hasVideoError) {
if (this.error instanceof VideoNotProcessedError) {
Expand Down Expand Up @@ -536,6 +540,8 @@ export default {
throw new VideoMalformedError();
} else if (video.error === this.errors['too-large']) {
throw new VideoTooLargeError();
} else if (video.error === this.errors['moov-atom']) {
throw new VideoMoovAtomError();
} else if (video.size === null) {
throw new VideoNotProcessedError();
}
Expand Down
8 changes: 8 additions & 0 deletions resources/views/manual/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@
Advanced configuration of the video annotation tool.
</p>

<h4>
<a href="{{route('manual-tutorials', ['videos', 'fix-video-encoding'])}}">Fix video encoding</a>
</h4>

<p>
Fix errors in video files that can cause problems in BIIGLE.
</p>

<h3>Reports</h3>
<h4>
<a href="{{route('manual-tutorials', ['reports', 'reports-schema'])}}">Reports schema</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@extends('manual.base')

@section('manual-title', 'Fix video encoding')

@section('manual-content')
<div class="row">
<p class="lead">
Fix errors in video files that can cause problems in BIIGLE
</p>
<p>
To modify the video files, download and install the tool <a href="https://www.ffmpeg.org/">FFmpeg</a>.
</p>
<h3>Fix MP4 moov atom position</h3>
<p>
The moov atom of an MP4 file is required by the browser to play the video correctly. If the moov atom is placed at the end of the video file, the entire file must be downloaded first before the video can be played. This can be fixed by moving the moov atom to the beginning of the file.
</p>
<p>
To check the current position of the moov atom in an MP4 file, run the following command.
</p>
<p>
Linux:
<pre>
ffprobe -v trace -i input.mp4 2>&1 | grep -o -e type:\'mdat\' -e type:\'moov\'
</pre>
</p>
<p>
Windows:
<pre>
ffprobe.exe -v trace -i "input.mp4" 2>&1 | findstr "type:'mdat' type:'moov'
</pre>
</p>
<p>
If <code>type:'moov'</code> occurs at first in the command output, the video's moov atom position is valid. Otherwise, fix the position with the command below.
</p>
<p>
Linux:
<pre>
ffmpeg -i input.mp4 -vcodec copy -acodec copy -movflags faststart output.mp4
</pre>
</p>
<p>
Windows:
<pre>
ffmpeg.exe -i "input.mp4" -vcodec copy -acodec copy -movflags faststart "output.mp4"
</pre>
</p>
<p>
The <code>output.mp4</code> file will have the moov atom at the correct position.
</p>
</div>
@endsection
4 changes: 4 additions & 0 deletions resources/views/videos/show/content.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
<span v-if="hasTooLargeError">
The video file is too large.
</span>
<span v-if="hasMoovAtomError">
The video's moov atom position is invalid.<br>
See <a href="{{url("manual/tutorials/videos/fix-video-encoding")}}">the manual</a> for how to fix this.
</span>
</div>
</div>
</div>
Expand Down
Binary file added tests/files/test_invalid_moov_atom.mp4
Binary file not shown.
35 changes: 35 additions & 0 deletions tests/php/Jobs/ProcessNewVideoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,32 @@ public function testHandleRemoveErrorOnSuccess()
$job->handle();
$this->assertNull($video->fresh()->error);
}

public function testHasInvalidMoovAtomPosition()
{
$video = VideoTest::create(['filename' => 'test_invalid_moov_atom.mp4']);
$job = new ProcessNewVideoStub($video);
$job->passThroughMimeType = true;
$job->handle();
$this->assertSame(Video::ERROR_INVALID_MOOV_POS, $video->fresh()->error);
}

public function testHasInvalidMoovAtomPositionNoAtom()
{
$video = VideoTest::create(['filename' => 'test.mp4']);
$job = new ProcessNewVideoStub($video);
$video->mimeType = 'video/webm';
$job->passThroughMimeType = true;
$job->handle();
$this->assertEmpty($video->fresh()->error);

$video = VideoTest::create(['filename' => 'test.mp4']);
$job = new ProcessNewVideoStub($video);
$video->mimeType = 'video/mpeg';
$job->passThroughMimeType = true;
$job->handle();
$this->assertEmpty($video->fresh()->error);
}
}

class ProcessNewVideoStub extends ProcessNewVideo
Expand All @@ -234,6 +260,7 @@ class ProcessNewVideoStub extends ProcessNewVideo
public $duration = 0;
public $passThroughDimensions = false;
public $passThroughCodec = false;
public $passThroughMimeType = false;

protected function getVideoDimensions($url)
{
Expand Down Expand Up @@ -272,4 +299,12 @@ protected function generateThumbnail(string $file, int $width, int $height): Vip
{
return VipsImage::black(100, 100);
}

protected function hasInvalidMoovAtomPosition($source)
{
if ($this->passThroughMimeType) {
return parent::hasInvalidMoovAtomPosition($source);
}
return false;
}
}
Loading