]> git.mxchange.org Git - friendica.git/commitdiff
Add more BlurHash to avoid not being able to display some picture
authorMichael <heluecht@pirati.ca>
Sun, 11 Dec 2022 09:56:30 +0000 (09:56 +0000)
committerMichael <heluecht@pirati.ca>
Sun, 11 Dec 2022 09:56:30 +0000 (09:56 +0000)
database.sql
doc/database/db_contact.md
doc/database/db_post-link.md
src/Contact/Avatar.php
src/Model/Contact.php
src/Model/Photo.php
src/Model/Post/Link.php
src/Module/Photo.php
static/dbstructure.config.php

index 855c913580d6781114a1f4729ff74c143f31febc..49f282db0a315e5d76739e54ab871cefdcf8e7fb 100644 (file)
@@ -1,6 +1,6 @@
 -- ------------------------------------------
 -- Friendica 2022.12-rc (Giant Rhubarb)
--- DB_UPDATE_VERSION 1500
+-- DB_UPDATE_VERSION 1501
 -- ------------------------------------------
 
 
@@ -129,6 +129,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
        `xmpp` varchar(255) NOT NULL DEFAULT '' COMMENT 'XMPP address',
        `matrix` varchar(255) NOT NULL DEFAULT '' COMMENT 'Matrix address',
        `avatar` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
+       `blurhash` varbinary(255) COMMENT 'BlurHash representation of the avatar',
        `header` varbinary(383) COMMENT 'Header picture',
        `url` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
        `nurl` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
