shikhalev.*

Рубрикатор

Последние записи

ТехнологииПрограммированиеRubyWeb

2026.08.05 • Иван Шихалев

Плагин jekyll-is-images v0.8.0.2

Картинка для привлечения внимания

Сделал таки плагин для Jekyll, реализующий именно то, и именно так, как мне надо…

Коротко суть: картинки обрезаются, ресайзятся и конвертируются в зависимости от параметров, и далее заворачиваются в теги <figure>. Более того, легко и непринужденно из этих картинок формируются галереи, которые тоже можно всячески кастомизировать.

Подробности можно почитать в README-ru проекта на Github. Здесь коротко.

Зависимости и ограничения

  • Требуется ImageMagick 6-й или 7-й версии. И это та зависимость, которая не подтягивается через bundle install по по­нят­ным причинам.

  • Полноценно плагин будет работать, только если в качестве markdown-конвертера выбран Kramdown (это дефолтный выбор, так что, думаю, для большинства пользователей Jekyll — не проблема).

    • Кроме того, в настройках самого kramdown нужно выбрать кастомный парсер ISKram, что может стать проблемой, если уже используется какой-то кастомный парсер…

      markdown: kramdown
      kramdown:
        input: ISKram
  • Запуск с нуля — при пустом кэше — это долго.

    У меня получилось примерно 47 минут для этого сайта. Повторный запуск, когда кэш в наличии, занимает чуть больше полутора минут.

Далее о хорошем.

Синтаксис

Стандартный. Т.е. пишем, как это в Markdown принято, ![Text](url){: атрибуты } и радуемся. Вся кастомизация сидит в ат­ри­бу­тах и контексте.

Если разметка рисунка сидит на отдельной строке, отбитая пустыми строками сверху и снизу, то она заворачивается не в <p>, как это обычно делает Kramdown, а в <figure>.

Но самая магия начинается, когда у нас в одном абзаце несколько картинок, и нет ничего другого — в этом случае формируется галерея.

Можно, впрочем, использовать и liquid-теги {% image ... %} и {% gallery ... %}, как раз на тот случай, если нет возможности использовать Kramdown с кастомным парсером. Правда, Kramdown все равно подтянется по зависимостям, и именно он будет разбирать параметры тегов.

Превьюшки для соцсетей

Штатно Jekyll позволяет указать картинку, которую соцсети будут использовать для превью. Плагин позволяет кропнуть и отресайзить эту картинку, а также накладывает на нее заголовок поста, дату и название сайта.

Кстати, именно это и занимает большую часть времени работы — формирование и наложение текста ImageMagick’ом.

Статус и планы

Текущая версия — 0.8.0.2, т.е., как и все 0.8.xxx у меня — это скорее публичная альфа, чем что-то еще.

Далее буду допиливать фичи и, главное, шлифовать стилизацию. Но делать это получится не быстро, поскольку проверять буду не на сферическом в вакууме сайте, а на своем живом — параллельно с редизайном…

Upd по GitHub Actions

Так и не удалось пока подружить плагин с ImageMagick v6. Пришлось через одно место ставить v7. Вероятно, буду все-таки уже в ближайшее время делать сборку через собственный контейнер, иначе слишком много бубнов сотрется…

CSSHTMLJekyll

ФотоОбработкаТехнологииСофтГрафика

2026.07.25 • Иван Шихалев

AI-функции в Darktable 5.6

Модуль «neural restore»

Недавно вышел Darktable 5.6.0. Там довольно много нового и интересного, но, конечно, главное, что обращает на себя внимание — это AI-функции. Имеются: шумоподавление в RAW, просто шумоподавление, апскейл (т.е. увеличение картинки) и маски. Маски я пока не смотрел — я ими как-то вообще не пользуюсь и не очень понимаю, что проверять. Шумодавами и апскейлом я, правда, тоже почти не пользуюсь, но куда смотреть — представляю.

Что дают?

