Удаление плавающей заставки из мультсериала

Волею судеб с некоторых пор я последовательно скачиваю серии мультиков про Наруто. После скачивания для экономии места на сетевом хранилище я пережимаю эти серии в H.265, оставляя только само содержимое, удаляя финальные титры и песню-заставку (opening).

В первой порции этого сериала из 220 серий песня звучала в самом начале, поэтому вырезать её было достаточно просто. Теперь же, скачав пачку новых эпизодов, я обнаружил, что заставка не имеет постоянного времени начала и воспользоваться старым способом уже нельзя — нужно как-то обнаруживать её местоположение в видеофайле.

Ffmpeg справляется и с этой задачей. Механизм заключается в том, что видеофайл прогоняется через фильтр, сравнивающий кадры с образцом на скриншоте, выдавая статистику, из которой можно извлечь нужную метку времени t:.

frame=12549 fps=353 q=-0.0 size=N/A time=00:08:24.36 bitrate=N/A speed=14.2x    
frame=12719 fps=353 q=-0.0 size=N/A time=00:08:31.16 bitrate=N/A speed=14.2x    
[Parsed_blackframe_1 @ 0000023f584beec0] frame:12737 pblack:100 pts:12798 t:511.920000 type:I last_keyframe:12737
[Parsed_blackframe_1 @ 0000023f584beec0] frame:12738 pblack:100 pts:12799 t:511.960000 type:P last_keyframe:12737
...
[Parsed_blackframe_1 @ 0000023f584beec0] frame:12846 pblack:88 pts:12907 t:516.280000 type:P last_keyframe:12845
[Parsed_blackframe_1 @ 0000023f584beec0] frame:12847 pblack:89 pts:12908 t:516.320000 type:P last_keyframe:12845
frame=12891 fps=353 q=-0.0 size=N/A time=00:08:38.08 bitrate=N/A speed=14.2x    
frame=13088 fps=353 q=-0.0 size=N/A time=00:08:45.92 bitrate=N/A speed=14.2x

Это нужно сделать 2 раза: для кадра начала заставки и кадра конца, а затем уже на основании полученных меток времени приступать к обработке и перекодированию. Конечно, операция гораздо более трудоёмкая, чем раньше, но положительные эмоции от найденного работающего решения того стоят, да и надо же чем-то себя занимать на этой бессмысленной новогодней неделе.

Ситуация дополнительно усложнялась следующими факторами:

  1. Кадры заставки от серии к серии могут немного меняться и фильтр перестаёт воспринимать кадр как похожий на образец на скриншоте. Ниже примеры, где один образец кадра наложен на другой и видно, что логотип был сдвинут или увеличен.
naruto_opening5_end.jpg
naruto_opening1_end.jpg
  1. Заставка сама по себе меняется примерно каждые 25 серий, так что тут придётся пробежаться по эпизодам сериала, записать их диапазоны с теми или иными заставками и наделать пары скриншотов для каждого диапазона.
  2. Серии (именно их содержательная часть) длятся неодинаковое время. У одних финальные титры начинаются в 20:06, а у других — на минуту позже.
  3. Наличие квадратных скобок в именах файлов и путях. Почему-то многие владельцы раздач на торрент-трекерах питают слабость к подобным символам, поэтому приходится добавлять в скрипт дополнительные ухищрения.

Чтобы фильтр не был таким дотошным и допускал мелкие изменения в кадре по сравнению с образцом на картинке, надо понизить ему порог чувствительности. Но вскоре выяснилось, что если в образце преобладает чёрный цвет, то фильтр с увеличенной погрешностью начинал ошибочно определять обычные чёрные кадры вместо искомого. Вот пример подобной картинки:

naruto_opening3_start.jpg

В целом получилось так: для полноцветных кадров я выставлял blackframe=80 при стандартном значении 98, а для кадров на чёрном фоне оставлял по умолчанию blackframe.

Чтобы определить длительность серий, я сделал быстрый просмотр, выбирая по 10 серий, чтобы индекс массива для удобства совпадал с последней цифрой серии, и нажимая F8 в Powershell ISE на соответствующей строке.

$episodes = 80..89 |% {$_.ToString("000")}
$e = dir *.avi |? name -match "$($episodes -join '|')"

