Meta - Migration Workflows

The migration flow above from standardnotes to Obsidian didn’t work for me.
So I’ve wrote simple python script, that migrates notes and tags structure.

Sharing:

import json
from argparse import ArgumentParser
from pathlib import Path

import os


def _parse_args():
    description = """Migrates standardnotes unencrypted backupfile to the obsidian compatible, markdown tree folder structure.
If tags are enabled - convert tags to front matter yaml notation.
First tag found on the note - converted to note folder, preserving tag structure in the file system.
"""
    argument_parser = ArgumentParser(description=description)

    argument_parser.add_argument('--output', type=str, help='Output folder. Defaults to input folder', default=None)
    argument_parser.add_argument('--skip-tags', dest='skip_tags', action='store_true', help='Ignores tags.', default=False)
    argument_parser.add_argument('input', type=str, help='Standardnotes backup folder')

    args = argument_parser.parse_args()

    return args


def prepare_notes_content(items, root_folder):
    notes = {}
    for item in items:
        if item['content_type'] != 'Note':
            continue

        content = item['content']['text']
        for clean_rich_text_words in [
            "<p>",
            "</p>",
            "<br>",
            '<p dir="auto">',
        ]:
            content = content.replace(clean_rich_text_words, "")

        notes[item['uuid']] = {
            'title': item['content']['title'],
            'content': content,
            "tags": [],
            "folder": ""
        }
    return notes


def _prepare_tags_parents(tag_folder):
    tag_parents = {}

    for tag_file in tag_folder.glob("*.txt"):
        with open(tag_file, "r") as f:
            tag_data = json.load(f)

        tag_uuid = tag_file.name.replace("Tag-", "").replace(".txt", "")
        tag_parents[tag_uuid] = {
            "parent": None,
            "title": tag_data['title']
        }
        for reference in tag_data['references']:
            if reference['content_type'] != "Tag":
                continue

            parent_uuid = reference['uuid'].split("-")[0]
            tag_parents[tag_uuid]['parent'] = parent_uuid

    return tag_parents


def prepare_tags(backup_folder, notes):
    tag_folder = Path(backup_folder) / "Items" / "Tag"

    if not os.path.isdir(tag_folder):
        return

    tag_parents = _prepare_tags_parents(tag_folder)

    for tag_file in tag_folder.glob("*.txt"):
        with open(tag_file, "r") as f:
            tag_data = json.load(f)

        references = tag_data['references']
        for reference in references:
            if reference['content_type'] != 'Note':
                continue
            try:
                item = notes[reference['uuid']]
            except KeyError:
                print(f'Wrong tag reference: missing uuid {reference["uuid"]}')

            tag_uuid = tag_file.name.replace("Tag-", "").replace(".txt", "")
            current_tag = tag_parents[tag_uuid]

            folder = current_tag['title']

            while current_tag['parent']:
                current_tag = tag_parents[current_tag['parent']]
                folder = f"{current_tag['title']}/{folder}"

            if not item['folder']:
                item['folder'] = folder

            item['tags'].append(folder)


def write_output(output, notes):
    output_destination = Path(output) / "markdown_migration"
    create_folder(output_destination)

    for item in notes.values():
        create_folder(output_destination / item['folder'])
        file_name = (output_destination / item['folder'] / item['title'].replace(
            " ", "_"
            ).replace("/", "_")).with_suffix('.md')

        with open(file_name, "w") as f:
            if item['tags']:
                f.write("---\n")
                f.write("tags:\n")
                for tag in item['tags']:
                    f.write(f"  - {tag}\n")
                f.write("---\n\n")

            f.write(item['content'])

    print(f"Output written to f{output_destination}")


def create_folder(folder):
    if os.path.isdir(folder):
        return
    try:
        os.makedirs(folder)
    except Exception:
        pass


def main(backup_folder, output=None, skip_tags=False):
    if output is None:
        output = backup_folder

    with open(Path(backup_folder) / "Standard Notes Backup and Import File.txt", "r") as f:
        backup = json.load(f)

    items = backup['items']
    notes = prepare_notes_content(items, output)

    if not skip_tags:
        prepare_tags(backup_folder, notes)

    write_output(output, notes)


if __name__ == '__main__':
    args = _parse_args()
    main(args.input, output=args.output, skip_tags=args.skip_tags)

1 Like