Add reMarkable backup system with web UI
- Docker container with rmapi cloud backup, PDF conversion pipeline - Web UI: file browser, multi-select, delete/move, thumbnail preview - Sync: backup from reMarkable cloud, rmdoc→PDF conversion via rmc+Inkscape - Excluded-files mechanism to prevent deleted items from returning after sync Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
169
remarkable/scripts/convert-to-pdf.sh
Executable file
169
remarkable/scripts/convert-to-pdf.sh
Executable file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
# Konvertiert alle .rmdoc-Dateien in /mnt/remarkable/current zu PDFs.
|
||||
# Originale bleiben erhalten. Bereits konvertierte Dateien werden übersprungen.
|
||||
#
|
||||
# Nutzung:
|
||||
# ./convert-to-pdf.sh [BASE_DIR]
|
||||
# BASE_DIR: Verzeichnis mit .rmdoc-Dateien (default: /mnt/remarkable/current)
|
||||
set -euo pipefail
|
||||
|
||||
BASE_DIR="${1:-/mnt/remarkable/current}"
|
||||
TMPDIR_BASE="/tmp/rmdoc_convert"
|
||||
CONVERTED=0
|
||||
SKIPPED=0
|
||||
FAILED=0
|
||||
|
||||
if ! command -v rmc >/dev/null 2>&1; then
|
||||
echo "Fehler: rmc nicht im PATH. Installation: pip3 install rmc --break-system-packages"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v inkscape >/dev/null 2>&1; then
|
||||
echo "Fehler: inkscape nicht im PATH. Installation: apt install inkscape"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v pdfunite >/dev/null 2>&1; then
|
||||
echo "Fehler: pdfunite nicht im PATH. Installation: apt install poppler-utils"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS= read -r -d '' RMDOC; do
|
||||
PDF="${RMDOC%.rmdoc}.pdf"
|
||||
|
||||
# Überspringen falls PDF bereits existiert und neuer als rmdoc
|
||||
if [[ -f "${PDF}" ]] && [[ "${PDF}" -nt "${RMDOC}" ]]; then
|
||||
(( SKIPPED++ )) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
# Primär: rmrl (direkt .rmdoc → PDF, kein Inkscape nötig)
|
||||
if python3 -m rmrl "${RMDOC}" > "${PDF}" 2>/tmp/rmrl_debug.log && [[ -s "${PDF}" ]]; then
|
||||
echo "OK: $(basename "${RMDOC}") (rmrl)"
|
||||
pdftoppm -r 72 -jpeg -singlefile "${PDF}" "${PDF%.pdf}.thumb" 2>/dev/null || true
|
||||
(( CONVERTED++ )) || true
|
||||
rm -rf "${PDF}" # Platzhalter entfernen falls leer
|
||||
continue
|
||||
fi
|
||||
|
||||
# Primär: rmrl (direkt .rmdoc → PDF, kein Inkscape nötig)
|
||||
if python3 -m rmrl "${RMDOC}" > "${PDF}" 2>/tmp/rmrl_debug.log && [[ -s "${PDF}" ]]; then
|
||||
echo "OK: $(basename "${RMDOC}") (rmrl)"
|
||||
pdftoppm -r 72 -jpeg -singlefile "${PDF}" "${PDF%.pdf}.thumb" 2>/dev/null || true
|
||||
(( CONVERTED++ )) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
TMPDIR="${TMPDIR_BASE}/$$_$(echo "${RMDOC%.rmdoc}" | md5sum | cut -c1-8)"
|
||||
mkdir -p "${TMPDIR}"
|
||||
|
||||
# rmdoc entpacken
|
||||
if ! unzip -o -q "${RMDOC}" -d "${TMPDIR}" 2>/dev/null; then
|
||||
echo "WARN: Konnte nicht entpacken: ${RMDOC}"
|
||||
rm -rf "${TMPDIR}"
|
||||
(( FAILED++ )) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
# Seitenreihenfolge aus .content lesen, sonst alphabetisch
|
||||
RM_FILES=()
|
||||
CONTENT_FILE="$(find "${TMPDIR}" -maxdepth 1 -name "*.content" | head -1)"
|
||||
DOC_UUID="$(basename "${CONTENT_FILE%.content}")"
|
||||
RM_DIR="${TMPDIR}/${DOC_UUID}"
|
||||
|
||||
if [[ -f "${CONTENT_FILE}" ]] && [[ -d "${RM_DIR}" ]]; then
|
||||
# Seiten-UUIDs aus .content in Reihenfolge extrahieren
|
||||
mapfile -t PAGE_IDS < <(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
data = json.load(open('${CONTENT_FILE}'))
|
||||
pages = data.get('cPages', {}).get('pages', [])
|
||||
for p in pages:
|
||||
print(p['id'])
|
||||
except Exception as e:
|
||||
sys.exit(1)
|
||||
" 2>/dev/null)
|
||||
|
||||
RM_FILES=()
|
||||
for PAGE_ID in "${PAGE_IDS[@]}"; do
|
||||
RM_FILE="${RM_DIR}/${PAGE_ID}.rm"
|
||||
if [[ -f "${RM_FILE}" ]]; then
|
||||
RM_FILES+=("${RM_FILE}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Fallback: alle .rm-Dateien alphabetisch
|
||||
if [[ ${#RM_FILES[@]} -eq 0 ]]; then
|
||||
mapfile -t RM_FILES < <(find "${TMPDIR}" -name "*.rm" | sort)
|
||||
fi
|
||||
|
||||
EMBEDDED_PDF="$(find "${TMPDIR}" -maxdepth 2 -name "*.pdf" | head -1)"
|
||||
|
||||
if [[ ${#RM_FILES[@]} -eq 0 ]]; then
|
||||
# Kein Handschrift-Inhalt — eingebettete PDF direkt nutzen
|
||||
if [[ -n "${EMBEDDED_PDF}" ]]; then
|
||||
cp "${EMBEDDED_PDF}" "${PDF}"
|
||||
echo "OK: $(basename "${RMDOC}") (eingebettete PDF)"
|
||||
pdftoppm -r 72 -jpeg -singlefile "${PDF}" "${PDF%.pdf}.thumb" 2>/dev/null || true
|
||||
(( CONVERTED++ )) || true
|
||||
else
|
||||
(( SKIPPED++ )) || true
|
||||
fi
|
||||
rm -rf "${TMPDIR}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Annotiertes Import-Dokument: Annotations auf Original-PDF legen
|
||||
if [[ -n "${EMBEDDED_PDF}" ]] && [[ ${#RM_FILES[@]} -gt 0 ]]; then
|
||||
if python3 /usr/local/bin/remarkable-merge.py "${TMPDIR}" "${EMBEDDED_PDF}" "${PDF}" 2>/tmp/merge_debug.log && [[ -s "${PDF}" ]]; then
|
||||
echo "OK: $(basename "${RMDOC}") (PDF + Annotationen)"
|
||||
pdftoppm -r 72 -jpeg -singlefile "${PDF}" "${PDF%.pdf}.thumb" 2>/dev/null || true
|
||||
(( CONVERTED++ )) || true
|
||||
else
|
||||
cp "${EMBEDDED_PDF}" "${PDF}"
|
||||
echo "WARN: Merge fehlgeschlagen ($(cat /tmp/merge_debug.log | tail -1)), nutze Original-PDF: $(basename "${RMDOC}")"
|
||||
pdftoppm -r 72 -jpeg -singlefile "${PDF}" "${PDF%.pdf}.thumb" 2>/dev/null || true
|
||||
(( CONVERTED++ )) || true
|
||||
fi
|
||||
rm -rf "${TMPDIR}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Jede Seite einzeln konvertieren, dann zusammenfügen
|
||||
PAGE_PDFS=()
|
||||
CONVERT_OK=true
|
||||
for RM_FILE in "${RM_FILES[@]}"; do
|
||||
PAGE_PDF="${TMPDIR}/$(basename "${RM_FILE%.rm}").pdf"
|
||||
if rmc -t pdf -o "${PAGE_PDF}" "${RM_FILE}" 2>/dev/null && [[ -s "${PAGE_PDF}" ]]; then
|
||||
PAGE_PDFS+=("${PAGE_PDF}")
|
||||
else
|
||||
CONVERT_OK=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${CONVERT_OK}" == true ]] && [[ ${#PAGE_PDFS[@]} -gt 0 ]]; then
|
||||
if [[ ${#PAGE_PDFS[@]} -eq 1 ]]; then
|
||||
cp "${PAGE_PDFS[0]}" "${PDF}"
|
||||
else
|
||||
pdfunite "${PAGE_PDFS[@]}" "${PDF}" 2>/dev/null
|
||||
fi
|
||||
if [[ -s "${PDF}" ]]; then
|
||||
echo "OK: $(basename "${RMDOC}") (${#PAGE_PDFS[@]} Seiten)"
|
||||
pdftoppm -r 72 -jpeg -singlefile "${PDF}" "${PDF%.pdf}.thumb" 2>/dev/null || true
|
||||
(( CONVERTED++ )) || true
|
||||
else
|
||||
rm -f "${PDF}"
|
||||
echo "WARN: Leere PDF für: $(basename "${RMDOC}")"
|
||||
(( FAILED++ )) || true
|
||||
fi
|
||||
else
|
||||
rm -f "${PDF}"
|
||||
echo "WARN: Konvertierung fehlgeschlagen: $(basename "${RMDOC}")"
|
||||
(( FAILED++ )) || true
|
||||
fi
|
||||
|
||||
rm -rf "${TMPDIR}"
|
||||
|
||||
done < <(find "${BASE_DIR}" -name "*.rmdoc" -print0)
|
||||
|
||||
echo "== Konvertierung fertig: ${CONVERTED} konvertiert, ${SKIPPED} übersprungen, ${FAILED} fehlgeschlagen =="
|
||||
Reference in New Issue
Block a user