#!/usr/bin/env python3 # # Script for converting reMarkable tablet ".rm" files to SVG image. # Based on rM2svg from # # https://github.com/lschwetlick/maxio/tree/master/tools # # Format appears to be as follows: # # header: 'reMarkable .lines file, version=3 ' # 4 bytes integer: number of layers # for each layer: # 4 bytes integer: number of strokes # for each stroke: # 4 bytes integer: pen # 4 bytes integer: colour # 4 bytes: unknown # 4 bytes floating point: width # 4 bytes integer: number of segments # for each segment: # 6 floating point numbers: x, y, speed, direction, width, pressure # # the segment information came from an email from reMarkable support. # # Eric S Fraga import sys import struct import os.path import argparse __prog_name__ = "rm2svg" __version__ = "0.0.2" # Size default_x_width = 1404 default_y_width = 1872 # Mappings stroke_colour={ 0 : "black", 1 : "grey", 2 : "white", } '''stroke_width={ 0x3ff00000 : 2, 0x40000000 : 4, 0x40080000 : 8, }''' def main(): parser = argparse.ArgumentParser(prog=__prog_name__) parser.add_argument('--height', help='Desired height of image', type=float, default=default_y_width) parser.add_argument('--width', help='Desired width of image', type=float, default=default_x_width) parser.add_argument("-i", "--input", help=".rm input file", required=True, metavar="FILENAME", #type=argparse.FileType('r') ) parser.add_argument("-o", "--output", help="prefix for output files", required=True, metavar="NAME", #type=argparse.FileType('w') ) parser.add_argument("-c", "--coloured_annotations", help="Colour annotations for document markup.", action='store_true', ) parser.add_argument('--version', action='version', version='%(prog)s {version}'.format(version=__version__)) args = parser.parse_args() if not os.path.exists(args.input): parser.error('The file "{}" does not exist!'.format(args.input)) if args.coloured_annotations: global stroke_colour stroke_colour = { 0: "blue", 1: "red", 2: "white", 3: "yellow" } rm2svg(args.input, args.output, args.coloured_annotations, args.width, args.height) def abort(msg): print(msg, file=sys.stderr) sys.exit(1) def rm2svg(input_file, output_name, coloured_annotations=False, x_width=default_x_width, y_width=default_y_width): # Read the file in memory. Consider optimising by reading chunks. with open(input_file, 'rb') as f: data = f.read() offset = 0 # Is this a reMarkable .lines file? expected_header_v3=b'reMarkable .lines file, version=3 ' expected_header_v5=b'reMarkable .lines file, version=5 ' if len(data) < len(expected_header_v5) + 4: abort('File too short to be a valid file') return fmt = '<{}sI'.format(len(expected_header_v5)) header, nlayers = struct.unpack_from(fmt, data, offset); offset += struct.calcsize(fmt) # print('header={} nlayers={}'.format(header, nlayers)) is_v3 = (header == expected_header_v3) is_v5 = (header == expected_header_v5) if (not is_v3 and not is_v5) or nlayers < 1: abort('Not a valid reMarkable file: '.format(header, nlayers)) return output = open(output_name, 'w') output.write(''.format(y_width, x_width, x_width, y_width)) # BEGIN Notebook output.write(''' ''') # Iterate through pages (There is at least one) output.write('') # Iterate through layers on the page (There is at least one) for layer in range(nlayers): # print('New layer') fmt = '\n\n') # END stroke # Overlay the page with a clickable rect to flip pages output.write(''.format(x_width, y_width)) output.write('') # Closing page group output.write('') # END notebook output.close() if __name__ == "__main__": main()