@@ -1297,6 +1298,9 @@ CREATE TABLE IF NOT EXISTS `post-link` (
        `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
        `url` varbinary(511) NOT NULL COMMENT 'External URL',
        `mimetype` varchar(60) COMMENT '',
+       `height` smallint unsigned COMMENT 'Height of the media',
+       `width` smallint unsigned COMMENT 'Width of the media',
+       `blurhash` varbinary(255) COMMENT 'BlurHash representation of the link',
         PRIMARY KEY(`id`),
         UNIQUE INDEX `uri-id-url` (`uri-id`,`url`),
        FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
index 6f5a39536699583650e540623eba076edeaff478..f8999054fde172918319399ba760f896d688608a 100644 (file)
@@ -21,6 +21,7 @@ Fields
 | xmpp                      | XMPP address                                                                                                   | varchar(255)       | NO   |     |                     |                |
 | matrix                    | Matrix address                                                                                                 | varchar(255)       | NO   |     |                     |                |
 | avatar                    |                                                                                                                | varbinary(383)     | NO   |     |                     |                |
+| blurhash                  | BlurHash representation of the avatar                                                                          | varbinary(255)     | YES  |     | NULL                |                |
 | header                    | Header picture                                                                                                 | varbinary(383)     | YES  |     | NULL                |                |
 | url                       |                                                                                                                | varbinary(383)     | NO   |     |                     |                |
 | nurl                      |                                                                                                                | varbinary(383)     | NO   |     |                     |                |
index a162453567a49c2b6e3d8af43a75562c61bd1695..cee382f3d7e7a871c52ee624dab291f8364d20a1 100644 (file)
@@ -6,12 +6,15 @@ Post related external links
 Fields
 ------
 
-| Field    | Description                                               | Type           | Null | Key | Default | Extra          |
-| -------- | --------------------------------------------------------- | -------------- | ---- | --- | ------- | -------------- |
-| id       | sequential ID                                             | int unsigned   | NO   | PRI | NULL    | auto_increment |
-| uri-id   | Id of the item-uri table entry that contains the item uri | int unsigned   | NO   |     | NULL    |                |
-| url      | External URL                                              | varbinary(511) | NO   |     | NULL    |                |
-| mimetype |                                                           | varchar(60)    | YES  |     | NULL    |                |
+| Field    | Description                                               | Type              | Null | Key | Default | Extra          |
+| -------- | --------------------------------------------------------- | ----------------- | ---- | --- | ------- | -------------- |
+| id       | sequential ID                                             | int unsigned      | NO   | PRI | NULL    | auto_increment |
+| uri-id   | Id of the item-uri table entry that contains the item uri | int unsigned      | NO   |     | NULL    |                |
+| url      | External URL                                              | varbinary(511)    | NO   |     | NULL    |                |
+| mimetype |                                                           | varchar(60)       | YES  |     | NULL    |                |
+| height   | Height of the media                                       | smallint unsigned | YES  |     | NULL    |                |
+| width    | Width of the media                                        | smallint unsigned | YES  |     | NULL    |                |
+| blurhash | BlurHash representation of the link                       | varbinary(255)    | YES  |     | NULL    |                |
 
 Indexes
 ------------
index 711a8549f26a96c7f6b85ec9de93a001210ae2b2..02a5a45d4ecda738f1e312baf9111d7b97e8c4e9 100644 (file)
@@ -90,6 +90,8 @@ class Avatar
                $filename  = self::getFilename($contact['url']);
                $timestamp = time();
 
+               $fields['blurhash'] = $image->getBlurHash();
+
                $fields['photo'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_SMALL, $timestamp);
                $fields['thumb'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_THUMB, $timestamp);
                $fields['micro'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_MICRO, $timestamp);
index c20435278f9d8973e5ccd53be9b425cb3ae38b72..8f9cb4d051c76832e52a4c9641fcf4a09f766ceb 100644 (file)
@@ -34,11 +34,15 @@ use Friendica\Core\Worker;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Network\HTTPClient\Client\HttpClientAccept;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
 use Friendica\Network\HTTPException;
 use Friendica\Network\Probe;
+use Friendica\Object\Image;
 use Friendica\Protocol\Activity;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Util\DateTimeFormat;
+use Friendica\Util\HTTPSignature;
 use Friendica\Util\Images;
 use Friendica\Util\Network;
 use Friendica\Util\Proxy;
@@ -2193,7 +2197,7 @@ class Contact
         */
        public static function updateAvatar(int $cid, string $avatar, bool $force = false, bool $create_cache = false)
        {
-               $contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'],
+               $contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'blurhash', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'],
                        ['id' => $cid, 'self' => false]);
                if (!DBA::isResult($contact)) {
                        return;
@@ -2203,8 +2207,19 @@ class Contact
 
                // Only update the cached photo links of public contacts when they already are cached
                if (($uid == 0) && !$force && empty($contact['thumb']) && empty($contact['micro']) && !$create_cache) {
-                       if ($contact['avatar'] != $avatar) {
-                               self::update(['avatar' => $avatar], ['id' => $cid]);
+                       if (($contact['avatar'] != $avatar) || empty($contact['blurhash'])) {
+                               $update_fields = ['avatar' => $avatar];
+                               $fetchResult = HTTPSignature::fetchRaw($avatar, 0, [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE]]);
+
+                               $img_str = $fetchResult->getBody();
+                               if (!empty($img_str)) {
+                                       $image = new Image($img_str, Images::getMimeTypeByData($img_str));
+                                       if ($image->isValid()) {
+                                               $update_fields['blurhash'] = $image->getBlurHash();
+                                       }
+                               }
+
+                               self::update($update_fields, ['id' => $cid]);
                                Logger::info('Only update the avatar', ['id' => $cid, 'avatar' => $avatar, 'contact' => $contact]);
                        }
                        return;
@@ -2275,7 +2290,7 @@ class Contact
                                if ($update) {
                                        $photos = Photo::importProfilePhoto($avatar, $uid, $cid, true);
                                        if ($photos) {
-                                               $fields = ['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => DateTimeFormat::utcNow()];
+                                               $fields = ['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'blurhash' => $photos[3], 'avatar-date' => DateTimeFormat::utcNow()];
                                                $update = !empty($fields);
                                                Logger::debug('Created new cached avatars', ['id' => $cid, 'uid' => $uid, 'owner-uid' => $local_uid]);
                                        } else {
index 16acab69aacd627c2fd721f173e853ac0d8e9785..82b8bd9dda3867615d146b5701de32df20b1736b 100644 (file)
@@ -313,6 +313,28 @@ class Photo
                return $fields;
        }
 
+       /**
+        * Construct a photo array for a given image data string
+        *
+        * @param string $image_data Image data
+        * @param string $mimetype   Image mime type. Is guessed by file name when empty.
+        *
+        * @return array
+        * @throws \Exception
+        */
+       public static function createPhotoForImageData(string $image_data, string $mimetype = ''): array
+       {
+               $fields = self::getFields();
+               $values = array_fill(0, count($fields), '');
+
+               $photo                  = array_combine($fields, $values);
+               $photo['data']          = $image_data;
+               $photo['type']          = $mimetype ?: Images::getMimeTypeByData($image_data);
+               $photo['cacheable']     = false;
+
+               return $photo;
+       }
+
        /**
         * Construct a photo array for a system resource image
         *
@@ -647,7 +669,11 @@ class Photo
                        $micro = Contact::getDefaultAvatar($contact, Proxy::SIZE_MICRO);
                }
 
-               return [$image_url, $thumb, $micro];
+               $photo = DBA::selectFirst(
+                       'photo', ['blurhash'], ['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
+               );
+
+               return [$image_url, $thumb, $micro, $photo['blurhash']];
        }
 
        /**
index 83e5da7b314dc49830bcfcf646fe9592e18e8187..83e5bea99fe000a62649f5497e4e910254d2a1a2 100644 (file)
@@ -28,7 +28,10 @@ use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
 use Friendica\Network\HTTPClient\Client\HttpClientOptions;
+use Friendica\Util\HTTPSignature;
+use Friendica\Util\Images;
 use Friendica\Util\Proxy;
+use Friendica\Object\Image;
 
 /**
  * Class Link
@@ -72,12 +75,14 @@ class Link
                if (!empty($link['id'])) {
                        $id = $link['id'];
                        Logger::info('Found', ['id' => $id, 'uri-id' => $uriId, 'url' => $url]);
-               } else {
-                       $mime = self::fetchMimeType($url);
+               } else { 
+                       $fields = self::fetchMimeType($url);
+                       $fields['uri-id'] = $uriId;
+                       $fields['url'] = $url;
 
-                       DBA::insert('post-link', ['uri-id' => $uriId, 'url' => $url, 'mimetype' => $mime], Database::INSERT_IGNORE);
+                       DBA::insert('post-link', $fields, Database::INSERT_IGNORE);
                        $id = DBA::lastInsertId();
-                       Logger::info('Inserted', ['id' => $id, 'uri-id' => $uriId, 'url' => $url]);
+                       Logger::info('Inserted', $fields);
                }
 
                if (empty($id)) {
@@ -114,19 +119,28 @@ class Link
         *
         * @param string $url URL to fetch
         * @param string $accept Comma-separated list of expected response MIME type(s)
-        * @return string Discovered MIME type or empty string on failure
+        * @return array Discovered MIME type and blurhash or empty array on failure
         */
-       private static function fetchMimeType(string $url, string $accept = HttpClientAccept::DEFAULT): string
+       private static function fetchMimeType(string $url, string $accept = HttpClientAccept::DEFAULT): array
        {
                $timeout = DI::config()->get('system', 'xrd_timeout');
 
-               $curlResult = DI::httpClient()->head($url, [HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::ACCEPT_CONTENT => $accept]);
-
-               if ($curlResult->isSuccess() && empty($media['mimetype'])) {
-                       return $curlResult->getHeader('Content-Type')[0] ?? '';
+               $curlResult = HTTPSignature::fetchRaw($url, 0, [HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::ACCEPT_CONTENT => $accept]);
+               if (!$curlResult->isSuccess()) {
+                       return [];
+               }
+               $fields = ['mimetype' => $curlResult->getHeader('Content-Type')[0]];
+
+               $img_str = $curlResult->getBody();
+               $image = new Image($img_str, Images::getMimeTypeByData($img_str));
+               if ($image->isValid()) {
+                       $fields['mimetype'] = $image->getType();
+                       $fields['width']    = $image->getWidth();
+                       $fields['height']   = $image->getHeight();
+                       $fields['blurhash'] = $image->getBlurHash();
                }
 
-               return '';
+               return $fields;
        }
 
        /**
index 78e403e6c751cda090c977b82a99ef33e1935d6a..5f1d65845b1213950935f24031a08a86b38c192e 100644 (file)
@@ -285,14 +285,14 @@ class Photo extends BaseModule
 
                                return MPhoto::createPhotoForExternalResource($media['url'], (int)DI::userSession()->getLocalUserId(), $media['mimetype'], $media['blurhash'], $media['width'], $media['height']);
                        case 'link':
-                               $link = DBA::selectFirst('post-link', ['url', 'mimetype'], ['id' => $id]);
+                               $link = DBA::selectFirst('post-link', ['url', 'mimetype', 'blurhash', 'width', 'height'], ['id' => $id]);
                                if (empty($link)) {
                                        return false;
                                }
 
-                               return MPhoto::createPhotoForExternalResource($link['url'], (int)DI::userSession()->getLocalUserId(), $link['mimetype'] ?? '');
+                               return MPhoto::createPhotoForExternalResource($link['url'], (int)DI::userSession()->getLocalUserId(), $link['mimetype'] ?? '', $link['blurhash'] ?? '', $link['width'] ?? 0, $link['height'] ?? 0);
                        case 'contact':
-                               $fields = ['uid', 'uri-id', 'url', 'nurl', 'avatar', 'photo', 'xmpp', 'addr', 'network', 'failed', 'updated'];
+                               $fields = ['uid', 'uri-id', 'url', 'nurl', 'avatar', 'photo', 'blurhash', 'xmpp', 'addr', 'network', 'failed', 'updated'];
                                $contact = Contact::getById($id, $fields);
                                if (empty($contact)) {
                                        return false;
@@ -364,7 +364,11 @@ class Photo extends BaseModule
                                                Logger::debug('Expected Content-Type', ['mime' => $mimetext, 'url' => $url]);
                                        }
                                }
-                               if (empty($mimetext)) {
+                               if (empty($mimetext) && !empty($contact['blurhash'])) {
+                                       $image = New Image('', 'image/png');
+                                       $image->getFromBlurHash($contact['blurhash'], $customsize, $customsize);
+                                       return MPhoto::createPhotoForImageData($image->asString());
+                               } elseif (empty($mimetext)) {
                                        if ($customsize <= Proxy::PIXEL_MICRO) {
                                                $url = Contact::getDefaultAvatar($contact ?: [], Proxy::SIZE_MICRO);
                                        } elseif ($customsize <= Proxy::PIXEL_THUMB) {
@@ -373,7 +377,7 @@ class Photo extends BaseModule
                                                $url = Contact::getDefaultAvatar($contact ?: [], Proxy::SIZE_SMALL);
                                        }
                                }
-                               return MPhoto::createPhotoForExternalResource($url, 0, $mimetext);
+                               return MPhoto::createPhotoForExternalResource($url, 0, $mimetext, $contact['blurhash'], $customsize, $customsize);
                        case 'header':
                                $fields = ['uid', 'url', 'header', 'network', 'gsid'];
                                $contact = Contact::getById($id, $fields);
index d5bbed666a172632e758f75b53b32bb7869c7ab5..d9ff07178bd05b4f393124583f22bdaae5a3741c 100644 (file)
@@ -55,7 +55,7 @@
 use Friendica\Database\DBA;
 
 if (!defined('DB_UPDATE_VERSION')) {
-       define('DB_UPDATE_VERSION', 1500);
+       define('DB_UPDATE_VERSION', 1501);
 }
 
 return [
@@ -186,6 +186,7 @@ return [
                        "xmpp" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "XMPP address"],
                        "matrix" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "Matrix address"],
                        "avatar" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
+                       "blurhash" => ["type" => "varbinary(255)", "comment" => "BlurHash representation of the avatar"],
                        "header" => ["type" => "varbinary(383)", "comment" => "Header picture"],
                        "url" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
                        "nurl" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
@@ -1323,6 +1324,9 @@ return [
                        "uri-id" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
                        "url" => ["type" => "varbinary(511)", "not null" => "1", "comment" => "External URL"],
                        "mimetype" => ["type" => "varchar(60)", "comment" => ""],
+                       "height" => ["type" => "smallint unsigned", "comment" => "Height of the media"],
+                       "width" => ["type" => "smallint unsigned", "comment" => "Width of the media"],
+                       "blurhash" => ["type" => "varbinary(255)", "comment" => "BlurHash representation of the link"],
                ],
                "indexes" => [
                        "PRIMARY" => ["id"],