Автоматизация публикации записей из Obsidian на WordPress-сайт

В данной статье рассмотрим автоматизацию публикаций заметок из Obsidian на сайт WordPress

Создание статей для сайта на WordPress может превратиться в настоящее испытание, особенно когда работа связана с большим количеством изображений (как, например, у меня было в статье про линейную интерполяцию в Unity

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

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

Я решил, что пора что-то менять и автоматизировал все эти действия. В этой статье я поделюсь с вами, как мне удалось упростить процесс создания статей, сделав его более эффективным и приятным. Мы рассмотрим, какие шаги были автоматизированы, какие инструменты использованы и как вы тоже можете внедрить подобное решение для своего сайта на WordPress.

Кстати, данная статья опубликована на сайт как раз по такому способу.

Поиск решения проблемы

Изначально, я хотел найти какое-либо приложение, которое будет это делать за меня. Но сколько бы я ни старался, не гуглил, не искал — не находилось ничего, кроме плагина WordPress Publish, который позволяет автоматизировано постить на сайт текущую статью Obsidian вместе с изображениями.

Собственно на этом можно было бы и остановиться, однако меня не устроили следующие вещи:

  • Изображения публикуются с оригинальными наименованиями, которые присваивает Obsidian автоматически. Например: ![[Pasted image 20240723085836.png]]
    Я же хочу чтобы картинки назывались у меня определенным наименованием, отражающим суть файла. Плюс к этому, файлы должны быть немного сжаты (т.к. в png они весят больше, чем в jpg, а место на хостинге не резиновое)
  • Порой я для удобства прописываю разрешение изображения, используя разметку: «Имя изображения|разрешение» — такие файлы плагин вообще не хотел воспринимать (судя по всему из-за символа «|» в наименовании)

Можно было бы форкнуть данный плагин, дописать его под свои нужды и пользоваться. Однако, он написан на JavaScript, который я почти не знаю, поэтому автоматизировать все действия я решил на Python (заодно и попрактиковать данный язык, который сейчас активно изучаю).

Приступаем

Хранилище Obsidian у меня реализовано достаточно просто. В настройках указано, что все заметки создаются в папке «Data», а все изображения в папке «Images»

Настройки хранилища Obsidian

Соответственно, на данную структуру я и ориентируюсь.

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

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

Поэтому, первым делом, создаем новое хранилище и настраиваем его в точности, как текущее (т.е. заметки в папке Data, картинки в папке Images):

Создать новое хранилище Obsidian

Путь к старому хранилищу у меня: E:\Kostegs\Obsidian_Storage\Storage
Новое хранилище я создал рядом: E:\Kostegs\Obsidian_Storage\Storage_ForSite

Скрипт на Python

Структура скрипта

Теперь приступим к скрипту на Python.

Определим последовательность действий, как должен работать наш скрипт:

  • Получить путь к исходному файлу
  • Определить из него путь к хранилищу
  • Сохранить исходный файл в новое хранилище
  • Пропарсить данный исходный файл и найти все файлы изображений
  • Каждое изображение сжать и сохранить в новое хранилище с другим наименованием
  • В копии исходной заметки, поменять пути к файлам, на новые файлы

После всех этих действий можно уже публиковать заметку.

Я решил для переименования файлов изображений воспользоваться прописыванием нового наименования в исходную ссылку на изображение. Т.е. все изображения, которые я хочу переименовать и которые имеют локальный адрес типа ![[Pasted image 20240806233446.png]] я меняю (при вставке) в исходной заметке на ![[Pasted image 20240806233446.png|Filename-for-site]], т.е. прописываю как должна называться картинка после обработки скриптом, через «Исходное название файла|Новое имя файла». Соответственно, скрипт должен это тоже учесть.

Приступим теперь к написанию данного скрипта.

Начало скрипта

Сперва импортируем нужные библиотеки:

import re  # для использования регулярок
import shutil  # для операций с файлами
import sys  # для получения аргументов запуска скрипта
from pathlib import Path  # для работы с путями
from PIL import Image  # для сжатия изображений

Теперь напишем точку входа:

if __name__ == '__main__':  
    # наш скрипт будет принимать 2 аргумента из командной строки
    # -путь к исходному файлу
    # -путь к хранилищу для сайта
    if len(sys.argv) > 2:  
        file_path = sys.argv[1]  
        storage_for_site = sys.argv[2]  
        process_file(file_path, storage_for_site)

Копируем исходный файл

Сделаем сперва копирование исходного файла в новое хранилище:

def process_file(file_path, storage_for_site):  
    # определяю путь к исходному хранилищу
    # через 2 parent, т.к. у меня такая структура, выше об этом писал
    storage_path = Path(file_path).parent.parent  
    # определяю имя исходного файла (только имя)
    file_name = Path(file_path).name  
    # пути к папкам Data и Images в новом хранилище
    processed_data_path = Path(storage_for_site).joinpath('Data')  
    compressed_images_path = Path(storage_for_site).joinpath('Images')  

    # проверяю на существование данных папок, создаю при необходимости
    check_folder_exist(processed_data_path)  
    check_folder_exist(compressed_images_path)  
  
    # копирую исходный файл в новое хранилище
    dest_file = copy_source_file(file_path, processed_data_path, file_name)

Метод check_folder_exist() проверяет — существует ли указанная директория и создаёт ее, если она отсутствует

def check_folder_exist(folder):  
    if not Path(folder).exists():  
        Path(folder).mkdir()

Метод copy_source_file() копирует исходный файл в новое хранилище:

def copy_source_file(original_file_path, processed_data_path, file_name):  
    # определяю, куда положить копию и копирую
    # это будет Путь к новому хранилищу\Data\имя файла.md
    dest_path = Path(processed_data_path).joinpath(file_name)  
    shutil.copy(original_file_path, dest_path)  
    return dest_path

Парсим изображения

Отлично, файл скопировали, теперь необходимо из него вытянуть информацию по картинкам. Добавим вызов parse_image_names() далее в метод process_file():

images, content = parse_image_names(dest_file)
def parse_image_names(source_file):  
    # читаем содержимое файла
	try:  
        with open(source_file, 'r', encoding='utf-8') as f:  
            file_contents = f.read()  
    except Exception as e:  
        print(f'Can\'t read the file, because: {e}')  
        return [], ''  
  
    # регуляркой ищем все значения в файле вида ![[Pasted image 20240711090319.png|Timeline_AutoPlay]]  
    # или ![[Pasted image 20240711090319.png|Timeline_AutoPlay|400]]
    # режем их на оригинальное имя: Pasted image 20240711090319.png
    # и новое имя: Timeline_AutoPlay    
    pattern = r'pasted image \d+.\w+\|.[^]|]+'  
    picture_names = re.findall(pattern, file_contents, re.IGNORECASE)  
    picture_names = [x.split(r'|') for x in picture_names]  
    temp_list = ['Name', 'NewName']  
  
    finish_list = []  
  
    # создаем список словарей вида: {Name : Original name, NewName : new name}  
    for pict_name in picture_names:  
        finish_list.append(dict(zip(temp_list, pict_name)))  

    # возвращаем данный список и содержимое файла
    return finish_list, file_contents

Теперь на руках у нас есть список, где лежат все, попадающие под наши критерии, картинки, со старым и новым наименованием

Сжатие изображений:

Для сжатия картинок напишем отдельный метод (будем его вызывать позже), который будет использовать библиотеку Pillow и сжимать наши картинки. Передадим в метод путь к оригинальной картинке (без расширения) и путь к картинке, в которую надо сохранить оригинальную картинку, а также качество:

def compress_img(original_img_path, compressed_image_path, quality):  
    # открываем исходную картинку
    with Image.open(original_img_path, 'r') as img:  
        # новое расширение будет JPEG
        new_filename = f"{compressed_image_path}.jpg"  

		# сохраняем оригинальную картинку с нужным качеством
        try:  
            img.save(new_filename, quality=quality, optimize=True)  
        except OSError:  
            # в случае ошибки, конвертим в RGB
            img = img.convert("RGB")  
            # сохраняем
            img.save(new_filename, quality=quality, optimize=True)

Дальнейшая обработка картинок

После того как мы получили список картинок из исходного файла, займемся их обработкой:

images, content = parse_image_names(dest_file)  
  
# путь, где лежат изображения изначального хранилища
origin_img_path = Path(storage_path).joinpath('Images')  
# шаблон паттерна, по которому мы будем искать наименования типа 
# ![[Pasted image 20240711090319.png|Timeline_AutoPlay]] в исходном 
# файле и полностью их заменять на путь к новой (сжатой) картинке
pattern = r'!\[+img_name.+\]'  
  
for image in images:  
    # путь к исходной картинке (без расширения)
    source_image = Path(origin_img_path).joinpath(image['Name'])  
    # путь к конечной картинке (без расширения)
    dest_image = Path(compressed_images_path).joinpath(image['NewName'])  
    # сжимаем исходную картинку и сохраняем по указанному конечному
    # пути с расширением .jpg
    compress_img(source_image, dest_image, 80)  
    # меняем в шаблоне паттерна поиска img_name на 
    # конкретное название исходной картинки
    current_pattern = pattern.replace('img_name', image['Name'])  
    # в содержимом файла делаем подмену наименований типа
    # ![[Pasted image 20240711090319.png|Timeline_AutoPlay]] на
    # ![[TimeLine_AutoPlay.jpg]]
    content = re.sub(current_pattern, f'![[{image['NewName']}.jpg]]', content)  

# записываем данное содержимое в конечный файл
with open(dest_file, 'w', encoding='utf-8') as f:  
    f.write(content)

Проверяем скрипт

Запустим теперь данный скрипт и проверим, как он работает. Для этого в терминале запускаем этот скрипт с аргументами: Исходный файл — Новое хранилище.

Check-script-terminal.jpg

Исходный файл выглядит вот так:

Source-file-Obsidian.jpg

Заходим в хранилище для сайта, видим, что в папке Data создалась копия исходного файла:

Data-new-storage.jpg

В папку Images скопированы исходные изображения, названы так, как я хотел и находятся в формате JPEG

Images-new-storage.jpg

В копии файла заменены все ссылки к изображениям на нужные:

Processed-file-Obsidian.jpg

Автоматизация запуска скрипта

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

Чтобы это сделать, нужно написать небольшой код на JavaScript и воспользоваться плагином для Obsidian под названием Templater.

Сперва установим сам плагин. Идем в Community Plugins и жмем кнопку Browse:

WordPress установка плагинов

Выбираем там Templater и устанавливаем его. Т.к. Python-скрипт мы будем вызывать из исходного хранилища, то и плагин устанавливаем тоже туда.

Установка Templater-плагина

После установки плагина на панели в Obsidian появляется возможность его запустить:

Obsidian-sidebar-templater.jpg

Пока еще ничего работать не будет. Сперва настроим данный плагин. Укажем папку, где будут лежать скрипты:

Настройка плагина Templater

И создадим данную папку в хранилище.

Templates-storage-Obsidian.jpg

В данной папке теперь создадим файл с расширением .js, назовем его, например, process_note_for_site.js и напишем в него следующий код:

<%*
// Импортируем функцию exec из модуля child_process,
// чтобы можно было запускать команды оболочки
const { exec } = require('child_process');
// Получаем путь к текущему файлу Obsidian
const filePath = tp.file.path();

// Запускаем Python-скрипт с передачей пути к текущему файлу в качестве первого 
// аргумента и пути к хранилищу для сайта - вторым аргументом
exec(`python E:/Dev/Python/Process_Images_Obsidian/start.py "${filePath}" "E:/Kostegs/Obsidian_Storage/Storage_ForSite/"`, (error, stdout, stderr) => {
    // при ошибке покажем данную ошибку на экран
    if (error) {
        alert(`Error: ${error}`);
        return;
    }
});
%>

Теперь, если нажать на плагин Templater на панели, то мы видим наш JS-скрипт, при нажатии на него, он запускает наш скрипт на Python

Выбор скрипта для запуска

Проверяем — всё работает. Исходный файл скопирован, картинки заменены, всё отлично.

Авто-публикация на сайт

Осталось только установить плагин для авто-публикации статьи на сайт в наше новое хранилище.

В хранилище, куда отправляются отредактированные данные (т.е. не в основном) переходим в Community Plugins и ищем там плагин WordPress Publish:

Wordpress-publish-install.jpg

Устанавливаем его и настраиваем:

Wordpress-publish настройка

Обязательно выделяем «Show Icon in Sidebar» чтобы иконка данного плагина появилась в сайдбаре обсидиана:

Wordpress-publish-show-icon-sidebar.jpg

Wordpress-publish-sidebar.jpg

Осталось только настроить профиль:

Wordpress-publish-new-profile.jpg

Прописываем название профиля, путь к сайту. Выбираем Api Type, как XML-RPC

WordPress-publish настройка профиля

Так как мы не хотим палить свой пароль от админки стороннему софту, то создадим данному плагину отдельного пользователя.

Для этого в админке сайта зайдем в Пользователи — Добавить нового пользователя

New-user-WP-admin.jpg

Add-user-wordpress-obsidian.jpg

Плюсом к этому, там же, в админке, зайдем в данного пользователя и добавим ему пароль приложений, чтобы использовать его для плагина (даже зная данный пароль в админку сайта попасть невозможно через web-интерфейс):

Wordpress пароли приложений

Пароль будет сгенерирован, нам останется его только скопировать и в настройке профиля плагина указать данного пользователя и пароль:

Wordpress-publish - user profile

Теперь, когда мы находимся внутри статьи достаточно нажать на иконку плагина в сайдбаре:

Wordpress-publish-process.jpg

Нам открывается окно, где мы настраиваем публикацию:

  • Post status — черновик
  • Comments — комментарии открыты
  • Post type — пост
  • Category — категория, куда отнести данный пост
Wordpress-publish-pre-settings.jpg

При нажатии кнопки Publish — пост публикуется на сайт, в формате черновика:

Wordpress-Draft.jpg

Как видим, все картинки опубликовались тоже:

Wordpress-Draft-Preview.jpg

Причем с нашими наименованиями, которые мы подготовили до этого скриптом

Wordpress-pic-unique-name.jpg

Заключение

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

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

Надеюсь, что мой опыт и описанные методы автоматизации помогут и вам облегчить вашу работу. Не бойтесь экспериментировать и внедрять новые инструменты – это всегда приносит свои плоды. Теперь я уверен, что создание статей для WordPress не должно быть обременительным занятием, а может стать увлекательным и творческим процессом. Удачи и вдохновения в ваших начинаниях!

P.S. Код скрипта на Python находится на гит-хабе: https://github.com/kostegs/Process_Images_Obsidian

Понравилась статья? Поделиться с друзьями:
Kostegs.Name
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: