livephoto: add Google and Samsung support

pull/231/head
Varun Patil 2022-11-22 08:54:19 -08:00
parent 578703768b
commit 799a39f968
5 changed files with 92 additions and 18 deletions

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace OCA\Memories\Controller;
use OCA\Memories\Exif;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\JSONResponse;
@ -163,27 +164,60 @@ class VideoController extends ApiBase
if (!$liveid) {
return new JSONResponse(['message' => 'Live ID not provided'], Http::STATUS_BAD_REQUEST);
}
$lp = $this->timelineQuery->getLivePhoto($fileid);
if (!$lp || $lp['liveid'] !== $liveid) {
return new JSONResponse(['message' => 'Live ID not found'], Http::STATUS_NOT_FOUND);
// Response data
$name = '';
$blob = null;
$mime = '';
// Video is inside the file
$path = null;
if (str_starts_with($liveid, 'self__')) {
$path = $file->getStorage()->getLocalFile($file->getInternalPath());
$mime = 'video/mp4';
$name = $file->getName().'.mp4';
}
// Get and return file
$liveFileId = (int) $lp['fileid'];
$files = $this->rootFolder->getById($liveFileId);
if (0 === \count($files)) {
return new JSONResponse(['message' => 'Live file not found'], Http::STATUS_NOT_FOUND);
// Different manufacurers have different formats
if ('self__trailer' === $liveid) {
try { // Get trailer
$blob = Exif::getBinaryExifProp($path, '-trailer');
} catch (\Exception $e) {
return new JSONResponse(['message' => 'Trailer not found'], Http::STATUS_NOT_FOUND);
}
} elseif ('self__embeddedvideo' === $liveid) {
try { // Get embedded video file
$blob = Exif::getBinaryExifProp($path, '-EmbeddedVideoFile');
} catch (\Exception $e) {
return new JSONResponse(['message' => 'Embedded video not found'], Http::STATUS_NOT_FOUND);
}
} else {
// Get stored video file (Apple MOV)
$lp = $this->timelineQuery->getLivePhoto($fileid);
if (!$lp || $lp['liveid'] !== $liveid) {
return new JSONResponse(['message' => 'Live ID not found'], Http::STATUS_NOT_FOUND);
}
// Get and return file
$liveFileId = (int) $lp['fileid'];
$files = $this->rootFolder->getById($liveFileId);
if (0 === \count($files)) {
return new JSONResponse(['message' => 'Live file not found'], Http::STATUS_NOT_FOUND);
}
$liveFile = $files[0];
if ($liveFile instanceof File) {
$name = $liveFile->getName();
$blob = $liveFile->getContent();
$mime = $liveFile->getMimeType();
}
}
$liveFile = $files[0];
if ($liveFile instanceof File) {
// Create and send response
$name = $liveFile->getName();
$blob = $liveFile->getContent();
// Make and send response
if ($blob) {
$response = new DataDisplayResponse($blob, Http::STATUS_OK, []);
$response->setHeaders([
'Content-Type' => $liveFile->getMimeType(),
'Content-Type' => $mime,
'Content-Disposition' => "attachment; filename=\"{$name}\"",
]);
$response->cacheFor(3600 * 24, false, false);

View File

@ -26,10 +26,28 @@ class LivePhoto
/** Get liveid from photo part */
public function getLivePhotoId(array &$exif)
{
// Apple JPEG (MOV has ContentIdentifier)
if (\array_key_exists('MediaGroupUUID', $exif)) {
return $exif['MediaGroupUUID'];
}
// Samsung JPEG
if (\array_key_exists('EmbeddedVideoType', $exif) && str_contains($exif['EmbeddedVideoType'], 'MotionPhoto')) {
return 'self__embeddedvideo';
}
// Google JPEG and Samsung HEIC (Apple?)
if (\array_key_exists('MotionPhoto', $exif)) {
if ('image/jpeg' === $exif['MIMEType']) {
// Google JPEG -- image should hopefully be in trailer
return 'self__trailer';
}
if ('image/heic' === $exif['MIMEType']) {
// Samsung HEIC -- no way to get this out yet
return '';
}
}
return '';
}

View File

@ -134,7 +134,7 @@ class TimelineWrite
}
// These are huge and not needed
if (str_starts_with($key, 'Nikon')) {
if (str_starts_with($key, 'Nikon') || str_starts_with($key, 'QuickTime')) {
unset($exif[$key]);
}
}

View File

@ -262,6 +262,28 @@ class Exif
}
}
public static function getBinaryExifProp(string $path, string $prop)
{
$pipes = [];
$proc = proc_open(array_merge(self::getExiftool(), [$prop, '-n', '-b', $path]), [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
], $pipes);
stream_set_blocking($pipes[1], false);
try {
return self::readOrTimeout($pipes[1], 5000);
} catch (\Exception $ex) {
error_log("Exiftool timeout: [{$path}]");
throw new \Exception('Could not read from Exiftool');
} finally {
fclose($pipes[1]);
fclose($pipes[2]);
proc_terminate($proc);
}
}
/** Get path to exiftool binary */
private static function getExiftool()
{

View File

@ -358,7 +358,7 @@ aside.app-sidebar {
}
:root {
--livephoto-img-transition: opacity 0.5s linear, transform 0.4s ease-in-out;
--livephoto-img-transition: opacity 0.4s linear, transform 0.3s ease-in-out;
}
// Live photo transitions
@ -388,7 +388,7 @@ aside.app-sidebar {
opacity: 1;
}
&.playing.canplay img {
transform: scale(1.07);
transform: scale(1.05);
}
}
</style>