Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

[![Mentioned in Awesome reMarkable](https://awesome.re/mentioned-badge.svg)](https://github.com/reHackable/awesome-reMarkable)

This is a collection of scripts that utilize the reMarkable USB webinterface to export files.
This is a collection of scripts that utilize the reMarkable USB web interface to export files.


## DISCLAIMER

Using the export functionality, the script will prompt your device to **export every single file** onto your pc.
This will - as of now - lead to freezes and other problems, as the software is not specifically suited for this usecase.
This will - as of now - lead to freezes and other problems, as the software is not specifically suited for this use case.

I (and reMarkable AS too) **WON'T take any responsibility** for potential damage done to your device using this software.

Expand Down Expand Up @@ -48,7 +48,7 @@ $ ./stats.py

Right now, you can export/mirror all your files into a specified folder, export only changed files or view general information using on of the various scripts available.

You can also make your own scripts using the `api.py` and give you access to all metadata provided by the reMarkable USB webinterface. An example of such information can be found in the file `.exampleRootMetadata.json`.
You can also make your own scripts using the `api.py` and give you access to all metadata provided by the reMarkable USB web interface. An example of such information can be found in the file `.exampleRootMetadata.json`.

There is certainly a lot more possible than currently provided.
If you want a certain feature, you can create an issue (= feature request) or contribute it yourself.
Expand All @@ -59,4 +59,3 @@ If you want a certain feature, you can create an issue (= feature request) or co
When exporting a large amount of files, it can happen that your reMarkable **enters sleep mode** during that process or **locks up and restarts** (happens on some huge pdf files).

When this happens, you can just execute the same command again. It should continue where it failed with no data loss.

28 changes: 15 additions & 13 deletions api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'''
Utilities regarding the reMarkable usb webinterface.
Utilities regarding the reMarkable USB web interface.
'''

from collections.abc import Iterable
from datetime import datetime
import requests
import os


RM_WEB_UI_URL = 'http://10.11.99.1' # No trailing slash!
Expand All @@ -20,7 +21,7 @@ def __init__(self, metadata, parent=None):
self.metadata = metadata
self.parent = parent

# Hierachial data:
# Hierarchial data:
self.isFolder = (metadata['Type'] == 'CollectionType')
self.files = [] if self.isFolder else None

Expand Down Expand Up @@ -71,15 +72,16 @@ def parentFolderPath(self, basePath=''):
else:
basePath

def exportPdf(self, targetPath):
def exportDoc(self, targetPath):
'''
Downloads this file as pdf to a given path.
Downloads this file as pdf or rmdoc depending in the file extension to a given path.
I may take a while before the download is started,
due to the conversion happening on the reMarkable device.

Returns bool with True for successful and False for unsuccessful.
'''
response = requests.get(RM_WEB_UI_URL + '/download/' + self.id + '/placeholder', stream=True)
(_, filetype) = os.path.splitext(targetPath)
response = requests.get(RM_WEB_UI_URL + '/download/' + self.id + '/' + filetype[1:], stream=True)

if not response.ok:
return False
Expand All @@ -101,12 +103,12 @@ def __repr__(self):

def iterateAll(filesOrRmFile):
'''
Yields all files in this iterable (list, tuple, ...) or file including subfiles and folders.
Yields all files in this iterable (list, tuple, ...) or file including sub-files and folders.

In case any (nested) value that isn't a iterable or of class api.RmFile a ValueError will be raised!
'''
if isinstance(filesOrRmFile, RmFile):
# Yield file and optional sub files recursivly:
# Yield file and optional sub files recursively:
yield filesOrRmFile
if filesOrRmFile.isFolder:
for recursiveSubFile in iterateAll(filesOrRmFile.files):
Expand All @@ -123,20 +125,20 @@ def iterateAll(filesOrRmFile):

def fetchFileStructure(parentRmFile=None):
'''
Fetches the fileStructure from the reMarkable USB webinterface.
Fetches the fileStructure from the reMarkable USB web interface.

Specify a RmFile of type folder to fetch only all folders after the given one.
Ignore to get all possible file and folders.

Returns either list of files OR FILLS given parentRmFiles files

May throw RuntimeError or other releated ones from "requests"
if there are problems fetching the data from the usb webinterface.
May throw RuntimeError or other related ones from "requests"
if there are problems fetching the data from the USB web interface.
'''
if parentRmFile and not parentRmFile.isFolder:
raise ValueError('"api.fetchFileStructure(parentRmFile)": parentRmFile must be None or of type folder!')

# Use own list if not parentRmFile is given (aka beeing in root)
# Use own list if not parentRmFile is given (aka being in root)
if not parentRmFile:
rootFiles = []

Expand All @@ -146,7 +148,7 @@ def fetchFileStructure(parentRmFile=None):
response.encoding = 'UTF-8' # Ensure, all Non-ASCII characters get decoded properly

if not response.ok:
raise RuntimeError('Url %s responsed with status code %d' % (directoryMetadataUrl, response.status_code))
raise RuntimeError('Url %s responded with status code %d' % (directoryMetadataUrl, response.status_code))

directoryMetadata = response.json()

Expand All @@ -158,7 +160,7 @@ def fetchFileStructure(parentRmFile=None):
else:
rootFiles.append(rmFile)

# Fetch subdirectories recursivly:
# Fetch subdirectories recursively:
if rmFile.isFolder:
fetchFileStructure(rmFile)

Expand Down
23 changes: 20 additions & 3 deletions backup.bat
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
@echo off
setlocal

:: hide any "UNC paths are not supported" warnings
cls

:: If this batch file is on a UNC path, then its folder will be mapped to new a drive letter
:: (that will be removed later by the popd command).
:: pushd will change your working directory to the scripts location in the new mapped drive.
@pushd %~dp0

set VENV=venv
set DEST=_backup

if not exist %VENV% (
echo ===== CREATING VIRTUAL ENVIROMENT
py -3 -m venv %VENV%
call %VENV%\scripts\activate
echo ===== ACTIVATE VIRTUAL ENVIROMENT
call %VENV%\scripts\activate.bat
echo ===== UPGRADING PIP
python -m pip install --upgrade pip
echo ===== INSTALLING REQUIREMENTS
pip install --requirement requirements.txt
) else (
call %VENV%\scripts\activate
echo ===== ACTIVATE VIRTUAL ENVIROMENT
call %VENV%\scripts\activate.bat
)

if not exist %DEST% mkdir %DEST%
export.py --update %DEST%
echo ===== EXPORTING FROM REMARKABLE
python export.py --update %DEST%

:: popd to clean up the mapped drive, if created.
@popd

pause
38 changes: 24 additions & 14 deletions export.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
'''
Export - Exports all files of the remarkable onto your PC as pdfs.
Export - Exports all files of the remarkable onto your PC as PDFs and rmdoc.

Info: If a file is already exported, it will get skipped by default.
'''
Expand All @@ -9,20 +9,21 @@
import api

from argparse import ArgumentParser, RawDescriptionHelpFormatter
from datetime import datetime
from datetime import datetime, timezone
from os import makedirs, utime
from os.path import exists, getmtime
from sys import stderr
from time import time
from os.path import exists, getmtime, splitext
from sys import stderr, argv

# ------------------------------
# Config:
DEBUG = False
# ------------------------------

def local_time_offset():
now_timestamp = time()
return (datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)).total_seconds()
local_now = datetime.now()
utc_now = datetime.now(timezone.utc)
offset = (local_now - utc_now.replace(tzinfo=None)).total_seconds()
return offset

def exportTo(files, targetFolderPath, onlyNotebooks, onlyBookmarked, updateFiles, onlyPathPrefix=None):
# Preprocessing filterPath:
Expand All @@ -43,7 +44,7 @@ def exportTo(files, targetFolderPath, onlyNotebooks, onlyBookmarked, updateFiles
if onlyNotebooks:
exportableFiles = list(filter(lambda rmFile: rmFile.isNotebook, exportableFiles))

# Filter for only bookmared if requested:
# Filter for only bookmarked if requested:
if onlyBookmarked:
exportableFiles = list(filter(lambda rmFile: rmFile.isBookmarked, exportableFiles))

Expand Down Expand Up @@ -94,14 +95,23 @@ def exportTo(files, targetFolderPath, onlyNotebooks, onlyBookmarked, updateFiles

# Export file if necessary:
if not skipFile:
(root, _) = splitext(path)
path_rmdoc = root + ".rmdoc"
path_pdf = root + ".pdf"
try:
exportableFile.exportPdf(path)
exportableFile.exportDoc(path_rmdoc)
except Exception as ex:
print('ERROR: Failed to export "%s" to "%s"' % (exportableFile.name, path))
print('ERROR: Failed to export "%s" to "%s"' % (exportableFile.name, path_rmdoc))
raise ex
try:
local_mod_time = exportableFile.modifiedTimestamp + local_time_offset()
utime(path, (local_mod_time, local_mod_time)) # Use timestamp from the reMarkable device
exportableFile.exportDoc(path_pdf)
except Exception as ex:
print('ERROR: Failed to export "%s" to "%s"' % (exportableFile.name, path_pdf))
raise ex
try:
local_mod_time = exportableFile.modifiedTimestamp + local_time_offset()
utime(path_pdf, (local_mod_time, local_mod_time)) # Use timestamp from the reMarkable device
utime(path_rmdoc, (local_mod_time, local_mod_time)) # Use timestamp from the reMarkable device
except Exception as ex:
print('ERROR: Failed to change timestamp for exported file "%s"' % path)
raise ex
Expand All @@ -115,7 +125,7 @@ def printUsageAndExit():
if __name__ == '__main__':
# Disclaimer:
print('DISCLAIMER: Please be aware that this puts the STRAIN of creating exported pdf files on YOUR REMARKABLE DEVICE rather than this computer.\n' \
'This can lead to UNEXPECTED BEHAVIOUR when many and/or big files are being exported.\n' \
'This can lead to UNEXPECTED BEHAVIOR when many and/or big files are being exported.\n' \
'I WON\'T TAKE ANY RESPONSIBILITY for potential damage this may induce!\n', file=stderr)

# Argument parsing:
Expand Down Expand Up @@ -171,5 +181,5 @@ def printUsageAndExit():
else:
print('ERROR: %s' % ex, file=stderr)
print(file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr)
exit(1)
2 changes: 1 addition & 1 deletion paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
else:
print('ERROR: %s' % ex, file=stderr)
print(file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr)
exit(1)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
requests==2.31.0
requests==2.32.3
2 changes: 1 addition & 1 deletion stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,5 @@ def printStats(files):
else:
print('ERROR: %s' % ex, file=stderr)
print(file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr)
exit(1)
2 changes: 1 addition & 1 deletion tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ def printTree(rmFiles, prefix=(' '*4)):
else:
print('ERROR: %s' % ex, file=stderr)
print(file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB Webinterface in "Settings -> Storage".', file=stderr)
print('Please make sure your reMarkable is connected to this PC and you have enabled the USB web interface in "Settings -> Storage".', file=stderr)
exit(1)