Итак. «raw denoise», «denoise» и «upscale». Как можно видеть на скриншоте, они объединены в один модуль, и это не модуль обработки, он не участвует цепочке обработки и не попадает в историю, это служебный модуль, и находится он в левой панели.

По кнопке «process» генерируется отдельный новый файл — TIFF в случае «denoise» и «upscale», и DNG — в случае «raw denoise». По умолчанию он размещается рядом с исходным и сразу регистрируется в библиотеке.

За исходник «raw denoise» берет исходное состояние RAW-файла, тогда как «denoise» и «upscale» — результат всех примененных обработок. Что, безусловно, логично, но не консистентно. К то­му же конкретно для «denoise» это неудобно — по идее это действие должно быть внутри цепочки, ближе к началу… Я недоучел этот момент, когда делал свои тесты (о чем ниже), но переделывать не буду — «правильный» порядок действий слишком заморочный, чтобы реальный пользователь им занимался.

Где взять?

Тут есть засада: если вы как порядочный человек просто обновитесь из своего дистрибутива, не факт, что новые фичи у вас появятся. У меня в Gentoo, например, не появились. Требуется сборка с дополнительным флагом, и когда мейнтейнеры разберутся — неизвестно. Поэтому самый простой способ — скачать официальный релиз с GitHib. Для Linux там имеется AppImage, и он у меня заработал.

Затем нужно зайти в настройки.

Скриншот настроек
  • Включаем вверху галочку «enable AI features».

  • Скачиваем модели. Все, или только дефолтные.

  • Закрываем Darktable и запускаем его снова.

Отдельный вопрос: как включить выполнение на GPU. По идее, нужно установить ONNX и радоваться, но у меня не получилось — ONNX установил, но он не цепляется. Для пробы это не критично, да и для использования — не так, чтобы очень — если нужно обработать небольшое количество фотографий, скорость на CPU вполне терпима.

Что получилось?

TL;DR
  • Как работает raw denoise мне в целом понравилось, это сильно лучше шумоподавления в стандартных модулях.

  • А вот denoise просто на высоких ISO порождает артефакты. Отчасти эти артефакты могут усиливаться тем, что у ме­ня по умолчанию включен локальный контраст, а denoise, как сказано выше, берет уже обработанное изображение.

  • Что касается upscale… Я не понял. Если брать 100% кроп, то какого-то улучшения деталей в сравнении с тупым увеличением при экспорте (там можно разрешить увеличение). Общая картинка выглядит пошарпленной, но это опять же может быть вызвано двойным применением локального контраста.

Ниже много тестовых картинок.


Читать далее »

AIDarktableRAWнейросетишумоподавление

ТехнологииПрограммированиеRubyПриродаБёрдвотчинг

2026.03.27 • Иван Шихалев

iNatGet v0.9.2 и его практическое применение

Версия 0.9.0.18

Итак, финальные отчеты по сво­им районным проектам на iNa­tu­ra­list я сфор­ми­ро­вал, пользуясь уже новой версией inat-get. Сразу выяснилось, что вер­сия 0.9.01, несмотря на то, что я старался для ба­зо­вых вещей писать тесты, никуда не го­дит­ся. Впрочем, это нормально на дан­ном этапе (ранней беты). В ре­зуль­та­те, отлаживая на ре­аль­ных данных, я дошел до вер­сии 0.9.0.18 — уже вполне рабочей.

Полученными отчетами я вполне доволен. Примеры:

В подвале скриптов можно разглядеть мелким шрифтом, что сгенерированы они версией 0.9.0.15, а не .18. Это потому, что самые последние исправления в вет­ке 0.9.0 касались исключительно оптимизаций, и на результат не влияли.

Средств для удоб­но­го формирования отчетов все еще нет, пишу текстом в фай­лы, так что скрипты отчетов получились довольно развесистые. Я их поместил в от­дель­ный репозиторий ing-sv-districts — можно полюбоваться, хотя стру­к­ту­ра там сильно так себе…

Версия 0.9.2

А здесь добавлена довольно мелкая фи­ча — поддержка ERB, как в ка­чес­т­ве шаблонов, вызываемых из поль­зо­ва­тель­с­ких скриптов, так и в ка­чес­т­ве пользовательских скриптов как таковых. Не то, чтобы это было существенное улучшение, но может, кому и пригодится.

Переводить районные скрипты на этот механизм я не стал, поскольку это ничего нового не да­ет. Впрочем, возможно, имело бы смысл для до­пол­ни­тель­ной отладки…

Промежуточные итоги

В общем и целом, текущая архитектура вполне годная, от глав­ных тормозов вер­сии 0.8.x удалось избавиться, основной упор по вре­ме­ни идет в ско­рость интернета, причем обновления, как и задумано, берутся ин­к­ре­мен­т­но — через параметр updated_since, что резко ускоряет повторные запросы.

Но работа с ло­каль­ной БД оставляет желать лучшего, груп­пи­ро­воч­ные запросы промахиваются мимо индексов, причем даже группировки по поль­зо­ва­те­лям, которые самые простые по сво­е­му внутреннему устройству… Что ж, значит следующая ите­ра­ция — 0.9.4, запланированная для ре­ше­ния именно этих про­б­лем — запланирована не зря. Заметные тормоза, впрочем, видны только на ге­не­ра­ции итоговой сводки — она сейчас занимает у ме­ня пару часов, что печально, конечно, но по срав­не­нию с 0.8.x — просто прекрасный результат.

Что по­ра­до­ва­ло — уже отлаженный на SQLite вариант на Post­greSQL заработал сра­зу — спасибо Sequel. Скорость работы на раз­ных СУБД практически не от­ли­ча­ет­ся. Нужны, конечно, аккуратные замеры, но в це­лом разницу можно игнорировать.

Что дальше?

Общий роадмап имеется на ви­ки проекта — Roadmap. Кстати, я тут «на­вайб­ко­дил» генератор ро­ад­ма­пов из Is­su­es и Mi­le­sto­nes — action-is-roadmap — довольно прикольно получилось, люблю наглядность. Даты майл­сто­у­нов проставлены от фо­на­ря — чисто для упо­ря­до­че­ния, как обещания их воспринимать точно не стоит.

А если не вдаваться в детали, то ключевые задачи такие:

  • Оптимизация запросов.

    Не знаю, насколько удастся их ускорить, но желательно выжать все возможное.

  • Доделать кэширование.

  • Удобный конструктор отчетов.

    Чтобы не нужно было заморачиваться на оформление в пользовательских скриптах. Писать их, думая только о ло­ги­ке, причем в тер­ми­нах множеств и выборок.

  • Доделать работу с про­чи­ми данными.

    Пока реализованы далеко не все возможные запросы и фильтры, есть куда развиваться. И здесь нужно будет не за­бы­вать об оп­ти­маль­нос­ти запросов.

Наверное, по ходу дела будут появляться новые задачи, и уж точ­но — находиться новые баги…

ERBPostgreSQLSQLiteSequeliNatGetiNaturalistinat-get

ТехнологииПрограммированиеRubyПриродаБёрдвотчинг

2026.03.04 • Иван Шихалев

iNatGet v0.9.0

Что ж… Вот я и за­ре­ли­зил 🌿 iNatGet v0.9. Через два с небольшим года после v0.8, и через примерно год после последней вер­сии — v0.8.0.16. Обо всех этих минорных корректирующих версиях я ничего не писал, главным образом потому, что там мучительно правились мелкие баги, а нормальная работа требовала глобального рефакторинга (т.е. переписать заново с нуля), на который никак не находилось времени.

