Skip to content

Instantly share code, notes, and snippets.

@naturale0
Last active June 7, 2024 12:46
Show Gist options
  • Save naturale0/fb240574b48e596e22ecef049ff9ebc2 to your computer and use it in GitHub Desktop.
Save naturale0/fb240574b48e596e22ecef049ff9ebc2 to your computer and use it in GitHub Desktop.
Backup reMarkable documents as PDFs
#!/usr/bin/env python
# coding: utf-8
## =============== REQUIREMENTS =============== ##
## rmrl: https://github.com/naturale0/rmrl
## rsync (for rM):
## - https://github.com/JBBgameich/rsync-static/releases/download/continuous/rsync-arm
## - https://www.reddit.com/r/RemarkableTablet/comments/hetyfa/comment/fvu1fy3/?utm_source=share&utm_medium=web2x&context=3
# In[36]:
import os
import sys
import shutil
import json
import tqdm
import argparse
from rmrl import render
# In[ ]:
parser = argparse.ArgumentParser(description='Backup reMarkable docs as PDFs.')
parser.add_argument('-s', dest='source', nargs="?",
const="10.11.99.1", default="10.11.99.1",
help='IP address of a reMarkable device')
parser.add_argument('-r', dest='render_only',
default=False, action='store_true',
help='Only render the already backed up raw files')
parser.add_argument('-f', dest='full_render',
default=False, action='store_true',
help='force full render including already rendered PDFs')
parser.add_argument('-d', dest='remove_deleted',
default=False, action='store_true',
help='force full render including already rendered PDFs')
parser.add_argument('-x', dest='include_xochitl',
default=False, action='store_true',
help='include xochitl in backup (only if rM is hacked)')
args = parser.parse_args()
remarkable = args.source
render_only = args.render_only
full_render = args.full_render
include_xochitl = args.include_xochitl
remove_deleted = args.remove_deleted
if not os.path.exists("_raw"):
os.mkdir("_raw")
if not render_only:
#os.system(f"scp -o PasswordAuthentication=no\
# -i ~/.ssh/id_rsa_remarkable\
# -o PubkeyAcceptedKeyTypes=+ssh-rsa\
# -o HostKeyAlgorithms=+ssh-rsa\
# -r root@{remarkable}:~/.local/share/remarkable/xochitl/\* ./_raw")
backup_cmd = f'rsync -avh -e "ssh -i ~/.ssh/id_rsa_remarkable -o PasswordAuthentication=no"\
root@{remarkable}:~/.local/share/remarkable/xochitl/ ./_raw'
if remove_deleted:
backup_cmd += " --delete"
os.system(backup_cmd)
#os.system(f"scp -o PasswordAuthentication=no -i ~/.ssh/id_rsa_remarkable root@{remarkable}:/home/rmhacks/xochitl.* ./_xochitl")
if include_xochitl:
os.system(f'rsync -avh -e "ssh -i ~/.ssh/id_rsa_remarkable -o PasswordAuthentication=no"\
root@{remarkable}:/home/rmhacks/ ./_xochitl')
# In[48]:
metadata = ["_raw/" + f for f in os.listdir("./_raw") if f.endswith("metadata")]
def remove_reserved(string):
reserved = ["<", ">", ":", '"', "/", "\\", "|", "?", "*", "'"]
for r in reserved:
string = string.replace(r, "")
return string
# In[49]:
for md in tqdm.tqdm(metadata, desc="Converting to PDFs"):
with open(md) as r:
data_dict = json.loads(r.read())
local_mod_time = 0
remote_mod_time = int(data_dict['lastModified']) / 1000
if (not data_dict["deleted"]) and (data_dict["parent"] != "trash"):
# first, create all directories structure
# if not in root directory
if data_dict["parent"]:
parents = []
parent_code = data_dict["parent"]
while parent_code:
if parent_code == "trash":
break
parent_meta = json.load(open("_raw/" + parent_code + ".metadata"))
parent_code = parent_meta["parent"]
parent_name = remove_reserved(parent_meta["visibleName"])
parents.insert(0, parent_name)
if parent_code == "trash":
continue
for i in range(1, len(parents)+1):
path = "/".join(parents[:i])
path = f"./{path}"
if not os.path.exists(path):
os.mkdir(path)
# finally export PDF file
if data_dict["type"] != "CollectionType":
fname = remove_reserved(data_dict["visibleName"]) + ".pdf"
#print(parents, fname)
fname = os.path.join(path, fname)
if os.path.exists(fname):
local_mod_time = os.path.getmtime(fname)
# check if remote file is updated
#print(local_mod_time < remote_mod_time)
if full_render or (remote_mod_time > local_mod_time):
try:
output = render(md.split(".")[0])
with open(fname, "wb") as wb:
wb.write(output.read())
except FileNotFoundError as e:
print(e)
# if in root directory
else:
# if it is a folder
if data_dict["type"] == "CollectionType":
path = remove_reserved(data_dict["visibleName"])
path = f"./{path}"
if not os.path.exists(path):
os.mkdir(path)
# if it is a file (note, PDF, ...)
else:
path = remove_reserved(data_dict["visibleName"])
path = f"./{path}.pdf"
if os.path.exists(path):
local_mod_time = os.path.getmtime(path)
# check if remote file is updated
#print(local_mod_time < remote_mod_time)
if full_render or (remote_mod_time > local_mod_time):
try:
output = render(md.split(".")[0])
with open(path, "wb") as wb:
wb.write(output.read())
except FileNotFoundError as e:
print(e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment