Skip to content

Commit

Permalink
Fix support of raw files (#574)
Browse files Browse the repository at this point in the history
* Create medium, small and thumb also for raw files
* Use imagemagic to extract metadata in case of raw files and exiftool not available
* Update composer.lock
* fix livePhotoUrl path (#590)
* Make raw file extensions case insensitive
* Added try-catch for scaling
* Added try-catch for path of xmp file
* Added attempt to create video preview at t=0

In case video thumbnail extaction at middle of video fails, we try a second thumbnail at the beginning of the video
  • Loading branch information
tmp-hallenser authored May 26, 2020
1 parent 32c87f8 commit 16c7c92
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 646 deletions.
6 changes: 4 additions & 2 deletions app/Http/Controllers/ImportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -362,15 +362,17 @@ public function server_exec(string $path, $albumID, $delete_imported, $force_ski
continue;
}
$extension = Helpers::getExtension($file, true);
if (@exif_imagetype($file) !== false || in_array(strtolower($extension), $this->photoFunctions->validExtensions, true)) {
$raw_formats = strtolower(Configs::get_value('raw_formats', ''));
$is_raw = in_array(strtolower($extension), explode('|', $raw_formats), true);
if (@exif_imagetype($file) !== false || in_array(strtolower($extension), $this->photoFunctions->validExtensions, true) || $is_raw) {
// Photo or Video
if ($this->photo($file, $delete_imported, $albumID, $force_skip_duplicates) === false) {
$this->status_update('Problem: ' . $file . ': Could not import file');
Logs::error(__METHOD__, __LINE__, 'Could not import file (' . $file . ')');
continue;
}
} else {
$this->status_update('Problem: ' . $file . ': Unsupported file type');
$this->status_update('Problem: Unsupported file type (' . $file . ')');
Logs::error(__METHOD__, __LINE__, 'Unsupported file type (' . $file . ')');
continue;
}
Expand Down
26 changes: 25 additions & 1 deletion app/Metadata/Extractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Configs;
use App\Logs;
use App\ModelFunctions\Helpers;
use PHPExif\Reader\Reader;

class Extractor
Expand Down Expand Up @@ -71,11 +72,24 @@ public function extract(string $filename, string $type): array
{
$reader = null;

// Get kind of file (photo, video, raw)
$extension = Helpers::getExtension($filename, false);

// check raw files
$is_raw = false;
$raw_formats = strtolower(Configs::get_value('raw_formats', ''));
if (in_array(strtolower($extension), explode('|', $raw_formats), true)) {
$is_raw = true;
}

if (strpos($type, 'video') !== 0) {
// It's a photo
if (Configs::hasExiftool()) {
// reader with Exiftool adapter
$reader = Reader::factory(Reader::TYPE_EXIFTOOL);
} elseif (Configs::hasImagick() && $is_raw) {
// Use imagick as exif reader for raw files (broader support)
$reader = Reader::factory(Reader::TYPE_IMAGICK);
} else {
// Use Php native tools
$reader = Reader::factory(Reader::TYPE_NATIVE);
Expand Down Expand Up @@ -109,7 +123,17 @@ public function extract(string $filename, string $type): array
// Attempt to get sidecar metadata if it exists, make sure to check 'real' path in case of symlinks
$sidecarData = [];

$realFile = is_link($filename) && readlink($filename) ? readlink($filename) : $filename;
// readlink fails if it's not a link -> we need to separate it
$realFile = $filename;
if (is_link($filename)) {
try {
// if readlink($filename) == False then $realFile = $filename.
// if readlink($filename) != False then $realFile = readlink($filename)
$realFile = readlink($filename) ?: $filename;
} catch (\Exception $e) {
Logs::error(__METHOD__, __LINE__, $e->getMessage());
}
}
if (Configs::hasExiftool() && file_exists($realFile . '.xmp')) {
try {
// Don't use the same reader as the file in case it's a video
Expand Down
116 changes: 97 additions & 19 deletions app/ModelFunctions/PhotoFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,11 @@ public function __construct(Extractor $metadataExtractor, ImageHandlerInterface
*
* @return string
*/
private function file_type($file, string $extension)
public function file_type($file, string $extension)
{
// check raw files
if (in_array(strtolower($extension), explode('|', Configs::get_value('raw_formats', '')), true)) {
$raw_formats = strtolower(Configs::get_value('raw_formats', ''));
if (in_array(strtolower($extension), explode('|', $raw_formats), true)) {
return 'raw';
}

Expand Down Expand Up @@ -286,20 +287,18 @@ public function add(array $file, $albumID_in = 0, $delete_imported = false, $for
}
}

$info = $this->metadataExtractor->extract($path, $mimeType);
if ($kind == 'raw') {
$info = $this->metadataExtractor->bare();
$this->metadataExtractor->size($info, $path);
$this->metadataExtractor->validate($info);
$info['type'] = 'raw';
} else {
$info = $this->metadataExtractor->extract($path, $mimeType);
}

// Use title of file if IPTC title missing
if ($kind == 'raw') {
$info['title'] = substr(basename($file['name']), 0, 98);
} elseif ($info['title'] === '') {
$info['title'] = substr(basename($file['name'], $extension), 0, 98);
if ($info['title'] === '') {
if ($kind == 'raw') {
$info['title'] = substr(basename($file['name']), 0, 98);
} else {
$info['title'] = substr(basename($file['name'], $extension), 0, 98);
}
}

$photo->title = $info['title'];
Expand Down Expand Up @@ -400,8 +399,16 @@ public function add(array $file, $albumID_in = 0, $delete_imported = false, $for
}
}

// Create Thumb
if ($kind == 'raw') {
try {
$frame_tmp = $this->createJpgFromRaw($photo);
} catch (Exception $exception) {
Logs::error(__METHOD__, __LINE__, $exception->getMessage());
}
}

// Create Thumb
if ($kind == 'raw' && $frame_tmp == '') {
$photo->thumbUrl = '';
$photo->thumb2x = 0;
} elseif (!in_array($photo->type, $this->validVideoTypes, true) || $frame_tmp !== '') {
Expand Down Expand Up @@ -450,13 +457,14 @@ public function add(array $file, $albumID_in = 0, $delete_imported = false, $for
*/
public function createSmallerImages(Photo $photo, string $frame_tmp = '')
{
if ($frame_tmp === '') {
if ($frame_tmp === '' || $photo->type == 'raw') {
// Create medium file for normal photos and for raws
$mediumMaxWidth = intval(Configs::get_value('medium_max_width'));
$mediumMaxHeight = intval(Configs::get_value('medium_max_height'));
$this->resizePhoto($photo, 'medium', $mediumMaxWidth, $mediumMaxHeight);
$this->resizePhoto($photo, 'medium', $mediumMaxWidth, $mediumMaxHeight, $frame_tmp);

if (Configs::get_value('medium_2x') === '1') {
$this->resizePhoto($photo, 'medium2x', $mediumMaxWidth * 2, $mediumMaxHeight * 2);
$this->resizePhoto($photo, 'medium2x', $mediumMaxWidth * 2, $mediumMaxHeight * 2, $frame_tmp);
}
}

Expand All @@ -469,6 +477,51 @@ public function createSmallerImages(Photo $photo, string $frame_tmp = '')
}
}

/**
* @param Photo $photo
*
* @return string Path of the jpg file
*/
public function createJpgFromRaw(Photo $photo): string
{
// we need imagick to do the job
if (!Configs::hasImagick()) {
Logs::notice(__METHOD__, __LINE__, 'Saving JPG of raw file to failed: Imagick not installed.');

return '';
}

$filename = $photo->url;
$url = Storage::path('raw/' . $filename);
$ext = pathinfo($filename)['extension'];

// test if Imagaick supports the filetype
// Query return file extensions as all upper case
if (!in_array(strtoupper($ext), \Imagick::queryformats())) {
Logs::notice(__METHOD__, __LINE__, 'Filetype ' . $ext . ' not supported by Imagick.');

return '';
}

$tmp_file = tempnam(sys_get_temp_dir(), 'lychee') . '.jpeg';
Logs::notice(__METHOD__, __LINE__, 'Saving JPG of raw file to ' . $tmp_file);

$resWidth = $resHeight = 0;
$resWidth = $resHeight = 0;
$width = $photo->width;
$height = $photo->height;

try {
$this->imageHandler->scale($url, $tmp_file, $width, $height, $resWidth, $resHeight);
} catch (\Exception $e) {
Logs::error(__METHOD__, __LINE__, 'Failed to create JPG from raw file ' . $url . $filename);

return '';
}

return $tmp_file;
}

/**
* @param Photo $photo
*
Expand All @@ -482,11 +535,36 @@ public function extractVideoFrame(Photo $photo): string

$ffmpeg = FFMpeg\FFMpeg::create();
$video = $ffmpeg->open(Storage::path('big/' . $photo->url));
$frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds($photo->aperture / 2));

$tmp = tempnam(sys_get_temp_dir(), 'lychee');
$tmp = tempnam(sys_get_temp_dir(), 'lychee') . '.jpeg';
Logs::notice(__METHOD__, __LINE__, 'Saving frame to ' . $tmp);
$frame->save($tmp);

try {
$frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds($photo->aperture / 2));
$frame->save($tmp);
} catch (\Exception $e) {
Logs::notice(__METHOD__, __LINE__, 'Failed to extract snapshot from video ' . $tmp);
}

// check if the image has data
$success = file_exists($tmp) ? (filesize($tmp) > 0) : false;

if (!$success) {
Logs::notice(__METHOD__, __LINE__, 'Failed to extract snapshot from video ' . $tmp);
try {
$frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(0));
$frame->save($tmp);
$success = file_exists($tmp) ? (filesize($tmp) > 0) : false;
if (!$success) {
Logs::notice(__METHOD__, __LINE__, 'Fallback failed to extract snapshot from video ' . $tmp);
} else {
Logs::notice(__METHOD__, __LINE__, 'Fallback successful - snapshot from video ' . $tmp . ' at t=0 created.');
}
} catch (\Exception $e) {
Logs::notice(__METHOD__, __LINE__, 'Fallback failed to extract snapshot from video ' . $tmp);

return '';
}
}

return $tmp;
}
Expand Down
15 changes: 9 additions & 6 deletions app/Photo.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ public function prepareData()

// We need to format the framerate (stored as focal) -> max 2 decimal digits
$photo['focal'] = round($photo['focal'], 2);
} elseif ($this->type == 'raw') {
// It's a raw file -> we also use jpeg as extension
$photoUrl = $this->thumbUrl;
} else {
$photoUrl = $this->url;
}
Expand Down Expand Up @@ -335,7 +338,7 @@ public function prepareData()
$photo['url'] = Storage::url($path_prefix . $this->url);

if ($this->livePhotoUrl !== '' && $this->livePhotoUrl !== null) {
$photo['livePhotoUrl'] = Storage::url($path_prefix . $this->livePhotoUrl);
$photo['livePhotoUrl'] = Storage::url('big/' . $this->livePhotoUrl);
} else {
$photo['livePhotoUrl'] = null;
}
Expand Down Expand Up @@ -488,7 +491,7 @@ public function predelete(bool $keep_original = false)
}
}

if (strpos($this->type, 'video') === 0) {
if ((strpos($this->type, 'video') === 0) || ($this->type == 'raw')) {
$photoName = $this->thumbUrl;
} else {
$photoName = $this->url;
Expand All @@ -501,11 +504,11 @@ public function predelete(bool $keep_original = false)
// TODO: USE STORAGE FOR DELETE
// check first if livePhotoUrl is available
if ($this->livePhotoUrl !== null) {
if (!Storage::exists($path_prefix . $this->livePhotoUrl)) {
Logs::error(__METHOD__, __LINE__, 'Could not find file in ' . Storage::path($path_prefix . $this->livePhotoUrl));
if (!Storage::exists('big/' . $this->livePhotoUrl)) {
Logs::error(__METHOD__, __LINE__, 'Could not find file in ' . Storage::path('big/' . $this->livePhotoUrl));
$error = true;
} elseif (!Storage::delete($path_prefix . $this->livePhotoUrl)) {
Logs::error(__METHOD__, __LINE__, 'Could not delete file in ' . Storage::path($path_prefix . $this->livePhotoUrl));
} elseif (!Storage::delete('big/' . $this->livePhotoUrl)) {
Logs::error(__METHOD__, __LINE__, 'Could not delete file in ' . Storage::path('big/' . $this->livePhotoUrl));
$error = true;
}
}
Expand Down
Loading

0 comments on commit 16c7c92

Please sign in to comment.