ffplay -i $e[0].Name -ss 20:06 -an
ffplay -i $e[1].Name -ss 20:06 -an
ffplay -i $e[2].Name -ss 20:06 -an
ffplay -i $e[3].Name -ss 20:06 -an
ffplay -i $e[4].Name -ss 20:06 -an
ffplay -i $e[5].Name -ss 20:06 -an
ffplay -i $e[6].Name -ss 20:06 -an
ffplay -i $e[7].Name -ss 20:06 -an
ffplay -i $e[8].Name -ss 20:06 -an
ffplay -i $e[9].Name -ss 20:06 -an

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

Чтобы в случае чего можно было вернуться к перекодированию без возни с повторным отлавливанием заставки, временные метки записываются в файл CSV:

Name;Start;End
Naruto_Shippuuden_TV2_[001_of_XXX]_[Rus_Jap]_[NIKITOS].avi;511.920000;601.760000
Naruto_Shippuuden_TV2_[002_of_XXX]_[Rus_Jap]_[NIKITOS].avi;119.000000;208.800000
Naruto_Shippuuden_TV2_[003_of_XXX]_[Rus_Jap]_[NIKITOS].avi;130.960000;220.800000
...
Naruto_Shippuuden_TV2_[131_of_XXX]_[Rus_Jap]_[NIKITOS].avi;448.489708;536.827964782715
Naruto_Shippuuden_TV2_[132_of_XXX]_[Rus_Jap]_[NIKITOS].avi;383.299583;471.637840270996
Naruto_Shippuuden_TV2_[133_of_XXX]_[Rus_Jap]_[NIKITOS].avi;1.501500;89.8814591169357

В файле CSV первые конечные метки отличаются от тех, что идут позже — у них меньше десятичных знаков. Дело в том, что сначала я указывал ffmpeg искать в первых 10 минутах видеофайла и начальный, и конечный кадр — в функции была одна команда. Потом я догадался, что конечный кадр ведь идёт после начального, поэтому нужно начинать искать конечный кадр с того места, где был начальный, а так как все заставки идут не более полутора минут, то ими надо поиск и ограничить. Длительность заставок выяснилась путём анализа того же CSV-файла.

Import-Csv 'C:\temp\log.txt' -Delimiter ';' |select *,@{n='Duration';e={$_.end - $_.start}}

Name                                                       Start      End        Duration
----                                                       -----      ---        -------
Naruto_Shippuuden_TV2_[001_of_XXX]_[Rus_Jap]_[NIKITOS].avi 511.920000 601.760000 89,84
Naruto_Shippuuden_TV2_[002_of_XXX]_[Rus_Jap]_[NIKITOS].avi 119.000000 208.800000  89,8
Naruto_Shippuuden_TV2_[003_of_XXX]_[Rus_Jap]_[NIKITOS].avi 130.960000 220.800000 89,84
Naruto_Shippuuden_TV2_[004_of_XXX]_[Rus_Jap]_[NIKITOS].avi 170.000000 259.840000 89,84
Naruto_Shippuuden_TV2_[005_of_XXX]_[Rus_Jap]_[NIKITOS].avi 81.040000  170.760000 89,72
Naruto_Shippuuden_TV2_[006_of_XXX]_[Rus_Jap]_[NIKITOS].avi 191.960000 281.760000  89,8
Naruto_Shippuuden_TV2_[007_of_XXX]_[Rus_Jap]_[NIKITOS].avi 0.000000   89.840000  89,84

Тем самым я убил двух зайцев: уменьшил время обработки и убрал дополнительную функцию, вычисляющую время между найденными кадрами и пишущую в лог ошибку и пропускающую перекодирование, если это время превышало 90 секунд. Но так как значение местонахождения конечного кадра было уже относительным к начальному (например, для начального теперь оно могло быть 150, а для конечного — 88), поэтому для команды кодирования нужно эти значения складывать, и это вычисленное значение даёт большее число знаков после запятой.

Для лучшего ориентирования я добавил вывод информации на экран. Здесь видно, что поиск начала занимает 43 секунды, а поиск конца уже 5.

2024.01.02 17:49:45 Поиск начала заставки в Naruto_Shippuuden_TV2_[147_of_XXX]_[Rus_Jap]_[NIKITOS].avi...
2024.01.02 17:50:28 Начало заставки найдено на 384.509125 сек.
2024.01.02 17:50:28 Поиск конца заставки в Naruto_Shippuuden_TV2_[147_of_XXX]_[Rus_Jap]_[NIKITOS].avi...
2024.01.02 17:50:33 Конец заставки найден на 472.847373962402 сек.

