hersle / rmirro

A script that synchronizes PDFs of documents between a Remarkable and a computer folder that mirrors its file structure without cloud access
MIT License
71 stars 7 forks source link

Chain rendering #10

Open Ph-St opened 1 month ago

Ph-St commented 1 month ago

Following up on issue #9, I tried to implement a chain_renderer. Here it is:

#!/usr/bin/python3

# Since all available 3rd party renderer tools for remarkable have issues, it is useful to run them in a chain.
#
# In the configuration, renderer commands can be defined that will be used in the order they are listed. Each command must be able to be called as "command infile outfile"

import sys
import os.path
import subprocess

renderer_list = ["./render_rmrl.py", "./render_maxio.py", "./render_fail.py"] # Add all renderers in the order in which they should be employed. render_fail.py will output a PDF with an error message; it can be added to the list to make sure further documents are being processed.

if __name__ == "__main__":
    args = sys.argv[1:]
    assert len(args) == 2, "usage: render_chain.py infile outfile"

    infile = args[0]
    outfile = args[1]

    for command in renderer_list:
        status, _ = subprocess.getstatusoutput(f"{command} \"{infile}\" \"{outfile}\"")

        if status == 0:
            break

    exit(status)

I also wrote a dummy renderer (render_fail.py) that is called last in the chain to make sure further documents are being processed. Here it is:

#!/usr/bin/python3

# This is a dummy renderer. It creates a pdf file with an error message. It can be used to make sure that other files get processed after an error.

import sys
import os.path
import subprocess
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4

def generate_pdf(outfile):
    c = canvas.Canvas(outfile, pagesize=A4)
    c.drawString(50, 750, "This file could not be rendered.")
    c.showPage()
    c.save()

if __name__ == "__main__":
    args = sys.argv[1:]
    assert len(args) == 2, "usage: render_fail.py infile outfile"

    infile = args[0]
    outfile = args[1]

    status = generate_pdf(outfile)
    exit(status)

For completeness, here is render_rmrl.py again:

#!/usr/bin/python3

# This method uses the third-party renderer rmrl to render reMarkable documents locally on the computer.
# It downloads the raw reMarkable files to the computer and renders them locally.
#
# It requires the python module rmrl (https://github.com/rschroll/rmrl/) and SSH access to the reMarkable (to download the raw files), but no USB connection.
#
# It fails in some cases.
#
# To use a different third-party renderer, wrap it in a script with call signature like this one!

import sys
import os.path
import subprocess
from rmrl import render

def render_rmrl(input, output):
    stream = render(input)
    with open(output, "wb") as out_file:
        out_file.write(stream.read())

if __name__ == "__main__":
    args = sys.argv[1:]
    assert len(args) == 2, "usage: render_rmrl.py infile outfile"

    infile = args[0]
    outfile = args[1]

    status = render_rmrl(infile, outfile)

    exit(status)
hersle commented 1 month ago

Thanks, these are nice additions!

Do you think it makes sense to move the chain rendering logic inside the main script? I'm thinking that the --renderer argument could be expanded to take a list of renderers that will be used to attempt to render each document, in order. For example, ./rmirro.py --renderers render_rmrl.py render_maxio.py render_fail.py would be equivalent to your chain renderer. That would prevent "hard-coding" the list of renderers into a script, and perhaps make it a little easier for users to change.

I think the default should still be --renderers render_usb.py for ease of use, stability and predictability. My first thought is that the chain rendering logic should throw an error/warning after trying the last renderer and output nothing. If one wants to change that behavior and output a "dummy file", one can elegantly include your failing renderer like --renderers render_rmrl.py render_fail.py. Do you think that is sensible behavior?

Ph-St commented 1 month ago

I agree with both points! I think it would be much better if the renderers could be specified in parameters instead of hard-coded in the chain-renderer and your proposed standard behavior also makes a lot of sense. Thanks for incorporating these changes!

hersle commented 1 month ago

You can now do e.g. ./rmirro.py --renderers render_fail.py render_usb.py.

hersle commented 1 month ago

I also added your rmrl renderer. Thanks!

Ph-St commented 1 month ago

Brilliant, many thanks for incorporating the new features!

Ph-St commented 1 month ago

I just tried it out and perhaps I'm not using it right, but when I run

./rmirro.py remarkable --renderers render_rmrl.py render_maxio.py render_fail.py

the script just stops on the first occurrence where render_rmrl.py and render_maxio.py can't render the document. That's in effect the same outcome as before, whereas the idea of render_fail.py was to note the failed rendering (in my case by producing a PDF that said so) and pass a success status back to the main script in order to allow the script to continue running. I guess the simplest way to achieve that would be to change

exit(1) # fail

to

exit(0) # fail

and perhaps give a more verbose error message?

Cheers!

hersle commented 3 weeks ago

Thanks, I see what you mean, and I like the intention of producing placeholder PDFs for documents that fail to render. But I am a little concerned that the placeholder documents in some unfortunate scenario are registered as "proper documents" that are synced back to the RM from the PC and overwrites the actual documents on the RM.

Ph-St commented 3 weeks ago

Ah, that's a good point. Well, the placeholder pdfs were just meant as a record of which files failed, so I suppose that a proper summary at the end of an rmirro run, in which all files that couldn't be rendered are listed, would do the job as well. And if that is implemented, then perhaps render_fail is not even necessary anymore, but could be replaced by a parameter that defines whether rmirro should stop as soon as a document cannot be rendered or whether it should continue.