mayan.apps.converter — Moteur de conversion de fichiers en images et système de transformations visuelles non-destructives.
L'application Converter est le cœur du rendu visuel de Mayan. Elle est responsable de :
Tous les rendus de pages de documents passent par cette app. Elle est consommée principalement par l'app documents pour générer les miniatures et les aperçus interactifs.
ConverterBase est la classe abstraite qui orchestre la conversion. Son implémentation concrète est sélectionnable via CONVERTER_GRAPHICS_BACKEND ; le seul backend fourni est mayan.apps.converter.backends.python.ConverterPython.
DocumentFile.file_object (PDF, DOCX, ODT, image, txt…)
│
▼
ConverterBase.__init__()
│ détecte le MIME type
▼
┌─────────────────────────────────────────────┐
│ Format Office ? (DOCX, ODS, PPTX, ODT…) │
│ → LibreOffice --headless --convert-to pdf │
│ → fichier PDF temporaire │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ PDF ? │
│ → pdftoppm -r 150 (poppler) │
│ → image PPM/PNG de la page demandée │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Image native (PNG, JPEG, TIFF, GIF…) │
│ → PIL/Pillow Image.open() │
│ → seek(frame=page_number) │
└──────────────────┬──────────────────────────┘
│
▼
Pillow Image en mémoire
│
▼
Transformations (couches, dans l'ordre)
│
▼
BytesIO (PNG/JPEG final)
Tous les formats que LibreOffice sait ouvrir : .docx, .xlsx, .pptx, .odt, .ods, .odp, .rtf, .txt, .csv, et les emails .msg (dépaquetés puis passés à LibreOffice).
| Outil | Rôle | Paquet système |
|---|---|---|
| Pillow | Images natives (PNG, JPEG, TIFF, GIF, WebP…) | python3-pil |
| pdftoppm | Rastérisation PDF page par page | poppler-utils |
| pdfinfo | Comptage des pages PDF | poppler-utils |
| LibreOffice | Conversion Office → PDF | libreoffice |
Les transformations sont organisées en couches ordonnées. Chaque couche définit un ensemble de transformations applicables à un objet et les permissions requises pour les manipuler.
| Couche | order |
Rôle | Par défaut |
|---|---|---|---|
decorations |
10 | Éléments visuels ajoutés par d'autres apps (ex: marque "En révision" de l'app Checkouts) | Non |
saved_transformations |
100 | Transformations persistées par l'utilisateur sur un objet | Oui |
L'ordre détermine le séquençage : les couches à order faible s'appliquent avant celles à order élevé. Les décorations sont donc toujours rendues avant les transformations sauvegardées.
StoredLayer ← persistance en base d'une Layer (name, order)
└── ObjectLayer ← lien entre un StoredLayer et un objet métier (GenericForeignKey)
└── LayerTransformation ← une transformation concrète (name, arguments YAML, order, enabled)
LayerTransformation.arguments est un champ texte en YAML. Exemple pour une rotation :
{"degrees": 90, "fillcolor": "#FFFFFF"}
Les transformations sont exécutées dans l'ordre du champ order. L'ordre est auto-assigné si laissé vide.
Toutes les transformations héritent de BaseTransformation et implémentent execute_on(image) qui reçoit et retourne un objet PIL.Image.
| Classe | Nom interne | Arguments | Description |
|---|---|---|---|
TransformationCrop |
crop |
left, top, right, bottom |
Recadrage en pixels |
TransformationResize |
resize |
width, height |
Redimensionnement |
TransformationRotate |
rotate |
degrees, fillcolor |
Rotation libre |
TransformationRotate90 |
rotate90 |
— | Rotation 90° |
TransformationRotate180 |
rotate180 |
— | Rotation 180° |
TransformationRotate270 |
rotate270 |
— | Rotation 270° |
TransformationZoom |
zoom |
percent |
Zoom en pourcentage |
TransformationFlip |
flip |
— | Retournement vertical |
TransformationMirror |
mirror |
— | Miroir horizontal |
| Classe | Nom interne | Arguments | Description |
|---|---|---|---|
TransformationGaussianBlur |
gaussianblur |
radius |
Flou gaussien (Pillow ImageFilter.GaussianBlur) |
TransformationUnsharpMask |
unsharpmask |
radius, percent, threshold |
Netteté (Pillow UnsharpMask) |
TransformationLineArt |
lineart |
— | Conversion en art linéaire (noir & blanc contrasté) |
| Classe | Nom interne | Arguments | Description |
|---|---|---|---|
TransformationDrawRectangle |
draw_rectangle |
left, top, right, bottom, fillcolor, outlinecolor, outlinewidth |
Rectangle en coordonnées absolues |
TransformationDrawRectanglePercent |
draw_rectangle_percent |
idem en % | Rectangle en coordonnées relatives |
TransformationQRCodePercent |
qr_code_percent |
coordonnées %, code_value |
Génère et colle un QR code |
| Classe | Nom interne | Arguments | Description |
|---|---|---|---|
TransformationAssetPaste |
paste_asset |
asset_name, coordonnées absolues |
Colle un asset à position fixe |
TransformationAssetPastePercent |
paste_asset_percent |
asset_name, coordonnées en % |
Colle un asset en position relative |
TransformationAssetWatermark |
paste_asset_watermark |
asset_name, opacité, position en % |
Colle un asset en filigrane semi-transparent |
Un Asset est une image (PNG, JPEG, SVG) stockée dans Mayan et réutilisable comme source dans les transformations de collage (paste_asset, paste_asset_watermark).
Exemples d'usage : logo d'entreprise en filigrane, tampon "CONFIDENTIEL", cachet numérique.
Les assets utilisent deux DefinedStorage distincts :
| Storage | Nom interne | Rôle |
|---|---|---|
storage_assets |
converter__assets |
Fichiers originaux des assets |
storage_assets_cache |
converter__assets_cache |
Cache des rendus d'assets |
Configurables via CONVERTER_ASSET_STORAGE_BACKEND et CONVERTER_ASSET_CACHE_STORAGE_BACKEND.
Chaque rendu de page (objet + liste de transformations) est mis en cache. La clé de cache est un hash SHA-256 calculé à partir de la combinaison de toutes les transformations de la pile (BaseTransformation.combine()). Si les transformations changent, le hash change, et un nouveau fichier de cache est généré.
Le cache est géré par l'app file_caching. Le converter utilise le cache de l'app documents (documents__cache) pour stocker les miniatures.
converter_tagsLe templatetag {% converter_get_object_image_data %} est la porte d'entrée du converter dans les templates Django. Il génère l'URL de l'image rendue pour un objet donné :
{% load converter_tags %}
{% converter_get_object_image_data document_file_page as image_data %}
<img src="{{ image_data.url }}" width="{{ image_data.width }}" />
Ce tag est utilisé dans les templates de miniatures (thumbnail.html), d'aperçus de pages (page_image.html, page_carousel.html) et dans le template d'impression (document_print.html).
| Tâche | Queue | Rôle |
|---|---|---|
task_content_object_image_generate |
converter |
Génère l'image d'un objet avec les transformations demandées |
La tâche est retry avec backoff en cas de LockError. Le nombre maximum de tentatives est configurable via CONVERTER_IMAGE_GENERATION_MAX_RETRIES.
| Variable | Défaut | Rôle |
|---|---|---|
CONVERTER_GRAPHICS_BACKEND |
…backends.python.ConverterPython |
Classe backend de conversion |
CONVERTER_GRAPHICS_BACKEND_ARGUMENTS |
{} |
Arguments du backend (voir ci-dessous) |
CONVERTER_IMAGE_CACHE_TIME |
86400 | Durée (s) du cache navigateur pour les images |
CONVERTER_IMAGE_GENERATION_MAX_RETRIES |
3 | Retries max pour la tâche de génération |
CONVERTER_IMAGE_GENERATION_TIMEOUT |
60 | Timeout (s) de la tâche |
CONVERTER_LOAD_TRUNCATED_IMAGES |
False | Accepter les images tronquées/corrompues |
CONVERTER_ASSET_CACHE_MAXIMUM_SIZE |
10 Mo | Taille max du cache d'assets |
CONVERTER_GRAPHICS_BACKEND_ARGUMENTS)MAYAN_CONVERTER_GRAPHICS_BACKEND_ARGUMENTS = {
"pillow_format": "PNG", # format de sortie (PNG ou JPEG)
"pillow_maximum_image_pixels": 89478485, # limite anti-bombe (Pillow)
"pdftoppm_path": "/usr/bin/pdftoppm",
"pdftoppm_dpi": 150, # résolution de rastérisation PDF
"pdftoppm_format": "png",
"pdfinfo_path": "/usr/bin/pdfinfo",
"libreoffice_path": "/usr/bin/soffice",
}
| Permission | Rôle |
|---|---|
permission_transformation_create |
Ajouter une transformation à un objet |
permission_transformation_delete |
Supprimer une transformation |
permission_transformation_edit |
Modifier une transformation |
permission_transformation_view |
Voir les transformations d'un objet |
Les permissions sont définies par couche : chaque Layer porte sa propre permission_map.
L'app Converter expose une action de workflow TransformationWorkflowAction qui permet d'ajouter automatiquement une transformation à un document lors d'une transition de workflow. Par exemple : apposer automatiquement un filigrane "APPROUVÉ" quand un document passe en état Approuvé.