1 ---
2 name: file-converter
3 description: Convert, merge, split, and compress files across formats — documents, images, audio, and more.
4 ---
5
6 # File Converter
7
8 Convert between data, document, image, audio formats, and ZIP archives. One-liners for each conversion pair.
9
10 ## Tool Map
11
12 | Domain | Tool | Install |
13
14 |---|---|---|
15
16 | CSV/JSON/Excel/Parquet | `pandas` | `pip install pandas openpyxl pyarrow` |
17
18 | YAML | `pyyaml` | `pip install pyyaml` |
19
20 | XML ↔ dict | `xmltodict` | `pip install xmltodict` |
21
22 | Any doc format ↔ any | **pandoc** (CLI) | `apt install pandoc` or `pip install pypandoc_binary` |
23
24 | Markdown → HTML | `markdown` | `pip install markdown` |
25
26 | HTML → Markdown | `markdownify` | `pip install markdownify` |
27
28 | .docx read/write | `python-docx` | `pip install python-docx` |
29
30 | PDF → text/tables | `pdfplumber` | `pip install pdfplumber` |
31
32 | PDF → images | `pdf2image` | `pip install pdf2image` + `apt install poppler-utils` |
33
34 | PDF manipulation | `pypdf` | `pip install pypdf` |
35
36 | Images | `Pillow` | `pip install Pillow` |
37
38 | SVG → PNG | `cairosvg` | `pip install cairosvg` |
39
40 | HEIC → JPG | `pillow-heif` | `pip install pillow-heif` |
41
42 | Audio formats | `pydub` | `pip install pydub` + `apt install ffmpeg` |
43
44 | EPUB ↔ other | **pandoc** or `ebooklib` | `pip install ebooklib` |
45
46 | HTML → PDF | `weasyprint` | `pip install weasyprint` |
47
48 | GIF creation | `Pillow` or `imageio` | `pip install imageio[ffmpeg]` |
49
50 | PDF → SVG | `pdf2image` + `potrace` or `pymupdf` | `pip install pymupdf` |
51
52 | ZIP archives | `zipfile` (stdlib) | built-in, no install needed |
53
54 ## File Input Handling
55
56 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.
57
58 Common unsupported-in-chat but convertible formats: `.heic`, `.avif`, `.webp`, `.flac`, `.ogg`, `.m4a`, `.wma`, `.aiff`, `.epub`, `.parquet`, `.psd`, `.svg`, `.zip`
59
60 ## Data Formats
61
62 ```python
63
64 import pandas as pd, json, yaml, xmltodict
65
66 # --- CSV ↔ JSON ---
67
68 pd.read_csv("in.csv").to_json("out.json", orient="records", indent=2)
69
70 pd.read_json("in.json").to_csv("out.csv", index=False)
71
72 # --- CSV → Excel / Excel → CSV ---
73
74 pd.read_csv("in.csv").to_excel("out.xlsx", index=False, engine="openpyxl")
75
76 pd.read_excel("in.xlsx", sheet_name="Sheet1").to_csv("out.csv", index=False)
77
78 # All sheets: pd.read_excel("in.xlsx", sheet_name=None) → dict of DataFrames
79
80 # --- CSV → Parquet (columnar, compressed) ---
81
82 pd.read_csv("in.csv").to_parquet("out.parquet", engine="pyarrow", compression="snappy")
83
84 # --- YAML ↔ JSON ---
85
86 data = yaml.safe_load(open("in.yaml")) \# ALWAYS safe_load, never load()
87
88 json.dump(data, open("out.json", "w"), indent=2)
89
90 yaml.safe_dump(json.load(open("in.json")), open("out.yaml", "w"), sort_keys=False)
91
92 # --- XML ↔ JSON ---
93
94 data = xmltodict.parse(open("in.xml").read())
95
96 json.dump(data, open("out.json", "w"), indent=2)
97
98 open("out.xml", "w").write(xmltodict.unparse(data, pretty=True))
99
100 # --- JSONL (one JSON object per line) ---
101
102 pd.read_json("in.jsonl", lines=True).to_csv("out.csv", index=False)
103
104 ```
105
106 **Encoding gotchas:**
107
108 - `pd.read_csv("f.csv", encoding="utf-8-sig")` strips the BOM that Excel inserts
109 - Auto-detect: `import chardet; enc = chardet.detect(open("f.csv","rb").read())["encoding"]`
110
111 - CSV delimiter sniffing: `pd.read_csv("f.csv", sep=None, engine="python")`
112
113 **Nested JSON → flat CSV:**
114
115 ```python
116
117 pd.json_normalize(data, sep=".").to_csv("out.csv", index=False) \# {"a":{"b":1}} → column "a.b"
118
119 ```
120
121 ## Document Formats — pandoc is the Swiss Army knife
122
123 ```bash
124
125 # Markdown → PDF (requires LaTeX: apt install texlive-xetex)
126
127 pandoc input.md -o output.pdf --pdf-engine=xelatex
128
129 # Markdown → DOCX
130
131 pandoc input.md -o output.docx
132
133 # DOCX → Markdown (extracts images to ./media/)
134
135 pandoc input.docx -o output.md --extract-media=.
136
137 # HTML → Markdown
138
139 pandoc input.html -o output.md -t gfm
140
141 # Any → Any (pandoc supports ~40 formats)
142
143 pandoc -f docx -t rst input.docx -o output.rst
144
145 ```
146
147 ```python
148
149 # From Python
150
151 import pypandoc
152
153 pypandoc.convert_file("in.md", "docx", outputfile="out.docx")
154
155 ```
156
157 **Without pandoc (pure Python):**
158
159 ```python
160
161 # Markdown → HTML
162
163 import markdown
164
165 html = markdown.markdown(open("in.md").read(), extensions=["tables", "fenced_code", "toc"])
166
167 # HTML → Markdown
168
169 from markdownify import markdownify
170
171 md = markdownify(html, heading_style="ATX") \# ATX = \# headers, not underlines
172
173 ```
174
175 ## PDF Operations
176
177 ```python
178
179 # --- Extract text + tables ---
180
181 import pdfplumber
182
183 with pdfplumber.open("in.pdf") as pdf:
184
185 text = "\n".join(p.extract_text() or "" for p in pdf.pages)
186
187 tables = pdf.pages[0].extract_tables() \# list of list-of-rows
188
189 # --- PDF → images (one PNG per page) ---
190
191 from pdf2image import convert_from_path
192
193 for i, img in enumerate(convert_from_path("in.pdf", dpi=200)):
194
195 img.save(f"page_{i+1}.png")
196
197 # --- Merge / split / rotate ---
198
199 from pypdf import PdfReader, PdfWriter
200
201 writer = PdfWriter()
202
203 for path in ["a.pdf", "b.pdf"]:
204
205 for page in PdfReader(path).pages:
206
207 writer.add_page(page)
208
209 writer.write("merged.pdf")
210
211 # Extract pages 2–5
212
213 reader = PdfReader("in.pdf")
214
215 writer = PdfWriter()
216
217 for p in reader.pages[1:5]:
218
219 writer.add_page(p)
220
221 writer.write("pages_2-5.pdf")
222
223 ```
224
225 **PDF gotchas:**
226
227 - `pdf2image` needs `poppler-utils` installed system-wide (not a pip package)
228 - Scanned PDFs have no text layer — pdfplumber returns `None`. Use `pytesseract` OCR on pdf2image output.
229
230 - `PyPDF2` is deprecated → use `pypdf` (same API, maintained fork)
231
232 ## Image Formats
233
234 ```python
235
236 from PIL import Image
237
238 # --- Basic conversion ---
239
240 Image.open("in.png").convert("RGB").save("out.jpg", quality=90)
241
242 # convert("RGB") is REQUIRED: JPEG can't store alpha channel, will raise OSError
243
244 # --- WebP (best web format) ---
245
246 Image.open("in.jpg").save("out.webp", quality=85, method=6) \# method 0-6, 6=best compression
247
248 # --- AVIF (smallest, Pillow 11+) ---
249
250 Image.open("in.jpg").save("out.avif", quality=75)
251
252 # --- HEIC (iPhone photos) → JPG ---
253
254 from pillow_heif import register_heif_opener
255
256 register_heif_opener()
257
258 Image.open("in.heic").convert("RGB").save("out.jpg", quality=90)
259
260 # --- SVG → PNG ---
261
262 import cairosvg
263
264 cairosvg.svg2png(url="in.svg", write_to="out.png", output_width=1024)
265
266 # --- Batch convert directory ---
267
268 from pathlib import Path
269
270 for p in Path("imgs").glob("*.png"):
271
272 Image.open(p).convert("RGB").save(p.with_suffix(".jpg"), quality=85)
273
274 ```
275
276 **Image gotchas:**
277
278 - PNG → JPG: **must** `convert("RGB")` first or transparency crashes the save
279 - `quality` for PNG is meaningless (lossless) — use `optimize=True, compress_level=9`
280
281 - Pillow can't open `.svg` natively — use `cairosvg` or `svglib`
282 - GIF → MP4 is a video operation: `ffmpeg -i in.gif -pix_fmt yuv420p out.mp4`
283
284 ## Audio Formats
285
286 ```python
287
288 from pydub import AudioSegment
289
290 # --- MP3 ↔ WAV ---
291
292 AudioSegment.from_mp3("in.mp3").export("out.wav", format="wav")
293
294 AudioSegment.from_wav("in.wav").export("out.mp3", format="mp3", bitrate="192k")
295
296 # --- FLAC → MP3 ---
297
298 AudioSegment.from_file("in.flac", format="flac").export("out.mp3", format="mp3", bitrate="320k")
299
300 # --- OGG → MP3 ---
301
302 AudioSegment.from_ogg("in.ogg").export("out.mp3", format="mp3", bitrate="192k")
303
304 # --- M4A / AAC → MP3 ---
305
306 AudioSegment.from_file("in.m4a", format="m4a").export("out.mp3", format="mp3", bitrate="256k")
307
308 # --- Any → Any (pydub supports mp3, wav, ogg, flac, m4a, aac, wma, aiff) ---
309
310 AudioSegment.from_file("in.wma", format="wma").export("out.flac", format="flac")
311
312 # --- Trim audio (first 30 seconds) ---
313
314 audio = AudioSegment.from_file("in.mp3")
315
316 audio[:30000].export("first_30s.mp3", format="mp3") \# milliseconds
317
318 # --- Merge / concatenate ---
319
320 combined = AudioSegment.from_file("a.mp3") + AudioSegment.from_file("b.mp3")
321
322 combined.export("merged.mp3", format="mp3")
323
324 # --- Adjust volume ---
325
326 audio = AudioSegment.from_file("in.mp3")
327
328 louder = audio + 6 \# +6 dB
329
330 quieter = audio - 6 \# -6 dB
331
332 louder.export("louder.mp3", format="mp3")
333
334 # --- Get audio info ---
335
336 audio = AudioSegment.from_file("in.mp3")
337
338 print(f"Duration: {len(audio)/1000:.1f}s, Channels: {audio.channels}, "
339
340 f"Sample rate: {audio.frame_rate}Hz, Sample width: {audio.sample_width*8}bit")
341
342 # --- Batch convert directory ---
343
344 from pathlib import Path
345
346 for p in Path("audio").glob("*.wav"):
347
348 AudioSegment.from_wav(str(p)).export(p.with_suffix(".mp3"), format="mp3", bitrate="192k")
349
350 ```
351
352 **Audio gotchas:**
353
354 - `pydub` requires `ffmpeg` installed system-wide for non-WAV formats
355 - Bitrate options: "128k" (small/low quality), "192k" (balanced), "256k" (high), "320k" (max for MP3)
356
357 - WAV files are uncompressed — expect 10x larger file sizes than MP3
358 - For sample rate conversion: `audio.set_frame_rate(44100).export("out.wav", format="wav")`
359
360 - Mono to stereo: `audio.set_channels(2)` / Stereo to mono: `audio.set_channels(1)`
361
362 ## ZIP Archives
363
364 ```python
365
366 import zipfile
367
368 from pathlib import Path
369
370 # --- Create ZIP from files ---
371
372 with zipfile.ZipFile("archive.zip", "w", zipfile.ZIP_DEFLATED) as zf:
373
374 zf.write("file1.txt")
375
376 zf.write("file2.csv")
377
378 zf.write("images/photo.jpg")
379
380 # --- Create ZIP from entire directory ---
381
382 import shutil
383
384 shutil.make_archive("archive", "zip", root_dir="my_folder") \# creates archive.zip
385
386 # --- Extract all ---
387
388 with zipfile.ZipFile("archive.zip", "r") as zf:
389
390 zf.extractall("output_dir")
391
392 # --- Extract single file ---
393
394 with zipfile.ZipFile("archive.zip", "r") as zf:
395
396 zf.extract("file1.txt", "output_dir")
397
398 # --- List contents without extracting ---
399
400 with zipfile.ZipFile("archive.zip", "r") as zf:
401
402 for info in zf.infolist():
403
404 print(f"{info.filename} {info.file_size:,} bytes {info.compress_size:,} compressed")
405
406 # --- Read file from ZIP without extracting ---
407
408 with zipfile.ZipFile("archive.zip", "r") as zf:
409
410 content = zf.read("file1.txt").decode("utf-8")
411
412 # --- Add files to existing ZIP ---
413
414 with zipfile.ZipFile("archive.zip", "a") as zf:
415
416 zf.write("new_file.txt")
417
418 # --- Create ZIP with password (read-only, use pyzipper for write) ---
419
420 # pip install pyzipper
421
422 import pyzipper
423
424 with pyzipper.AESZipFile("secure.zip", "w", compression=pyzipper.ZIP_DEFLATED,
425
426 encryption=pyzipper.WZ_AES) as zf:
427
428 zf.setpassword(b"my_password")
429
430 zf.write("secret.txt")
431
432 # --- Batch: ZIP all PDFs in a directory ---
433
434 with zipfile.ZipFile("all_pdfs.zip", "w", zipfile.ZIP_DEFLATED) as zf:
435
436 for p in Path(".").glob("**/*.pdf"):
437
438 zf.write(p)
439
440 ```
441
442 **ZIP gotchas:**
443
444 - `zipfile` is in Python's standard library — no install needed
445 - Always use `ZIP_DEFLATED` compression (default is `ZIP_STORED` = no compression)
446
447 - For password-protected ZIPs, stdlib `zipfile` can only read (not write) — use `pyzipper` for encrypted writes
448 - Max file size in standard ZIP is 4 GB; use `allowZip64=True` (default in Python 3) for larger files
449
450 - `shutil.make_archive` is the simplest way to ZIP an entire directory tree
451
452 ## EPUB Formats
453
454 ```python
455
456 # --- EPUB → other formats (via pandoc) ---
457
458 # pandoc is the easiest way to convert EPUB
459
460 import pypandoc
461
462 # EPUB → Markdown
463
464 pypandoc.convert_file("in.epub", "markdown", outputfile="out.md")
465
466 # EPUB → HTML
467
468 pypandoc.convert_file("in.epub", "html", outputfile="out.html")
469
470 # EPUB → DOCX
471
472 pypandoc.convert_file("in.epub", "docx", outputfile="out.docx")
473
474 # EPUB → plain text
475
476 pypandoc.convert_file("in.epub", "plain", outputfile="out.txt")
477
478 # --- Other formats → EPUB ---
479
480 # Markdown → EPUB
481
482 pypandoc.convert_file("in.md", "epub", outputfile="out.epub",
483
484 extra_args=["--metadata", "title=My Book"])
485
486 # HTML → EPUB
487
488 pypandoc.convert_file("in.html", "epub", outputfile="out.epub",
489
490 extra_args=["--metadata", "title=My Book"])
491
492 # DOCX → EPUB
493
494 pypandoc.convert_file("in.docx", "epub", outputfile="out.epub")
495
496 ```
497
498 ```bash
499
500 # --- CLI equivalents ---
501
502 pandoc in.epub -o out.md
503
504 pandoc in.epub -o out.pdf --pdf-engine=xelatex
505
506 pandoc in.md -o out.epub --metadata title="My Book"
507
508 pandoc in.html -o out.epub --metadata title="My Book" --epub-cover-image=cover.jpg
509
510 ```
511
512 ```python
513
514 # --- Pure Python: read/write EPUB with ebooklib ---
515
516 from ebooklib import epub
517
518 # Read EPUB
519
520 book = epub.read_epub("in.epub")
521
522 for item in book.get_items_of_type(9): \# 9 = ITEM_DOCUMENT (HTML chapters)
523
524 print(item.get_name())
525
526 html_content = item.get_content().decode("utf-8")
527
528 # Create EPUB from scratch
529
530 book = epub.EpubBook()
531
532 book.set_identifier("id123")
533
534 book.set_title("My Book")
535
536 book.set_language("en")
537
538 book.add_author("Author Name")
539
540 ch1 = epub.EpubHtml(title="Chapter 1", file_name="ch1.xhtml", lang="en")
541
542 ch1.content = "<h1>Chapter 1</h1><p>Hello world.</p>"
543
544 book.add_item(ch1)
545
546 book.toc = [epub.Link("ch1.xhtml", "Chapter 1", "ch1")]
547
548 book.add_item(epub.EpubNcx())
549
550 book.add_item(epub.EpubNav())
551
552 book.spine = ["nav", ch1]
553
554 epub.write_epub("out.epub", book)
555
556 ```
557
558 **EPUB gotchas:**
559
560 - `pandoc` is the simplest for format-to-format EPUB conversion
561 - Always add `--metadata title="..."` when creating EPUB — readers require a title
562
563 - EPUB is essentially a ZIP of HTML files — `ebooklib` gives you fine-grained control
564 - For EPUB → PDF, pandoc needs a LaTeX engine (`texlive-xetex`)
565
566 - Cover images: use `--epub-cover-image=cover.jpg` with pandoc
567
568 ## HTML to PDF
569
570 ```python
571
572 # --- weasyprint (best CSS support, no browser needed) ---
573
574 from weasyprint import HTML
575
576 # Simple file conversion
577
578 HTML("in.html").write_pdf("out.pdf")
579
580 # From URL
581
582 HTML("https://example.com").write_pdf("page.pdf")
583
584 # From HTML string
585
586 HTML(string="<h1>Hello</h1><p>World</p>").write_pdf("out.pdf")
587
588 # With custom CSS
589
590 HTML("in.html").write_pdf("out.pdf", stylesheets=["custom.css"])
591
592 # With page size and margins
593
594 from weasyprint import CSS
595
596 HTML("in.html").write_pdf("out.pdf", stylesheets=[
597
598 CSS(string="@page { size: A4; margin: 2cm; }")
599
600 ])
601
602 # Landscape orientation
603
604 HTML("in.html").write_pdf("out.pdf", stylesheets=[
605
606 CSS(string="@page { size: A4 landscape; margin: 1.5cm; }")
607
608 ])
609
610 ```
611
612 ```bash
613
614 # --- CLI alternatives ---
615
616 # pandoc (simpler, less CSS fidelity)
617
618 pandoc in.html -o out.pdf --pdf-engine=xelatex
619
620 # weasyprint CLI
621
622 weasyprint in.html out.pdf
623
624 weasyprint https://example.com page.pdf
625
626 ```
627
628 **HTML to PDF gotchas:**
629
630 - `weasyprint` supports CSS3 including flexbox, grid, and `@page` rules — best for styled documents
631 - `weasyprint` does NOT run JavaScript — for JS-heavy pages, use `playwright` or `pyppeteer` instead
632
633 - For JS-rendered pages: `playwright` → `page.pdf()` is the most reliable option
634 - pandoc HTML → PDF goes through LaTeX, so complex CSS layouts may not render correctly
635
636 - Large HTML files with many images: use `HTML(filename="in.html", base_url=".")` so relative image paths resolve
637
638 ## GIF Creation
639
640 ```python
641
642 from PIL import Image
643
644 import imageio.v3 as iio
645
646 from pathlib import Path
647
648 # --- Images → animated GIF (Pillow) ---
649
650 frames = [Image.open(f"frame_{i}.png") for i in range(10)]
651
652 frames[0].save("out.gif", save_all=True, append_images=frames[1:],
653
654 duration=100, loop=0) \# duration in ms, loop=0 means infinite
655
656 # --- Images → GIF with optimization ---
657
658 frames = [Image.open(f"frame_{i}.png").convert("RGBA") for i in range(10)]
659
660 frames[0].save("out.gif", save_all=True, append_images=frames[1:],
661
662 duration=100, loop=0, optimize=True)
663
664 # --- Directory of images → GIF ---
665
666 frame_paths = sorted(Path("frames").glob("*.png"))
667
668 frames = [Image.open(p) for p in frame_paths]
669
670 frames[0].save("out.gif", save_all=True, append_images=frames[1:],
671
672 duration=100, loop=0)
673
674 # --- GIF → individual frames ---
675
676 gif = Image.open("in.gif")
677
678 for i in range(gif.n_frames):
679
680 gif.seek(i)
681
682 gif.save(f"frame_{i}.png")
683
684 # --- Resize GIF ---
685
686 gif = Image.open("in.gif")
687
688 resized_frames = []
689
690 for i in range(gif.n_frames):
691
692 gif.seek(i)
693
694 resized_frames.append(gif.copy().resize((320, 240), Image.LANCZOS))
695
696 resized_frames[0].save("small.gif", save_all=True, append_images=resized_frames[1:],
697
698 duration=gif.info.get("duration", 100), loop=0)
699
700 # --- Video → GIF (imageio + ffmpeg) ---
701
702 import imageio.v3 as iio
703
704 frames = iio.imread("in.mp4", plugin="pyav")
705
706 iio.imwrite("out.gif", frames, plugin="pillow", duration=40, loop=0)
707
708 # --- GIF → MP4 (ffmpeg CLI, much smaller file) ---
709
710 # ffmpeg -i in.gif -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" out.mp4
711
712 ```
713
714 **GIF gotchas:**
715
716 - GIF is limited to 256 colors per frame — complex images lose quality
717 - Use `optimize=True` to reduce file size, but large GIFs are still huge compared to MP4
718
719 - `duration` is per-frame in milliseconds (100ms = 10 FPS, 40ms = 25 FPS)
720 - `loop=0` means infinite loop; `loop=1` plays once then stops
721
722 - For video → GIF, consider downscaling first — full-resolution GIFs are enormous
723 - Pillow GIF output doesn't support transparency well — use `imageio` for better results
724
725 - For best quality: create GIF from video with ffmpeg: `ffmpeg -i in.mp4 -vf "fps=15,scale=480:-1" out.gif`
726
727 ## PDF to SVG
728
729 ```python
730
731 # --- PyMuPDF (fitz) — best quality, vector-preserving ---
732
733 import fitz \# pip install pymupdf
734
735 # Single page
736
737 doc = fitz.open("in.pdf")
738
739 page = doc[0]
740
741 svg_text = page.get_svg_image()
742
743 with open("page_1.svg", "w") as f:
744
745 f.write(svg_text)
746
747 # All pages
748
749 doc = fitz.open("in.pdf")
750
751 for i, page in enumerate(doc):
752
753 svg_text = page.get_svg_image()
754
755 with open(f"page_{i+1}.svg", "w") as f:
756
757 f.write(svg_text)
758
759 doc.close()
760
761 # With custom resolution (matrix scales the output)
762
763 doc = fitz.open("in.pdf")
764
765 page = doc[0]
766
767 mat = fitz.Matrix(2, 2) \# 2x scale for higher detail
768
769 svg_text = page.get_svg_image(matrix=mat)
770
771 with open("page_hires.svg", "w") as f:
772
773 f.write(svg_text)
774
775 ```
776
777 ```bash
778
779 # --- CLI alternatives ---
780
781 # pdf2svg (if installed)
782
783 pdf2svg in.pdf out.svg 1 \# page number
784
785 # Inkscape CLI
786
787 inkscape in.pdf --export-type=svg --export-filename=out.svg
788
789 # pdftocairo (from poppler-utils)
790
791 pdftocairo -svg in.pdf out.svg
792
793 ```
794
795 **PDF to SVG gotchas:**
796
797 - `pymupdf` (imported as `fitz`) produces true vector SVGs — text stays as text, paths stay as paths
798 - Scanned PDFs produce SVGs with embedded raster images (no vector data to extract)
799
800 - Large PDFs with complex graphics produce very large SVG files
801 - `pdf2svg` CLI tool is simple but must be installed separately (`apt install pdf2svg`)
802
803 - For rasterized SVG (simpler but not truly vector): render PDF to PNG first, then embed in SVG
804
805 ## Validation
806
807 Always verify output:
808
809 ```python
810
811 # Row count parity
812
813 assert len(pd.read_csv("out.csv")) == len(pd.read_json("in.json"))
814
815 # JSON well-formed
816
817 json.load(open("out.json"))
818
819 # Image opens
820
821 Image.open("out.jpg").verify()
822
823 ```