Есть и журнал ошибок. Какие-то файлы всё же приходится обрезать самому в Avidemux, пока компьютер перелопачивает остальные, к тому же, среди набора серий попадается неформат — склеенные или разделённые на несколько частей эпизоды. Но это всё равно лучше, чем обрабатывать руками все 235 видеофайлов.

2024.01.02 14:28:50 Конец заставки не найден в Naruto_Shippuuden_TV2_[125_of_XXX]_[Rus_Jap]_[NIKITOS].avi
2024.01.02 14:29:20 Начало заставки не найдено в Naruto_Shippuuden_TV2_[126_of_XXX]_[Rus_Jap]_[NIKITOS].avi
2024.01.03 07:23:11 Начало заставки не найдено в Naruto_Shippuuden_TV2_[183_of_XXX]_[Rus_Jap]_[NIKITOS].avi

Собственно, скрипт.

## Заставка 1 рассвет
#$episodes = 1..30
## Заставка 2 глаз
#$episodes = 31..53
## Заставка 3 птица
#$episodes = 54..77
#$episodesLong = ,55+71
## Заставка 4 облака
$episodes = 78..102                         # Диапазон обрабатываемых эпизодов
$episodesLong = ,79+80+82+85+91+92+100+102  # Эпизоды с увеличенной длиной
## Заставка 5 водопад
#$episodes = 103..128
#$episodesLong = ,104+106+107+111..115+120+123..125
## Заставка 6 мороженое
#$episodes = 129..153
#$episodesLong = ,131+133+135+138..141+143+145+148+149..155
## Заставка 7 зонтик
#$episodes = 154..179
#$episodesLong = 149..155+157..159+162+167+170..172+174..179
## Заставка 8 лицо
#$episodes = 180..196+199..205
#$episodesLong = 180..184+186..208
## Заставка 9 летающие острова
#$episodes = 206..230
#$episodesLong = ,208+210+211+213..235
## Заставка 10 скала
#$episodes = 231..235
#$episodesLong = 213..235

$inFolder = 'C:\Users\User\downloads\Naruto Shippuuden TV2 `[NIKITOS`] HWP' # Каталог с исходниками
$outFolder = 'C:\temp'             # Каталог с обработанными файлами
$logFolder = 'C:\temp'             # Каталог с логами
$csv = "$logFolder\log.txt"        # Журнал/CSV 
$logError = "$logFolder\error.txt" # Журнал ошибок
$ext = '*.avi'                     # Маска и расширение исходных файлов 
$episodes = $episodes |% {$_.ToString("000")}              # Переделать номера в трёхзначные для поиска файлов
$openingStartPic = Get-Item "$inFolder\opening4_start.jpg" # Картинка начального кадра
$openingEndPic = Get-Item "$inFolder\opening4_end.jpg"     # Картинка конечного кадра
$openingMax = 90          # Длина заставки (сек.), глубина поиска конечного кадра
$vidDuration = 1206       # Стандартная длина эпизода (сек.)
$vidDurationLong = 1266   # Увеличенная длина эпизода (сек.)

# Функция поиска кадров
function Find-Frame ($file,$pic,$startPoint) {
    if ($startPoint) {
        # Конечный кадр
        (& ffmpeg.exe -hwaccel_output_format qsv -ss $startPoint -t $openingMax -an -i $file -loop 1 -i $pic -filter_complex "blend=difference:shortest=1,blackframe=80" -f null - 2>&1) -match 'blackframe.*type:I'
    }
    else {
        # Начальный кадр
        (& ffmpeg.exe -hwaccel_output_format qsv -t 10:00 -an -i $file -loop 1 -i $pic -filter_complex "blend=difference:shortest=1,blackframe=80" -f null - 2>&1) -match 'blackframe.*type:I'
    }
}

# Функция логирования/вывода
function log ($text,$file,$color) {
    $t = (get-date).tostring("yyyy.MM.dd HH:mm:ss")
    if ($file) {
        Tee-Object -InputObject "$t $text" -FilePath $file -Append
    }
    else {
        if (-not $color) {$color = "white"}
        Write-Host -fore $color "$t $text"
    }
}

cd $inFolder

