Last active
June 7, 2024 12:46
-
-
Save naturale0/fb240574b48e596e22ecef049ff9ebc2 to your computer and use it in GitHub Desktop.
Backup reMarkable documents as PDFs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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