Однако все это время я не прекращал пользоваться своей программой, регулярно формируя отчеты для своих проектов1 на iNa­tu­ra­list. Таким образом я постепенно уточнял свое понимание того, как это в принципе должно работать и где у меня провалы в архитектуре. Плюс развитие ней­ро­се­тей… В проекте ни строчки сгенерированного кода, но для консультаций и ревью я пользовался различными LLM очень активно2, что и позволило сравнительно быстро написать новую, полностью переделанную, версию.

Если версии v0.8.x я характеризовал как альфа-версии, то ветка v0.9.x пусть будет бетой, но пока — очень ранней бетой

Коротко о том, что это за проект вообще, можно прочитать в README-ru.md в репозитории.

  1. См. журналы проекта «Биоразнообразие районов Свердловской области» и его дочерних. 

  2. О своем отношении и подходе к использованию нейросетей я подробно писал в посте: «Заметки об LLM и нейросетях вообще»


Читать далее »

iNatGetiNaturalistinat-get

ТехнологииПрограммированиеRubyБД

2026.02.05 • Иван Шихалев

Автодокументирование моделей Sequel

КДПВ сгенерирована по запросу «(yard) is (sequel) in winter fantasy style, detailed eyes»

Набросал предварительную версию YARD-плагина для автодокументирования Sequel-моделей — yard-is-sequel. Су­щес­т­ву­ю­щий yard-sequel с современными версиями YARD/Ruby/Sequel не работает.

Мой вариант, конечно, не может пока похвастаться полнофункциональностью (версия 0.8.0 — это ранняя альфа), но кое-что самое важное умеет:

  • Генерирует список ассоциаций: many_to_many, many_to_one и one_to_many с корректными ссылками на типы.

  • Генерирует список полей. Также с типами, но тут есть нюансы…

    1. Маппинг типов полей требует доработки. Кроме того, ти­пы, не поддерживаемые SQLite, скорее всего, не будут нормально обрабатываться в принципе.

      Поля берутся из Database#schema на созданной in-memory SQLite базе данных. Было бы хорошо, безусловно, брать их непосредственно из миграций, но пока непонятно, как это сделать.

    2. Путь к миграциям следует указать через переменную окружения.

Чего нужно доделать?

  • Отрефакторить и упростить обработку ассоциаций.

  • Расширить обработку типов в полях и сделать ее менее хрупкой. Сейчас есть подозрение, что минорная смена версии Sequel может всё поломать…

  • Добавить возможность брать схему из отдельно сохраненного файла (в формате JSON, скорее всего).


Но надо понимать, что я буду что-то править и дорабатывать только постольку, поскольку мне это самому нужно… Однако, если кто-то предложит свои пулл-реквесты, или хотя бы подробные баг-репорты, отнесусь со вниманием.

SequelYARDyardocдокументирование

ТехнологииПрограммированиеАбстракцииRubyДыбр

2026.01.19 • Иван Шихалев

Об инвалидацию кэша

КДПВ сгенерирована нейросетью по запросу «cache invalidation»

Как известно, в программировании есть только две реально сложные задачи: именование переменных и инвалидация кэша1. С первой мы сделать ничего не можем, со второй, конечно, тоже, но что-то делать приходится…

И вот в процессе большого рефакторинга inat-get я в очередной раз задумался. Суть тут вот в чем: данные, которые требуется получать из API iNa­tu­ra­list — очень большие (зависит от це­лей, конечно, но они могут быть очень большими), и логично их за­кэ­ши­ро­вать в локальной базе данных. Естественно, кэшированные данные нужно обновлять.

Есть прекрасный параметр updated_since в запросах к API, т.е. мы храним у себя информацию о сделанных запросах, и когда нам нужно получить новые данные по тем же условиям, указываем дату/время предыдущего запроса в этом параметре. Но полностью проблему это не решает: updated_since не отменяет (и не должен отменять) все остальные параметры запроса, т.е. те наблюдения, которые выпали из запроса, в выборку не попадут. И останутся в локальной БД в старом состоянии.

Ну, то есть, выбрали мы, например, данные по проекту, который фильтрует наблюдения с quality_grade=research, а потом пришел добрый человек и заметил, что в наблюдении культурное растение. Наблюдение выпадает из проекта, но мы об этом не можем узнать через обновление…

Какие есть пути решения? Вижу несколько вариантов, ни один из которых меня не устраивает полностью…

  1. Автор этой фразы, предположительно, Фил Карлтон (Phil Karlton), ведущий инженер компании Netscape. 


Читать далее »

iNaturalistinat-get

ТехнологииПрограммированиеRubyАбстракции

2025.12.23 • Иван Шихалев

Паттерн «Фасад» и гем для DSL

Картинка для привлечения внимания

При написании inat-channel я столкнулся вот с какой проблемой: с одной стороны, более-менее сложные действия должны быть декомпозированы, то есть разбиты на модули и отдельные методы в них; с другой — глубокая декомпозиция заставляет писать длинные обращения к методам типа INat­Chan­nel::​Te­le­gram::​send_​ob­ser­va­ti­on, что неудобно, да и не эстетично. По хо­ро­ше­му вообще нужно верхний уровень методов ин­клю­дить и писать send_observation в основной программе, но если писать все как включаемые методы модулей, то во-первых, они все из всех модулей попадут в финале в одно пространство имен, а во-вторых, туда же попадут и приватные методы.

Для подобных случаев и предназначен паттерн «Фасад» — мы создаем отдельный программный модуль — в данном случае это модуль же в терминах Ruby — который содержит только нужные извне методы, делегируя их в основной нормально де­ком­по­зи­ро­ван­ный код. И затем его спокойно ин­клю­дим в ко­де основного скрипта.

Собственно, именно так я и сделал, определив модуль IC и заполняя его методами в тех же файлах, где они определены. Туда же отправились некоторые методы, не нужные вовне, а используемые слабо логически связанными модулями — здесь речь скорее не о логике и отделении фасада, а о сокращении (текстовом) кросс­мо­дуль­ных вызовов. Впрочем, по мере разрастания структуры вопрос, что считать внутренним, а что внешним, становится не очень однозначным.

Подумав немного на эту тему, я решил вынести абстракцию в код и написал is-dsl — гем, упрощающий, а главное — структурирующий делегирование методов и констант фасаду. Подробнее — в README репозитория (есть русская версия), а также в yard-документации. Здесь коротко обозначу основные особенности:

  • Помимо основного модуля фасада формируется теневой модуль — для использования внутри библиотеки. Все, что попадает в основной, попадает и в теневой, обратное неверно. См. shadow-методы.

  • Можно делегировать как статически сингл­тон-методы классов и модулей, так и лениво методы произвольных сингл­тон-объектов, где сам объект создается или получается через вызов блока. См. lazy-ме­то­ды. Предполагается применение с ме­то­дом класса instance в пер­вую очередь.


Планов менять или добавлять что-то в основную функциональность нет, думаю когда-нибудь сделать плагин для YARD, чтобы делегирование методов правильно автоматически документировалось.

DSLgemis-dslпаттерны проектирования

ТехнологииПрограммированиеWebО себе

2025.12.16 • Иван Шихалев

Пара апдейтов

inat-channel v0.9.2

Что это такое — см. пре­ды­ду­щий пост.

Помимо исправления мелкого бага (имя lock-файла по умолчанию), изменил немного ло­ги­ку — при за­про­се свежих наблюдений отсечка происходит не по да­те загрузки наблюдения, а по да­те его последнего обновления. Это позволяет попадать в выборку наблюдениям, которые долго пролежали без ис­сле­до­ва­тель­с­ко­го статуса. В це­лом это должно улучшить ситуацию с по­с­туп­ле­ни­ем наблюдений в «не­се­зон», по край­ней мере, я на это надеюсь.

При этом удаление устаревших наблюдений из пу­ла по преж­не­му контролируется по да­те загрузки наблюдения.

jekyll-is-announcer v0.8.3