dir $ext |? name -match "$($episodes -join '|')" |% {

# Поиск начала заставки
log -text "Поиск начала заставки в $($_.name)..." -color yellow 
$openingStart = Find-Frame -file $_.name -pic $openingStartPic.fullname
if ($openingStart) {
    $openingStartFrame = ($openingStart[0] -split ' ')[-3] -replace "t:"
    log -text "Начало заставки найдено на $openingStartFrame сек." -color green
}
else {
    log -text "Начало заставки не найдено в $($_.name)" -file $logError
    continue
}

# Поиск конца заставки
log -text "Поиск конца заставки в $($_.name)..." -color yellow
$openingEnd = Find-Frame -file $_.name -pic $openingEndPic.fullname -startPoint $openingStartFrame
if ($openingEnd) {
    $openingEndFrame = [single]$openingStartFrame + [single](($openingEnd[-1] -split ' ')[-3] -replace "t:")
    log -text "Конец заставки найден на $openingEndFrame сек." -color green
}
else {
    log -text "Конец заставки не найден в $($_.name)" -file $logError
    continue
}

# CSV
"$($_.name);$openingStartFrame;$openingEndFrame" |Out-File $csv -Encoding default -Append

# Выбор длины серии в целом
if ($_.basename -match "$($episodesLong -join '|')") {$tail = $vidDurationLong}
else {$tail = $vidDuration}

# Обработка/кодирование
& ffmpeg.exe -y -hide_banner -hwaccel_output_format qsv -i $_.name `
-filter_complex `
"[0:0]trim=start=0:end=$($openingStartFrame),setpts=PTS-STARTPTS[av];
 [0:1]atrim=start=0:end=$($openingStartFrame),asetpts=PTS-STARTPTS[aa];
 [0:0]trim=start=$($openingEndFrame):end=$($tail),setpts=PTS-STARTPTS[bv];
 [0:1]atrim=start=$($openingEndFrame):end=$($tail),asetpts=PTS-STARTPTS[ba];
 [av][bv]concat[outv];[aa][ba]concat=v=0:a=1[outa]" `
-map [outv] -map [outa] -c:v hevc_qsv -global_quality:v 28 `
-c:a libopus -ac 1 -b:a 64k `
"$outFolder\$($_.basename).mp4"

Clear-Variable openingStart,openingEnd,openingStartFrame,openingEndFrame
}

После перекодирования всех серий они стали занимать 9,5 ГБ вместо исходных 67.

С новым годом, желаю вам мира и спокойствия.

Хорошо не наступает

Самое большое количество суицидов — 45-47 лет, это тот самый «кризис среднего возраста». Когда мы растём, то нам говорят: если будешь делать так и так, то будет хорошо. И вот человек хорошо учится, старается всё делать правильно, находит плюс-минус интересную работу, поднимается по иерархической лестнице, у него семья, дети.

И вот уже дожил до 40, вроде бы всё делает правильно, но только «хорошо» не наступает и он понимает, что вроде бы уже и не наступит. Он попадает в ситуацию переосмысления и вспоминает, чего на самом деле хотел.

Кто дожил до 50, тому уже более-менее стало полегче, пока не дойдёт где-то до 70. Никто не говорит о суицидах пожилых людей, а их очень много. Люди оказываются в ситуации одиночества, экономических кризисов, болезней. Сложнее всего с изоляцией, потому что людям часто просто не с кем общаться; из-за проблем, например, с суставами лишний раз на улицу не выйти. Никто об этом особенно не задумывается.

Конец велосезона - 2023

Сегодня утром я выглянул в окно и понял, что велосезон закончен: всё было покрыто снегом. Помню, в 2017 году я пытался не заканчивать сезона и ездить круглый год, но я быстро отказался от этой идеи, даже несмотря на то, что я купил себе отличные шипованные покрышки, на которых можно было спокойно ездить по голому льду — по рыхлому снегу передвигаться очень тяжело, особенно на велосипеде с маленькими 20-дюймовыми колёсами, а в наших условиях зимы с идеально расчищенными дорогами не бывает. Так что теперь я заканчиваю сезон, как только выпадает снег.

Этот год выдался рекордным по пробегу — я проехал более 4500 км. Была очень хорошая погода, особенно в сентябре, который был фактически четвёртым месяцем лета. Наверное, можно было бы достичь и отметки в 5000, но октябрь был дождливым и перед последней поездкой 25 октября я пропустил целых две недели.

Приведу статистику пробега по годам с момента покупки велосипеда:

