Skip to content

Commit

Permalink
Merge pull request #1056 from biigle/moovAtomBug
Browse files Browse the repository at this point in the history
Check moov atom position
  • Loading branch information
mzur authored Jan 28, 2025
2 parents 0965e33 + a002ffc commit 6999c17
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 0 deletions.
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;
}
}

0 comments on commit 6999c17

Please sign in to comment.