diff --git a/lib/Db/LivePhoto.php b/lib/Db/LivePhoto.php new file mode 100644 index 00000000..6476a178 --- /dev/null +++ b/lib/Db/LivePhoto.php @@ -0,0 +1,78 @@ +connection = $connection; + } + + /** Check if a given Exif data is the video part of a live photo */ + public function isVideoPart(array &$exif) + { + return 'video/quicktime' === $exif['MIMEType'] && \array_key_exists('ContentIdentifier', $exif); + } + + /** Get liveid from photo part */ + public function getLivePhotoId(array &$exif) + { + if (\array_key_exists('MediaGroupUUID', $exif)) { + return $exif['MediaGroupUUID']; + } + + return ''; + } + + public function processVideoPart(File &$file, array &$exif) + { + $fileId = $file->getId(); + $mtime = $file->getMTime(); + $liveid = $exif['ContentIdentifier']; + if (empty($liveid)) { + return; + } + + $query = $this->connection->getQueryBuilder(); + $query->select('fileid') + ->from('memories_livephoto') + ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))) + ; + $cursor = $query->executeQuery(); + $prevRow = $cursor->fetch(); + $cursor->closeCursor(); + + if ($prevRow) { + // Update existing row + $query->update('memories_livephoto') + ->set('liveid', $query->createNamedParameter($liveid, IQueryBuilder::PARAM_STR)) + ->set('mtime', $query->createNamedParameter($mtime, IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))) + ; + $query->executeStatement(); + } else { + // Try to create new row + try { + $query->insert('memories_livephoto') + ->values([ + 'liveid' => $query->createNamedParameter($liveid, IQueryBuilder::PARAM_STR), + 'mtime' => $query->createNamedParameter($mtime, IQueryBuilder::PARAM_INT), + 'fileid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT), + ]) + ; + $query->executeStatement(); + } catch (\Exception $ex) { + error_log('Failed to create memories_livephoto record: '.$ex->getMessage()); + } + } + } +} diff --git a/lib/Db/TimelineWrite.php b/lib/Db/TimelineWrite.php index 4f8dfa46..7aa3dccd 100644 --- a/lib/Db/TimelineWrite.php +++ b/lib/Db/TimelineWrite.php @@ -15,11 +15,13 @@ class TimelineWrite { protected IDBConnection $connection; protected IPreview $preview; + protected LivePhoto $livePhoto; public function __construct(IDBConnection $connection, IPreview &$preview) { $this->connection = $connection; $this->preview = $preview; + $this->livePhoto = new LivePhoto($connection); } /** @@ -79,6 +81,19 @@ class TimelineWrite $cursor = $query->executeQuery(); $prevRow = $cursor->fetch(); $cursor->closeCursor(); + + // Check in live-photo table in case this is a video part of a live photo + if (!$prevRow) { + $query = $this->connection->getQueryBuilder(); + $query->select('fileid', 'mtime') + ->from('memories_livephoto') + ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))) + ; + $cursor = $query->executeQuery(); + $prevRow = $cursor->fetch(); + $cursor->closeCursor(); + } + if ($prevRow && !$force && (int) $prevRow['mtime'] === $mtime) { return 1; } @@ -91,11 +106,19 @@ class TimelineWrite } catch (\Exception $e) { } + // Hand off if live photo video part + if ($isvideo && $this->livePhoto->isVideoPart($exif)) { + $this->livePhoto->processVideoPart($file, $exif); + + return 2; + } + // Get more parameters $dateTaken = Exif::getDateTaken($file, $exif); $dayId = floor($dateTaken / 86400); $dateTaken = gmdate('Y-m-d H:i:s', $dateTaken); [$w, $h] = Exif::getDimensions($exif); + $liveid = $this->livePhoto->getLivePhotoId($exif); // Video parameters $videoDuration = 0; @@ -140,6 +163,7 @@ class TimelineWrite ->set('w', $query->createNamedParameter($w, IQueryBuilder::PARAM_INT)) ->set('h', $query->createNamedParameter($h, IQueryBuilder::PARAM_INT)) ->set('exif', $query->createNamedParameter($exifJson, IQueryBuilder::PARAM_STR)) + ->set('liveid', $query->createNamedParameter($liveid, IQueryBuilder::PARAM_STR)) ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))) ; $query->executeStatement(); @@ -158,6 +182,7 @@ class TimelineWrite 'w' => $query->createNamedParameter($w, IQueryBuilder::PARAM_INT), 'h' => $query->createNamedParameter($h, IQueryBuilder::PARAM_INT), 'exif' => $query->createNamedParameter($exifJson, IQueryBuilder::PARAM_STR), + 'liveid' => $query->createNamedParameter($liveid, IQueryBuilder::PARAM_STR), ]) ; $query->executeStatement(); diff --git a/lib/Migration/Version400800Date20221122105007.php b/lib/Migration/Version400800Date20221122105007.php new file mode 100644 index 00000000..8b7883d4 --- /dev/null +++ b/lib/Migration/Version400800Date20221122105007.php @@ -0,0 +1,98 @@ + + * @author Your name + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\Memories\Migration; + +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * Auto-generated migration step: Please modify to your needs! + */ +class Version400800Date20221122105007 extends SimpleMigrationStep +{ + /** + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + */ + public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void + { + } + + /** + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + */ + public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper + { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('memories'); + + if (!$table->hasColumn('liveid')) { + $table->addColumn('liveid', 'string', [ + 'notnull' => false, + 'length' => 256, + 'default' => '', + ]); + } + + // Live photos table + if (!$schema->hasTable('memories_livephoto')) { + $table = $schema->createTable('memories_livephoto'); + + $table->addColumn('id', 'integer', [ + 'autoincrement' => true, + 'notnull' => true, + ]); + + $table->addColumn('liveid', 'string', [ + 'notnull' => true, + 'length' => 256, + ]); + + $table->addColumn('fileid', Types::BIGINT, [ + 'notnull' => true, + 'length' => 20, + ]); + + $table->addColumn('mtime', Types::INTEGER, [ + 'notnull' => true, + ]); + + $table->setPrimaryKey(['id']); + $table->addIndex(['liveid'], 'memories_lp_liveid_index'); + $table->addUniqueIndex(['fileid'], 'memories_lp_fileid_index'); + } + + return $schema; + } + + /** + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + */ + public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void + { + } +}