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)