livephoto: add Google and Samsung support
parent
578703768b
commit
799a39f968
|
@ -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);
|
||||
|
|
|
@ -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 '';
|
||||
}
|
||||
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
22
lib/Exif.php
22
lib/Exif.php
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue