Convert#
The convert tool reformats files offline using system converters, falling
back gracefully when a specific one is unavailable. Choose the destination
format by the output_path extension (.png, .jpg, .txt, .md),
and pass dry_run to see the converter plan without executing. Image-to-image
output paths are literal filenames: asset%03d.jpg writes that exact name,
not a numbered image sequence. It has no
markdown-fence block type, so ask for it in natural language and let the model
call it with structured arguments.
Conversions stage their single output beside the destination and replace the
requested file only after success. A failed conversion leaves an existing file
unchanged and does not publish a partial new file. Existing regular-file symlinks
are followed without replacing the link; the requested extension still selects
the format. Existing permission bits are retained, but replacement creates a new
inode (other hardlinks keep the old contents); ownership, ACLs and extended
attributes are not preserved. The destination directory must be writable even
when the old output file itself is writable. This is not crash durability or
protection against concurrent pathname changes. A cleanup problem after
successful publication is reported as a warning, not as failed conversion.
Custom Converter subclasses can implement _convert to use this staging
boundary. Existing subclasses that override convert remain supported and
retain their own publication behavior; overriding convert bypasses staging.
For example:
gptme "convert report.pdf to report-page-1.png at high quality, dry run first"
When converting images to PNG or JPEG (.png, .jpg, .jpeg), animated
input produces its first frame at the exact requested output path, not a numbered
image sequence. Animation-capable GIF and WebP targets are not subject to this
first-frame selection.
TIFF-to-TIFF conversion preserves document pages in their original order using ImageMagick. If a TIFF has multiple pages and ImageMagick is unavailable, the conversion fails explicitly without writing the destination; FFmpeg alone cannot preserve these pages. Single-page TIFF conversions can still use FFmpeg.
Image and video sources passed to FFmpeg are local filesystem paths, not
protocol URLs. A relative filename such as file:photo.png selects that
literal file, not a different file named photo.png.
Percent-bearing image source paths are literal too: scan%03d.png reads
that file, not scan001.png. For FFmpeg, these inputs are copied into a
private temporary directory under a safe basename, preserving normal content
probing (including animated PNG and GIF). This uses additional temporary disk
space equal to the source size; the copy is removed after conversion.
File format conversion tool for gptme.
Provides offline, multi-format file conversion with auto-detection and graceful degradation when specific converters are unavailable.
Supported conversions:
PDF → PNG/JPEG (Poppler/pdftoppm, ImageMagick fallback)
Image → Image: PNG ↔ JPEG ↔ WebP (FFmpeg primary, ImageMagick fallback). PNG/JPEG output selects the first frame of animated input.
DOCX → text/markdown (python-docx, LibreOffice headless fallback)
MP4/video → JPEG thumbnail (FFmpeg)
PDF → text (pypdf, Poppler/pdftotext fallback)
- class gptme.tools.convert.ConversionResult
ConversionResult(success: ‘bool’, output_path: ‘Path | None’, converter_used: ‘str’, warnings: ‘list[str]’ = <factory>, error: ‘str | None’ = None, lossy: ‘bool’ = False, metadata: ‘dict’ = <factory>)
- __init__(success: bool, output_path: ~pathlib.Path | None, converter_used: str, warnings: list[str] = <factory>, error: str | None = None, lossy: bool = False, metadata: dict = <factory>) None
- class gptme.tools.convert.Converter
- convert(src: Path, dest: Path, quality: str = 'medium', **kwargs) ConversionResult
Publish a completed single output, never a backend’s partial writes.
Resolve the destination separately from its staging filename: existing symlinks keep pointing to their target, while the requested extension still selects the output format. Replacement is on the same filesystem. This is not a durability or concurrent-path-mutation guarantee.
- class gptme.tools.convert.DocumentToTextConverter
DOCX/ODT/etc. → plain text or markdown via python-docx or LibreOffice.
- class gptme.tools.convert.ImageConverter
Image ↔ image format conversion via FFmpeg (primary) or ImageMagick.
- gptme.tools.convert.MAX_DOCX_EXPANDED_BYTES = 16777216
Maximum total expanded package size before document conversion refuses it. A DOCX is a zip, so a small attachment can expand into a large working set: measured 2026-11-05, 110 KB of input declaring 25 MB of body XML cost 421 MB peak RSS and 9.2 s, while an ordinary 37 KB document cost 44 MB and 0.09 s. The bound is on expanded work, because input bytes do not predict it: the same expanded XML compressed into 46 KB or stored as 4 MB costs the same. Use the whole package, not a fixed body path: DOCX relationships can relocate the body, and backends also load styles, headers, media and other parts. This deliberately counts embedded media too; a small body is not proof that the package is cheap to load. Non-ZIP documents retain backend-owned handling.
- class gptme.tools.convert.PDFToImageConverter
PDF first page → one raster image (PNG/JPEG) via pdftoppm or ImageMagick.
- class gptme.tools.convert.PDFToTextConverter
PDF → plain text via pypdf or pdftotext (Poppler).
- class gptme.tools.convert.ToolAvailability
ToolAvailability(ffmpeg: ‘bool’ = <factory>, imagemagick: ‘bool’ = <factory>, pdftoppm: ‘bool’ = <factory>, pdftotext: ‘bool’ = <factory>, libreoffice: ‘bool’ = <factory>, tesseract: ‘bool’ = <factory>, python_magic: ‘bool’ = <factory>, python_docx: ‘bool’ = <factory>, pypdf: ‘bool’ = <factory>)
- __init__(ffmpeg: bool = <factory>, imagemagick: bool = <factory>, pdftoppm: bool = <factory>, pdftotext: bool = <factory>, libreoffice: bool = <factory>, tesseract: bool = <factory>, python_magic: bool = <factory>, python_docx: bool = <factory>, pypdf: bool = <factory>) None
- class gptme.tools.convert.VideoThumbnailConverter
Video → JPEG thumbnail via FFmpeg.
- gptme.tools.convert.convert_file(src: Path, dest: Path, quality: str = 'medium', dry_run: bool = False, **kwargs) ConversionResult
Main entry point for file conversion.
- Parameters:
src – Source file path.
dest – Destination file path (extension determines target format).
quality – One of ‘low’, ‘medium’, ‘high’.
dry_run – If True, return converter plan without executing.