Опять же, о нем я уже писал. Впрочем, детали и концепция, чую, будут еще меняться и меняться…

А сейчас поменял кнопку перехода в ка­нал на виджет от те­ле­гра­ма с ком­мен­та­ри­я­ми. Что характерно, когда я делал кнопку, я ведь спрашивал у двух нейросетей, существует ли такой виджет… Но, видимо, как-то неправильно сформулировал и заузил область поиска1. А потом почти случайно сам наткнулся.

Что-то широкий и разнородный плагин получается… Пока не могу сообразить, как его окончательно заархитектурить — кноп­ки-то мож­но бы делать к разным сервисам легко, а вот встраиваемые виджеты — уже сложнее. Буду думать дальше. Раскидывать же его на несколько плагинов по отдельным сервисам не хочется, чтобы не плодить массу отдельных вспомогательных JSON-файликов.

Визуальную составляющую пока не дорабатывал — тут тоже надо сначала с общей картиной определиться, потом верстать конкретику.

Jekyll ISTelegramiNaturalistinat-channeljekyll-is-announcerкомментарии

ТехнологииПрограммированиеRubyWebПриродаОбщество

2025.12.12 • Иван Шихалев

iNaturalist + Telegram

Анонс

Написал скрипт для автопостинга выборок из iNaturalist в tg-каналы. Скрипт делает выборку по произвольно сконфигурированным параметрам (которые, разумеется, должны поддерживаться iNaturalist API), затем берет случайное наблюдение, постит его, а остальные складывает в пул, который будет задействован, если свежие кончатся. Это если коротко.

Более подробно, как это все работает, а главное — как настраивается, я описал в README проекта inat-get/inat-channel. В том числе и на русском. Здесь пара моментов:

  • Наблюдения не дублируются.

  • Можно включить режим, когда и таксоны не будут дублироваться, с ограниченным, правда, сроком. Но его можно выставить произвольно большим.

  • Форматирование делается ERB-шаблоном, т.е. максимально гибко.

  • Скрипт прекрасно работает на GitHub Actions, запускаясь по расписанию. Для контроля неповторямости необходимо настроить обратный пуш, чтобы данные, которые хранятся в JSON-файлах, сохранялись в репозитории между сеансами.

  • Используется iNaturalist API v2, которое находится в ранней бете и может поломаться. Однако, на первой версии пришлось бы вытягивать в десятки, если не в сотни раз бо́льшие объемы данных, что малоприемлемо.

Примеры

На реальных примерах работу скрипта можно посмотреть на двух моих каналах:

Подписывайтесь, ставьте лайки, комментируйте… Отелеграмливайте свои проекты. В общем, велкам.


Читать далее »

TelegramiNaturalistinat-channelавтоматизация

ТехнологииПрограммированиеWebО себе

2025.11.29 • Иван Шихалев

Анонсер — техническая сторона

В посте о подключении телеграм-канала я уже предполагал, что напишу подробнее о технической стороне этого подключения. Вообще-то, я планировал этим заняться попозже, а пока переключиться на «Прак­ти­чес­кое руководство по dark­table»… Но внезапно обнаружил, что толком переключиться не могу, пока не доведу эту задачу с анонсером до какой-то логической точки.

Что ж, причесал Actions, отладил это хозяйство до более-менее стабильного со­сто­я­ния — хоть и далекого от завершения, но уже приемлемого для описания. Принципиальных изменений в ближайших версиях, скорее всего, не будет, а о плановых доделках я здесь еще скажу.

Задача

Собственно, основная задача стояла в следующем:

  • Отправлять анонсы (пока только в телеграм-канал) новых постов;

  • Сохранять ссылки на анонсы и показывать их на страницах, чтобы можно было перейти к обсуждению.

Уже по ходу дела решил добавить в Actions отправку уведомлений себе о выполненных операциях.


Читать далее »

GitHubGitHub ActionsJekyllJekyll ISTelegramпланысайт