Год Пройдено км
2016 2809
2017 3720
2018 3973
2019 3456
2020 4237
2021 3076
2022 3943
2023 4542

Сейчас на счётчике 29756 км, до 30000 не доехал всего-то 250 км, но теперь уж в следующем сезоне.

Настройка пульта для встроенного ИК-датчика Orange Pi

Дано: Orange Pi PC Plus с установленным LibreELEC 11, которым хотелось бы управлять с помощью пульта. Инфракрасный приёмник у Orange Pi имеется, поэтому нужно просто научить систему понимать с этого пульта сигналы.

Прежде всего нужно купить какой-нибудь пульт или взять уже имеющийся, у которого есть кнопки стрелок, старт/пауза, стоп, ОК и тому подобные, чтобы можно было нормально управлять медиацентром. Я купил пульт для телевизора Supra RS41-MOUSE за 200 рублей.

Заходим по SSH в систему. В инструкции по настройке пультов сначала рекомендуют подбирать совместимые конфигурации из списка по пути /usr/lib/udev/rc_keymaps, но их там полторы сотни и нет ничего похожего по названию, так что я не стал возиться, а сразу перешёл к созданию собственной конфигурации, описанному в разделе Advanced.

Выводим список поддерживаемых протоколов управления (supported kernel protocols):

OrangePiPCPlus:~ # ir-keytable
Found /sys/class/rc/rc0/ with:
        Name: sunxi-ir
        Driver: sunxi-ir
        Default keymap: rc-empty
        Input device: /dev/input/event0
        LIRC device: /dev/lirc0
        Attached BPF protocols:
        Supported kernel protocols: lirc rc-5 rc-5-sz jvc sony nec sanyo mce_kbd rc-6 sharp xmp imon rc-mm
        Enabled kernel protocols: lirc
        bus: 25, vendor/product: 0001:0001, version: 0x0100
        Repeat delay = 500 ms, repeat period = 125 ms

Нужно подобрать протокол, с которым совместим пульт. В моём случае подошёл nec, после включения которого в консоли начали отображаться коды кнопок при их нажатии на пульте:

OrangePiPCPlus:~ # ir-keytable -p nec -t
Protocols changed to nec
Testing events. Please, press CTRL-C to abort.
1018.929441: lirc protocol(necx): scancode = 0x710205
1018.984459: lirc protocol(necx): scancode = 0x710205 repeat
1021.562363: lirc protocol(necx): scancode = 0x710205
1024.293455: lirc protocol(necx): scancode = 0x710204
1024.348472: lirc protocol(necx): scancode = 0x710204 repeat
1028.355072: lirc protocol(necx): scancode = 0x710268
1028.410087: lirc protocol(necx): scancode = 0x710268 repeat
1028.517783: lirc protocol(necx): scancode = 0x710268 repeat
1030.043425: lirc protocol(necx): scancode = 0x710262
1030.098426: lirc protocol(necx): scancode = 0x710262 repeat

Отлично, теперь нужно нарисовать карту кнопок (keymap), где прописывается протокол пульта и соответствие кодов кнопок с их функциями. Список функций можно посмотреть с помощью команды irrecord -l | grep ^KEY или в секции <remote device="devinput"> файла /usr/share/kodi/system/Lircmap.xml.

В итоге у меня получилась конфигурация, приведённая ниже. Закомментированы кнопки, к которым я не нашёл близкой функции, а кнопка POWER была чуть позже изменена на ENTER, потому что одноплатник выключался, но включить его потом с пульта было нельзя.

# table supra_rs41, type: nec
# 0x710202 KEY_POWER
0x710202 KEY_ENTER
# 0x71020f source
0x710220 KEY_RED
0x710234 KEY_GREEN
0x71022b KEY_YELLOW
0x71022c KEY_BLUE
0x710227 KEY_MUTE
0x710225 KEY_ZOOM
# 0x710200 freeze
0x710228 KEY_TEXT
0x710203 KEY_FAVORITES
0x710232 KEY_SUBTITLE
0x710240 KEY_AUDIO
0x710255 KEY_RECORD
0x710226 KEY_REWIND
0x71021e KEY_FORWARD
0x710239 KEY_PREVIOUS
0x710213 KEY_NEXT
0x71021a KEY_PLAY
0x710201 KEY_STOP
0x710260 KEY_UP
0x710261 KEY_DOWN
0x710265 KEY_LEFT
0x710262 KEY_RIGHT
0x710268 KEY_ENTER
0x71022d KEY_MENU
0x71021f KEY_ESC
0x710207 KEY_VOLUMEUP
0x71020b KEY_VOLUMEDOWN
0x710222 KEY_HOME
# 0x710221 mouse
0x710212 KEY_CHANNELUP
0x710210 KEY_CHANNELDOWN
0x710204 KEY_1
0x710205 KEY_2
0x710206 KEY_3
0x710208 KEY_4
0x710209 KEY_5
0x71020a KEY_6
0x71020c KEY_7
0x71020d KEY_8
0x71020e KEY_9
0x710211 KEY_0
0x710223 KEY_DISPLAYTOGGLE
# 0x710250 return

