| name | file-converter |
| description | Convert, merge, split, and compress files across formats — documents, images, audio, and more. |
File Converter
Convert between data, document, image, audio formats, and ZIP archives. One-liners for each conversion pair.
Tool Map
| Domain | Tool | Install |
|---|---|---|
| CSV/JSON/Excel/Parquet | pandas | pip install pandas openpyxl pyarrow |
| YAML | pyyaml | pip install pyyaml |
| XML ↔ dict | xmltodict | pip install xmltodict |
| Any doc format ↔ any | pandoc (CLI) | apt install pandoc or pip install pypandoc_binary |
| Markdown → HTML | markdown | pip install markdown |
| HTML → Markdown | markdownify | pip install markdownify |
| .docx read/write | python-docx | pip install python-docx |
| PDF → text/tables | pdfplumber | pip install pdfplumber |
| PDF → images | pdf2image | pip install pdf2image + apt install poppler-utils |
| PDF manipulation | pypdf | pip install pypdf |
| Images | Pillow | pip install Pillow |
| SVG → PNG | cairosvg | pip install cairosvg |
| HEIC → JPG | pillow-heif | pip install pillow-heif |
| Audio formats | pydub | pip install pydub + apt install ffmpeg |
| EPUB ↔ other | pandoc or ebooklib | pip install ebooklib |
| HTML → PDF | weasyprint | pip install weasyprint |
| GIF creation | Pillow or imageio | pip install imageio[ffmpeg] |
| PDF → SVG | pdf2image + potrace or pymupdf | pip install pymupdf |
| ZIP archives | zipfile (stdlib) | built-in, no install needed |
File Input Handling
When a user wants to convert a file that can't be attached directly in the chat (e.g., .heic, .flac, .epub, .psd, .m4a, .wma, .parquet), ask them to upload it to the project's file system. Uploaded files typically appear in attached_assets/ or the project root. Always check both locations. If the file isn't found, ask the user where they saved it.
Common unsupported-in-chat but convertible formats: .heic, .avif, .webp, .flac, .ogg, .m4a, .wma, .aiff, .epub, .parquet, .psd, .svg, .zip
Data Formats
import pandas as pd, json, yaml, xmltodict
pd.read_csv("in.csv").to_json("out.json", orient="records", indent=2)
pd.read_json("in.json").to_csv("out.csv", index=False)
pd.read_csv("in.csv").to_excel("out.xlsx", index=False, engine="openpyxl")
pd.read_excel("in.xlsx", sheet_name="Sheet1").to_csv("out.csv", index=False)
pd.read_csv("in.csv").to_parquet("out.parquet", engine="pyarrow", compression="snappy")
data = yaml.safe_load(open("in.yaml")) \
json.dump(data, open("out.json", "w"), indent=2)
yaml.safe_dump(json.load(open("in.json")), open("out.yaml", "w"), sort_keys=False)
data = xmltodict.parse(open("in.xml").read())
json.dump(data, open("out.json", "w"), indent=2)
open("out.xml", "w").write(xmltodict.unparse(data, pretty=True))
pd.read_json("in.jsonl", lines=True).to_csv("out.csv", index=False)
Encoding gotchas:
-
pd.read_csv("f.csv", encoding="utf-8-sig") strips the BOM that Excel inserts
-
Auto-detect: import chardet; enc = chardet.detect(open("f.csv","rb").read())["encoding"]
-
CSV delimiter sniffing: pd.read_csv("f.csv", sep=None, engine="python")
Nested JSON → flat CSV:
pd.json_normalize(data, sep=".").to_csv("out.csv", index=False) \
Document Formats — pandoc is the Swiss Army knife
pandoc input.md -o output.pdf --pdf-engine=xelatex
pandoc input.md -o output.docx
pandoc input.docx -o output.md --extract-media=.
pandoc input.html -o output.md -t gfm
pandoc -f docx -t rst input.docx -o output.rst
import pypandoc
pypandoc.convert_file("in.md", "docx", outputfile="out.docx")
Without pandoc (pure Python):
import markdown
html = markdown.markdown(open("in.md").read(), extensions=["tables", "fenced_code", "toc"])
from markdownify import markdownify
md = markdownify(html, heading_style="ATX") \
PDF Operations
import pdfplumber
with pdfplumber.open("in.pdf") as pdf:
text = "\n".join(p.extract_text() or "" for p in pdf.pages)
tables = pdf.pages[0].extract_tables() \
from pdf2image import convert_from_path
for i, img in enumerate(convert_from_path("in.pdf", dpi=200)):
img.save(f"page_{i+1}.png")
from pypdf import PdfReader, PdfWriter
writer = PdfWriter()
for path in ["a.pdf", "b.pdf"]:
for page in PdfReader(path).pages:
writer.add_page(page)
writer.write("merged.pdf")
reader = PdfReader("in.pdf")
writer = PdfWriter()
for p in reader.pages[1:5]:
writer.add_page(p)
writer.write("pages_2-5.pdf")
PDF gotchas:
-
pdf2image needs poppler-utils installed system-wide (not a pip package)
-
Scanned PDFs have no text layer — pdfplumber returns None. Use pytesseract OCR on pdf2image output.
-
PyPDF2 is deprecated → use pypdf (same API, maintained fork)
Image Formats
from PIL import Image
Image.open("in.png").convert("RGB").save("out.jpg", quality=90)
Image.open("in.jpg").save("out.webp", quality=85, method=6) \
Image.open("in.jpg").save("out.avif", quality=75)
from pillow_heif import register_heif_opener
register_heif_opener()
Image.open("in.heic").convert("RGB").save("out.jpg", quality=90)
import cairosvg
cairosvg.svg2png(url="in.svg", write_to="out.png", output_width=1024)
from pathlib import Path
for p in Path("imgs").glob("*.png"):
Image.open(p).convert("RGB").save(p.with_suffix(".jpg"), quality=85)
Image gotchas:
-
PNG → JPG: must convert("RGB") first or transparency crashes the save
-
quality for PNG is meaningless (lossless) — use optimize=True, compress_level=9
-
Pillow can't open .svg natively — use cairosvg or svglib
-
GIF → MP4 is a video operation: ffmpeg -i in.gif -pix_fmt yuv420p out.mp4
Audio Formats
from pydub import AudioSegment
AudioSegment.from_mp3("in.mp3").export("out.wav", format="wav")
AudioSegment.from_wav("in.wav").export("out.mp3", format="mp3", bitrate="192k")
AudioSegment.from_file("in.flac", format="flac").export("out.mp3", format="mp3", bitrate="320k")
AudioSegment.from_ogg("in.ogg").export("out.mp3", format="mp3", bitrate="192k")
AudioSegment.from_file("in.m4a", format="m4a").export("out.mp3", format="mp3", bitrate="256k")
AudioSegment.from_file("in.wma", format="wma").export("out.flac", format="flac")
audio = AudioSegment.from_file("in.mp3")
audio[:30000].export("first_30s.mp3", format="mp3") \
combined = AudioSegment.from_file("a.mp3") + AudioSegment.from_file("b.mp3")
combined.export("merged.mp3", format="mp3")
audio = AudioSegment.from_file("in.mp3")
louder = audio + 6 \
quieter = audio - 6 \
louder.export("louder.mp3", format="mp3")
audio = AudioSegment.from_file("in.mp3")
print(f"Duration: {len(audio)/1000:.1f}s, Channels: {audio.channels}, "
f"Sample rate: {audio.frame_rate}Hz, Sample width: {audio.sample_width*8}bit")
from pathlib import Path
for p in Path("audio").glob("*.wav"):
AudioSegment.from_wav(str(p)).export(p.with_suffix(".mp3"), format="mp3", bitrate="192k")
Audio gotchas:
-
pydub requires ffmpeg installed system-wide for non-WAV formats
-
Bitrate options: "128k" (small/low quality), "192k" (balanced), "256k" (high), "320k" (max for MP3)
-
WAV files are uncompressed — expect 10x larger file sizes than MP3
-
For sample rate conversion: audio.set_frame_rate(44100).export("out.wav", format="wav")
-
Mono to stereo: audio.set_channels(2) / Stereo to mono: audio.set_channels(1)
ZIP Archives
import zipfile
from pathlib import Path
with zipfile.ZipFile("archive.zip", "w", zipfile.ZIP_DEFLATED) as zf:
zf.write("file1.txt")
zf.write("file2.csv")
zf.write("images/photo.jpg")
import shutil
shutil.make_archive("archive", "zip", root_dir="my_folder") \
with zipfile.ZipFile("archive.zip", "r") as zf:
zf.extractall("output_dir")
with zipfile.ZipFile("archive.zip", "r") as zf:
zf.extract("file1.txt", "output_dir")
with zipfile.ZipFile("archive.zip", "r") as zf:
for info in zf.infolist():
print(f"{info.filename} {info.file_size:,} bytes {info.compress_size:,} compressed")
with zipfile.ZipFile("archive.zip", "r") as zf:
content = zf.read("file1.txt").decode("utf-8")
with zipfile.ZipFile("archive.zip", "a") as zf:
zf.write("new_file.txt")
import pyzipper
with pyzipper.AESZipFile("secure.zip", "w", compression=pyzipper.ZIP_DEFLATED,
encryption=pyzipper.WZ_AES) as zf:
zf.setpassword(b"my_password")
zf.write("secret.txt")
with zipfile.ZipFile("all_pdfs.zip", "w", zipfile.ZIP_DEFLATED) as zf:
for p in Path(".").glob("**/*.pdf"):
zf.write(p)
ZIP gotchas:
-
zipfile is in Python's standard library — no install needed
-
Always use ZIP_DEFLATED compression (default is ZIP_STORED = no compression)
-
For password-protected ZIPs, stdlib zipfile can only read (not write) — use pyzipper for encrypted writes
-
Max file size in standard ZIP is 4 GB; use allowZip64=True (default in Python 3) for larger files
-
shutil.make_archive is the simplest way to ZIP an entire directory tree
EPUB Formats
import pypandoc
pypandoc.convert_file("in.epub", "markdown", outputfile="out.md")
pypandoc.convert_file("in.epub", "html", outputfile="out.html")
pypandoc.convert_file("in.epub", "docx", outputfile="out.docx")
pypandoc.convert_file("in.epub", "plain", outputfile="out.txt")
pypandoc.convert_file("in.md", "epub", outputfile="out.epub",
extra_args=["--metadata", "title=My Book"])
pypandoc.convert_file("in.html", "epub", outputfile="out.epub",
extra_args=["--metadata", "title=My Book"])
pypandoc.convert_file("in.docx", "epub", outputfile="out.epub")
pandoc in.epub -o out.md
pandoc in.epub -o out.pdf --pdf-engine=xelatex
pandoc in.md -o out.epub --metadata title="My Book"
pandoc in.html -o out.epub --metadata title="My Book" --epub-cover-image=cover.jpg
from ebooklib import epub
book = epub.read_epub("in.epub")
for item in book.get_items_of_type(9): \
print(item.get_name())
html_content = item.get_content().decode("utf-8")
book = epub.EpubBook()
book.set_identifier("id123")
book.set_title("My Book")
book.set_language("en")
book.add_author("Author Name")
ch1 = epub.EpubHtml(title="Chapter 1", file_name="ch1.xhtml", lang="en")
ch1.content = "<h1>Chapter 1</h1><p>Hello world.</p>"
book.add_item(ch1)
book.toc = [epub.Link("ch1.xhtml", "Chapter 1", "ch1")]
book.add_item(epub.EpubNcx())
book.add_item(epub.EpubNav())
book.spine = ["nav", ch1]
epub.write_epub("out.epub", book)
EPUB gotchas:
-
pandoc is the simplest for format-to-format EPUB conversion
-
Always add --metadata title="..." when creating EPUB — readers require a title
-
EPUB is essentially a ZIP of HTML files — ebooklib gives you fine-grained control
-
For EPUB → PDF, pandoc needs a LaTeX engine (texlive-xetex)
-
Cover images: use --epub-cover-image=cover.jpg with pandoc
HTML to PDF
from weasyprint import HTML
HTML("in.html").write_pdf("out.pdf")
HTML("https://example.com").write_pdf("page.pdf")
HTML(string="<h1>Hello</h1><p>World</p>").write_pdf("out.pdf")
HTML("in.html").write_pdf("out.pdf", stylesheets=["custom.css"])
from weasyprint import CSS
HTML("in.html").write_pdf("out.pdf", stylesheets=[
CSS(string="@page { size: A4; margin: 2cm; }")
])
HTML("in.html").write_pdf("out.pdf", stylesheets=[
CSS(string="@page { size: A4 landscape; margin: 1.5cm; }")
])
pandoc in.html -o out.pdf --pdf-engine=xelatex
weasyprint in.html out.pdf
weasyprint https://example.com page.pdf
HTML to PDF gotchas:
-
weasyprint supports CSS3 including flexbox, grid, and @page rules — best for styled documents
-
weasyprint does NOT run JavaScript — for JS-heavy pages, use playwright or pyppeteer instead
-
For JS-rendered pages: playwright → page.pdf() is the most reliable option
-
pandoc HTML → PDF goes through LaTeX, so complex CSS layouts may not render correctly
-
Large HTML files with many images: use HTML(filename="in.html", base_url=".") so relative image paths resolve
GIF Creation
from PIL import Image
import imageio.v3 as iio
from pathlib import Path
frames = [Image.open(f"frame_{i}.png") for i in range(10)]
frames[0].save("out.gif", save_all=True, append_images=frames[1:],
duration=100, loop=0) \
frames = [Image.open(f"frame_{i}.png").convert("RGBA") for i in range(10)]
frames[0].save("out.gif", save_all=True, append_images=frames[1:],
duration=100, loop=0, optimize=True)
frame_paths = sorted(Path("frames").glob("*.png"))
frames = [Image.open(p) for p in frame_paths]
frames[0].save("out.gif", save_all=True, append_images=frames[1:],
duration=100, loop=0)
gif = Image.open("in.gif")
for i in range(gif.n_frames):
gif.seek(i)
gif.save(f"frame_{i}.png")
gif = Image.open("in.gif")
resized_frames = []
for i in range(gif.n_frames):
gif.seek(i)
resized_frames.append(gif.copy().resize((320, 240), Image.LANCZOS))
resized_frames[0].save("small.gif", save_all=True, append_images=resized_frames[1:],
duration=gif.info.get("duration", 100), loop=0)
import imageio.v3 as iio
frames = iio.imread("in.mp4", plugin="pyav")
iio.imwrite("out.gif", frames, plugin="pillow", duration=40, loop=0)
GIF gotchas:
-
GIF is limited to 256 colors per frame — complex images lose quality
-
Use optimize=True to reduce file size, but large GIFs are still huge compared to MP4
-
duration is per-frame in milliseconds (100ms = 10 FPS, 40ms = 25 FPS)
-
loop=0 means infinite loop; loop=1 plays once then stops
-
For video → GIF, consider downscaling first — full-resolution GIFs are enormous
-
Pillow GIF output doesn't support transparency well — use imageio for better results
-
For best quality: create GIF from video with ffmpeg: ffmpeg -i in.mp4 -vf "fps=15,scale=480:-1" out.gif
PDF to SVG
import fitz \
doc = fitz.open("in.pdf")
page = doc[0]
svg_text = page.get_svg_image()
with open("page_1.svg", "w") as f:
f.write(svg_text)
doc = fitz.open("in.pdf")
for i, page in enumerate(doc):
svg_text = page.get_svg_image()
with open(f"page_{i+1}.svg", "w") as f:
f.write(svg_text)
doc.close()
doc = fitz.open("in.pdf")
page = doc[0]
mat = fitz.Matrix(2, 2) \
svg_text = page.get_svg_image(matrix=mat)
with open("page_hires.svg", "w") as f:
f.write(svg_text)
pdf2svg in.pdf out.svg 1 \# page number
inkscape in.pdf --export-type=svg --export-filename=out.svg
pdftocairo -svg in.pdf out.svg
PDF to SVG gotchas:
-
pymupdf (imported as fitz) produces true vector SVGs — text stays as text, paths stay as paths
-
Scanned PDFs produce SVGs with embedded raster images (no vector data to extract)
-
Large PDFs with complex graphics produce very large SVG files
-
pdf2svg CLI tool is simple but must be installed separately (apt install pdf2svg)
-
For rasterized SVG (simpler but not truly vector): render PDF to PNG first, then embed in SVG
Validation
Always verify output:
assert len(pd.read_csv("out.csv")) == len(pd.read_json("in.json"))
json.load(open("out.json"))
Image.open("out.jpg").verify()