Замечания и предложения по нему, а также их обсуждение.Предыдущий тред тонет тут >>1000357.
>>1000965>stretched_aoba.pngЧем и как такое забавное сгенерировано?
>>1000975gimp, Filters → Distorts → Lens Distortion. Подбирать параметры.
Автор форматтера приглашается для ревью https://codeberg.org/yakui-lover/fbe-410/pulls/19
Пока посмотрел код на телефоне. Я не автор rec_node_format.> // Currently there's a bug here: it produces 15 lines instead of 16 (compare with <br> above).Разве в контексте предложенного алгоритма !20:<li>x</li>: не корректнее сравнивать с !20:<br>x:, чем с !20:x<br>?Всегда ли ли <ul><li> означает новую строку? Или если в самом начале сообщения или сразу после <br>, то переноса строки не будет? I wonder.
!20:<li>x</li>:
!20:<br>x:
!20:x<br>
>>1001001Э… там какой-то майндфак вообще. Требует изучения. А я подозревал, что вырезание <body> и </body> по индексам это нехорошо.
Ну, видимо, дело в моей версии PHP или чём-то ещё. Здесь не воспроизводится.По существу заданных мне вопросов могу пояснить следующее:// This is correct (there is a line break after "Begin", even two of them):testcase(my_expand('Begin<ul>!20:<li>x</li>:</ul>End'), my_expand('Begin<ul>!15:<li>x</li>:</ul>'));// This is incorrect (15 lines total, not 16):testcase(my_expand('<ul>!20:<li>x</li>:</ul>'), my_expand('<ul>!15:<li>x</li>:</ul>'));
// This is correct (there is a line break after "Begin", even two of them):testcase(my_expand('Begin<ul>!20:<li>x</li>:</ul>End'), my_expand('Begin<ul>!15:<li>x</li>:</ul>'));// This is incorrect (15 lines total, not 16):testcase(my_expand('<ul>!20:<li>x</li>:</ul>'), my_expand('<ul>!15:<li>x</li>:</ul>'));
Поскольку автор форматтера не объявился, давайте рассмотрим PR в его отсутствие.И это, так а чего, будем прикручивать поиск картинок? Для этого постгрес не нужен: вот это отрабатывает в MySQL за 40 мс:SELECT filename, hash0, hash1, hash2FROM imagesWHERE id BETWEEN 3 AND 199997ORDER BY BIT_COUNT(hash0 ^ %s) + BIT_COUNT(hash1 ^ %s) + BIT_COUNT(hash2 ^ %s) ASCLIMIT 30(ПослеCREATE TABLE images ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(32) NOT NULL, hash0 BIGINT UNSIGNED NOT NULL, hash1 BIGINT UNSIGNED NOT NULL, hash2 BIGINT UNSIGNED NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4и вставки 200k записей со случайными хешами).Вроде бы даже «на холодную» столько же времени требуется.А хэшер скомпилировать в WASM и запускать на сервере и на клиенте — оказывается, синусы-косинусы-корни зависят от платформы, IEEE 754 не определяет результат до последнего бита. А WASM определяет. Да и сишкой тыкать в живой интерпретатор PHP как-то небезопасно.
SELECT filename, hash0, hash1, hash2FROM imagesWHERE id BETWEEN 3 AND 199997ORDER BY BIT_COUNT(hash0 ^ %s) + BIT_COUNT(hash1 ^ %s) + BIT_COUNT(hash2 ^ %s) ASCLIMIT 30
CREATE TABLE images ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(32) NOT NULL, hash0 BIGINT UNSIGNED NOT NULL, hash1 BIGINT UNSIGNED NOT NULL, hash2 BIGINT UNSIGNED NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
>>1001006> И это, так а чего, будем прикручивать поиск картинок?Всё не могу пока включиться в работу. Так что прошу, если можете, взять дело в свои руки.> BIT_COUNT(hash0 ^ %s) + BIT_COUNT(hash1 ^ %s) + BIT_COUNT(hash2 ^ %s)С Постгресом, как минимум, можно обойтись одним bit(192) столбцом вместо трёх. Но если есть желание вскорости увидеть рабочий вариант... Решайтесь.
>>1001007Хорошо.Из последних упражнений:Можно заставить выделять памяти максимум 6 байт на пиксель. Это вместе с преобразованием прозрачности в чёрное или белое в зависимости от weighted luma, как у вас. imagemagick, вроде, максимум 4 байта на пиксель может потреблять, ffmpeg — 12.Center crop на 87.5% хорошо работает, результаты лучше.Выглядит интересным https://github.com/manjunath5496/ML-Papers/blob/main/Saliency Detection A Spectral Residual Approach.pdf,но у авторов большие проблемы с выражением своих мыслей формализмами — большинство формул и формулировок означают не то, что можно подумать)Но #2 и #3 это на потом.> С Постгресом, как минимум, можно обойтись одним bit(192) столбцом вместо трёх.Ну, в MySQL тоже есть BINARY(192). Только BIT_COUNT с ним не работает (он обрезает входное значение до 64 бит). И, почему-то, даже с BIT_COUNT(whole_hash), что неправильно, оно работает медленнее.Да и какая разница, если 40 мс?
BIT_COUNT(whole_hash)
> BIT_COUNT(whole_hash)BIT_COUNT(whole_hash ^ UNHEX(%s)), конечно же.
BIT_COUNT(whole_hash ^ UNHEX(%s))
Будет ли принята фича с поиском похожих картинок, если таковая будет реализована? Там на сервере нужно будет запускать только ffmpeg и мою штуку через что-то типа wasmtime с максимальными ограничениями (без доступа к сети, ФС, etc, с лимитами на память и время работы — настраивается в wasmtime), чтобы было безопасно.
>>1001014>wasmtimeНет. Пишите на языке кодобазы.
>>1001015На языке кодобазы это будет непрактично, поскольку подход требует обработки изображений, которую у меня не получается выразить в терминах libvips или чего-либо ещё.Ну и мы планировали это дело на клиенте ещё запускать — там-то точно нужен будет васм, лол.В общем, weird. Ну может кто-то, не я, сделает версию без блюра на PHP. Но зачем дублировать то, что на клиенте будет.Васмтайм, оказывается, нет в репозиториях дебиана. Но есть wasmedge и wazero. Чем они-то не угодили?
>>1001016На языке отличным от кодобазы это будет непрактично, потому что сопровождающему тогда придётся знать 2-2.5 языка вместо 1.5.Пишите отдельный продукт в отдельной репе. А дальше если это можно вызывать через exec, то и славно.
>>1001017OK. Этот отдельный продукт может требовать для запуска на сервере какой-либо рантайм WASI (wasmtime/wasmedge/whatever)? Без этого можно, но нужно будет заморачиваться с изоляцией (ну и детерминизм потеряется, хотя, опять же, nobody cares).Запускать какой-то сишный код без изоляции на сервере кажется плохой идеей даже мне, автору кода.
>>1001019> детерминизм потеряетсяПроблему с синусами-косинусами-корнями разве нельзя решить, захардкодив матрицы преобразования?> спойлерКто он, safe и blazing fast? Верно, дети. Это Rust!>>1001016> подход требует обработки изображений, которую у меня не получается выразить в терминах libvipsКакие конкретно моменты не получется? Попробую в субботу посмотреть-таки.
>>1001019У нас там magic и ffmpeg уже запускаются от апача, какая ещё изоляция? Я-то могу что-нибудь настроить, но в случае Соуса, думаю будет проще какой-нибудь просто Докер обернуть.
>>1001022> Проблему с синусами-косинусами-корнями разве нельзя решить, захардкодив матрицы преобразования?Нет, потому что там ещё exp в blur_gauss_dyn. А вообще, если мы не компилируемся в WASM и не включаем в себя всю лабуду для декодирования изображений, то не имеет смысла заботиться о каком-то детерминизме: всё равно RGBA, которые надекодирует библиотека, может отличаться от платформы к платформе (минимальные пары есть), и уж точно от того, что надекодирует браузер, если мы на клиенте собираемся использовать его возможности для декодирования, а не полагаться на WASM, в котором всё нужное для декодирования уже есть.> Кто он, safe и blazing fast? Верно, дети. Это Rust!Ну вот тут не вполне понятно, какой от него прок. Быстрее, чем текущее под bwrap, точно не будет.> Какие конкретно моменты не получется?Момент blur_gauss_dyn & scale_nn. Там нужно считать свёртку. Не во всех пикселях, а только в тех, которые нужны scale_nn. Свёртка прямоугольная.>>1001023> У нас там magic и ffmpeg уже запускаются от апачаmagic и ffmpeg написаны понятно кем, их код всегда под пристальным присмотром, они есть в репозиториях всех крупных дистрибутивов, etc. Поэтому их не должно быть страшно запускать.Вот эту мою хреновину — должно быть.> , какая ещё изоляция?bwrap. Или firejail. Или Podman/Docker/что-то там в systemd завезли.$ ffmpeg -hide_banner -loglevel error -i картинка.jpg -vf format=rgba -f image2pipe -vcodec pam - | bwrap --ro-bind /usr/bin/busybox /usr/bin/busybox --unshare-all /usr/bin/busybox sha1sumeb3609abf7df96a19edf40c059bb2f15f2bf5911 -$(Для повторения нужен busybox-static, наш хэшер будет тоже статически слинкован, если будет просто читать PAM из stdin.)Это изоляция вообще всего, включая сеть, ФС, user namespace, etc.Ну или не через пайп, а использовать ту же SDL2_Image, и прокидывать всё нужное-слинкованное тоже через bwrap.> Я-то могу что-нибудь настроить, но в случае Соуса, думаю будет проще какой-нибудь просто Докер обернуть.Можно. Но не знаю, зачем заморачиваться с чем-то ещё, если bwrap умеет всё, что нужно, и есть в репозиториях.
$ ffmpeg -hide_banner -loglevel error -i картинка.jpg -vf format=rgba -f image2pipe -vcodec pam - | bwrap --ro-bind /usr/bin/busybox /usr/bin/busybox --unshare-all /usr/bin/busybox sha1sumeb3609abf7df96a19edf40c059bb2f15f2bf5911 -$
> bwrapЯ тут обнаружил, что systemd-run --scope ещё лучше: даже не нужно устанавливать отдельный пакет, и оно работает везде, где есть системд, а bwrap в таком виде не работает на каких-то хентайных hardened-версиях дистрибутивов.«Если у вас не systemd, делайте bwrap. Если у вас не bwrap, сделайте chmod u+s /usr/bin/bwrap или обращайтесь к нам за помощью, если мы ещё живы.»
systemd-run --scope
chmod u+s /usr/bin/bwrap
>>1001024> Момент blur_gauss_dyn & scale_nnИ правда, крайне неудобно, если возможно.Судя по всему, в libvips нету условного reduce. Поэтому в терминах php-vips для подсчёта значения в заблюреном пикселе надо будет делать extract_area вокруг него и multiply'ить её на ядро, потом брать у результата avg (sum нету) и умножать среднее на количество элементов в матрице. extract_area не копирует память, а вот multiply, как я понимаю, будет. Что не есть эффективно. И ещё проблема с clamp.В чём-то побыстрее и понизкоуровнее PHP вроде есть воспользоваться более низкоуровневым Vips.Region и обращаться к пикселям по указателю.> uint32_t rgba = f(f_userdata, x, y);На всякий случай. Grayscale, как Соусная "Неправда", и 16-битные цвета, как в некоторых PNG аниме-скриншотах, обрабатываются корректно?
RGB565 ещё.
>>1001028> На всякий случай. Grayscale, как Соусная "Неправда", и 16-битные цвета, как в некоторых PNG аниме-скриншотах, обрабатываются корректно?Sure thing. Это же SDL2 этим занимается, а нам просто предоставляет SDL_GetRGBA() и количество байт на пиксель (bpp). Мы читаем bpp байт по нужному смещению, передаём результат и SDL_PixelFormat *format в SDL_GetRGBA(), она декодирует в rgba.format->format может принимать одно из большого списка значений SDL_PIXELFORMAT_*.У меня готов образец, который читает PAM из ffmpeg’а. И даже прозрачность выставляет в чёрное или белое в зависимости от средней люмы (в основной ветке это пока не реализовано). Ветка prod. Сделать ./build_main_pam.sh и запускать такffmpeg -hide_banner -loglevel error -i PICTURE.jpg -vf format=rgba -f image2pipe -vcodec pam - | ./main_pam
SDL_PixelFormat *format
format->format
SDL_PIXELFORMAT_*
ffmpeg -hide_banner -loglevel error -i PICTURE.jpg -vf format=rgba -f image2pipe -vcodec pam - | ./main_pam
(На картинке была демонстрация корректность обработки grayscale старым хэшером с SDL2_Image.)Статическая сборка (CC="gcc -static -s" ./build_main_pam.sh) даёт бинарник в 747 кб, что вполне. Его нужно будет потом просто запускать под systemd-run --scope (...какие-то_опции...).Осталось код на PHP и фронтенд написать.
CC="gcc -static -s" ./build_main_pam.sh
systemd-run --scope (...какие-то_опции...)
>>1001031>>1001032Замечательно.> ffmpegХм. Там такая проблема вроде, что он отказывается картинки, превосходящие где-то 20000 (?) пикселей по одной стороне. С одной стороны, релевантно для сшивок манги. С другой, вряд ли есть польза от phashing'а таких сшивок.
>>1001033> Там такая проблема вроде, что он отказывается картинки, превосходящие где-то 20000 (?) пикселей по одной стороне.Всё гораздо интересней. Ограничения такие:(8×W + 1024)×(H + 128) < 2147483647;W < 65536;H < 65536.На практике это означает W×H < ≈260'000'000 (на самом деле верхний предел 259M〜264M в зависимости от aspect ratio).При этом приведённая мной команда ffmpeg выделяет 8 байт на один пиксель. А наша программа ещё допольнительно 2 байта на пиксель.Если мы хотим, чтобы всё вместе это потребляло не больше, скажем, 1G (лимит по умолчанию у imagemagick), то площадь (W×H) должна быть < ≈107'000'000. То есть ffmpeg’у нужно передавать именно те самые -max_pixels 100M, про которые мной говорилось в >>1000949.А ≈107M (ограничение по памяти, которую дозволительно скушать) это меньше, чем ≈260M (техническое ограничение ffmpeg’а).Мне кажется, получилось неплохо: для компиляции, кроме build-essential, ничего не нужно; собранный бинарник не может сломаться после обновлений, ибо статический; systemd-run даже не нужно устанавливать.
-max_pixels 100M
1. Должна ли функциональность «похожие картинки» находиться за [кф]апчей?2. Название пуродакуто®©? Изначально не хотелось название.
>>1001038Под похожие, может быть, а под почти идентичные, думаю, нет.Я как это себе представлял: пользователь выбирает картинку для прикрепления, её хэш тут же отправляется серверу, а сервер возвращает, есть ли likely идентичные картинки. Если есть, показывается в status bar'е о том уведомление и reflink на одну из.Но это про почти идентичные. А под похожие, наверное, окно вроде «Избранные нити» или отдельную страницу? На которую в уведомлении давать ссылку, если почти идентичные или похожие были обнаружены.You decide. Я бы на чём-то совсем утилитарном остановился вроде round_phash.
>>1001039Вы понимаете, что такое захочется трогать даже не только для того чтоб постить - а чтоб найти что-то милое?
>>1001040Ну, я из соображений отказоустойчивости и хотел HNSW индекс.Но будут ли желающие? Ведь iqdb вроде не трогают для целей поиска милоты, и вряд ли iqdb для таких целей сколь-нибудь удовлетворительно. И надолго ли желания у желающих хватит? Скажем, если похожие картинки бывают редко: сделать поиск без капчи вплоть до расстояния, где false positive rate < 0.001). Более глубокий пока отключить вовсе, а потом спрятать за капчу. Сейчас, мне кажется, менять-возиться с капчесистемой будет нецелесообразно муторно в рамках MVP.Как альтернативу капче можно потом rate limiter.
О чём вы?MySQL пробегается по 200k записям за 40 мс (мой эксперимент из >>1001006). У нас сейчас ≈20k картинок. ffmpeg [...] largest_image.webp | ./main_pam отрабатывает, on the other hand, за 3.7 сек.Т.е. bottleneck в вычислении хэша, а не в поиске.Даже если у нас накопится >200k картинок, можно брать 200k последних (для этого я замерял вместе с WHERE clause).Мы ещё не перешли на постгрес. Даже если перейдём, непонятно, будет ли от HNSW вообще какая-либо польза.
ffmpeg [...] largest_image.webp | ./main_pam
В общем предлагаю сделать что-то типа такого.Не знаю, нужно ли syncWithReality().Можно вместо этого написать скрипт.Для работы с load balancer’ом нужно будет расширить протокол Load_Balancer, добавив хотя бы Get_Available_Files.Само изображение можно запрашивать по URL (это KU_FILESERVER?).Потом интегрировать в кодобазу.class SimilarPicture { public string $board; public int $postid; public int $distance;}class SimilarPicturesService { __construct( MySQL $tc_db, string $hasherExecutablePath, int $maxRowsToQuery = 200_000, int $maxSimilarToFind = 30, string $isolationMethod = 'via_systemd_run' ) // Returns false if image cannot be hashed. addRecord( string $board, int $postid, string $imagePath ) : bool // Returns null if image cannot be hashed, otherwise an array of 'SimilarPicture' objects. findSimilar( string $imagePath, int $maxDistance = -1 ): array|null // Delete everything except the latest 'maxRowsToQuery' rows. deleteOld() : void // Delete rows for all images not physically present on disk anymore. // If $onlyDelete === false, also hash, and add rows for, all images that are physically present on disk. // This may take some time. syncWithReality(bool $onlyDelete) : bool}
class SimilarPicture { public string $board; public int $postid; public int $distance;}class SimilarPicturesService { __construct( MySQL $tc_db, string $hasherExecutablePath, int $maxRowsToQuery = 200_000, int $maxSimilarToFind = 30, string $isolationMethod = 'via_systemd_run' ) // Returns false if image cannot be hashed. addRecord( string $board, int $postid, string $imagePath ) : bool // Returns null if image cannot be hashed, otherwise an array of 'SimilarPicture' objects. findSimilar( string $imagePath, int $maxDistance = -1 ): array|null // Delete everything except the latest 'maxRowsToQuery' rows. deleteOld() : void // Delete rows for all images not physically present on disk anymore. // If $onlyDelete === false, also hash, and add rows for, all images that are physically present on disk. // This may take some time. syncWithReality(bool $onlyDelete) : bool}
Алсо, ping regarding PRs.
>>1001044В рамках текущей архитектуры, не проще ли получать phash файла в upload.class.php, обращаясь, также как и в случае с генерацией preview, к файлу по $_FILES['imagefile']?И либо передавать получаемый в $upload_class пхэш методу $post_class, либо insert'ить в image_phashes таблицу прямо в board.php, если есть что.А удалять запись из image_phashes при удалении поста или только его файла; пусть у нас и несколько разных функций связанных с удалением в кодобазе, каждую из которых надо обновить.Если отказаться от кроссбородового поиска, можно без особых неудобств хранить пхэши в тех же posts_ таблицах: тогда и функции удаления обновлять не надо, правда, поиск может стать куда дольше.
>>1001047> В рамках текущей архитектуры, не проще ли получать phash файла в upload.class.php, обращаясь, также как и в случае с генерацией preview, к файлу по $_FILES['imagefile']?> И либо передавать получаемый в $upload_class пхэш методу $post_class, либо insert'ить в image_phashes таблицу прямо в board.php, если есть что.> А удалять запись из image_phashes при удалении поста или только его файла; пусть у нас и несколько разных функций связанных с удалением в кодобазе, каждую из которых надо обновить.Оно и будет так, только функции, связанные с хэшами, будут в отдельном модуле.Я не вижу недостатков в этом.Из преимуществ: separation of concerns (у нас и так manage.class.php весит 212 Kb); легче будет выпилить это всё, если будет надобность.> только его файлаАдминистратор может удалить файлы через rm -rf, если через модерку по какой-то причине не работает. syncWithReality() нужно для этого. Ну ещё и для хэширования всего, что есть, при первом деплое этой фичи.> Если отказаться от кроссбородового поиска, можно без особых неудобств хранить пхэши в тех же posts_ таблицах: тогда и функции удаления обновлять не надо, правда, поиск может стать куда дольше.Не понял про «функции удаления обновлять не надо». А таблицы с неконстантными именами это плохо, уже натерпелись с posts_$board (это единственное, что сейчас держится на дисциплине, всё остальное сделано/можно сделать с ? placeholders).
rm -rf
syncWithReality()
posts_$board
?
Я, наверное, несколько непонятно выразился в >>1001044.Следует читать так:В общем предлагаю сделать что-то типа такого. Потом интегрировать в кодобазу.<отступление>Не знаю, нужно ли syncWithReality().Можно вместо него (метода syncWithReality()) написать скрипт.Если этот метод всё-таки будет иметь место, и он должен будет работать с load balancer’ом, то нужно будет расширить протокол Load_Balancer, добавив хотя бы Get_Available_Files.В таком случае изображение можно запрашивать по URL (это KU_FILESERVER?), не добавляя ничего дополнительного в протокол Load_Balancer.
>>1001045Я в малой доступности ещё джве недели.
>>1001050ОК.>>1001047> Если отказаться от кроссбородового поиска, можно без особых неудобств хранить пхэши в тех же posts_ таблицах: тогда и функции удаления обновлять не надо, правда, поиск может стать куда дольше.Вашу эту мысль я теперь понял. Я не хочу так делать. Не только из-за скорости поиска (не измерял, но думаю, что таки да, куда), а ещё и из-за количества действий, необходимых для того, чтобы эту фичу потом выпилить. Orthogonality.
>>1001045Последнее с изменением ДБ солью чуть позднее.
Тред тонет. Пожалуйста, перекатите.>>1001053Спасибо. >>1001044 более-менее принято? Вместо syncWithReality() будет скрипт, иначе слишком хрупко это всё будет (у апача и PHP в нескольких разных местах стоят таймауты на время исполнения кода на пхп для страницы).
ЪЭЯГОУ
ВНАКЛЫ
ЖЭУГЧБ
ЧЁЪЦЖУ
ЧВФПДЪ
ИАКХНС
ДПЕБЯЭ
МНЦБЙЭ
ШЩНСЭМ
ЫЗТФЖУ
Можете удолить, это я тестировал [Pr. 109]. По паролю (DELETE_ME) не удаляется почему-то.