Теперь надо создать файл с нашей картой кнопок и запустить его:

OrangePiPCPlus:~ # nano /storage/.config/rc_keymaps/supra_rs41
OrangePiPCPlus:~ # ir-keytable -c -w /storage/.config/rc_keymaps/supra_rs41
Read supra_rs41 table
Old keytable cleared
Wrote 39 keycode(s) to driver
Protocols changed to nec

Пульт сразу же начинает работать. Осталось добавить эту конфигурацию в автозагрузку:

OrangePiPCPlus:~ # echo "* * supra_rs41" > /storage/.config/rc_maps.cfg

Смена движка блога

Некоторое время назад, зайдя в админку Вордпресса, на котором работал этот блог, я обнаружил в разделе рекомендаций, что производительность оценивается как довольно низкая, сайт отвечает на запросы с задержкой и неплохо бы добавить ещё механизм кэширования объектов на базе Redis.

Честно говоря, Wordpress с течением времени нравился мне всё меньше. Он превратился в какую-то оболочку для плагинов, из которых многие имеют платную версию; в бесплатной же функционал урезан и она постоянно напоминает плашками в админке о том, что хорошо бы купить платную. Сам Вордпресс постоянно обрастает бирюльками вроде вышеупомянутых «рекомендаций о здоровье сайта».

Редактор «Гутенберг», появившийся в 5-й версии Вордпресса, был очень удобен, но каждый раз он показывал окно «Добро пожаловать в редактор блоков!», которое должно было появиться только единожды при первом запуске. В интернете есть статьи по решению этой проблемы, но попробовав один метод, который не сработал, я оставил эти попытки. Не хотелось что-либо поломать, а пишу в блог я не так часто.

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

Теперь, значит, рекомендуется поставить Redis, чтобы ускорить работу. Ладно. Помимо контейнера с Redis опять-таки ставится очередной плагин, причём настраивать его приходится из консоли, редактируя wp-config.php.

После настройки я запустил Wordpress и получил неработающий сайт, который не поднялся даже после удаления изменений, внесённых в конфигурацию. Восстановить работоспособность удалось только удалив все плагины, но из-за этого поехала вёрстка, в частности, блоки кода начали выглядеть искажённо даже после установки плагина подсветки заново.

После этого мне надоело и я решил перейти на другую CMS, желательно без базы данных, как DokuWiki. От движка нужна поддержка:

  • Галерей картинок;
  • Аудио-плейлистов (в Вордпрессе они были, но их убрали);
  • Подсветки синтаксиса;
  • Видео (Youtube и локальных файлов);
  • По возможности, RSS и типографики.

Перебрав около десятка статических CMS (Grav, HTMLy, Pico и т. п.), я остановился на Datenstrom Yellow, для работы которого требуется только веб-сервер и PHP с минимумом дополнительных модулей (php-curl, php-gd, php-mbstring, php-zip). Для сравнения, в PHP Вордпресса было больше 20 модулей. Записи в блоге делаются в формате Markdown, можно использовать и HTML-код. Переезжал я с помощью wordpress-export-to-markdown, который сконвертировал XML-экспорт из Вордпресса в файлы Markdown и скачал картинки; последующая доработка делалась с помощью Powershell и вручную.

Мелкие недостатки есть:

  1. В Вордпрессе можно было указать у галереи, сколько будет картинок в ряду, здесь указывается точный размер превью.
  2. Не очень удобный способ заполнения альтернативного текста для картинок в галереях.
  3. Видео (локальное или ссылка) не адаптируется к размеру колонки.
  4. Не самый удобный способ написания постов, но это для регулярно имеющего дело с кодом и командной строкой человека не проблема.

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