[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71K8V4GDMF522ZQBHJG8WC0":3,"skill-same:01M71K8V4GDMF522ZQBHJG8WC0":76},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":41,"linkOnly":42,"markdownContent":43,"markdownBody":44,"frontMatter":45,"refName":46,"commitSha":47,"skillDirectory":48,"forksCount":49,"repositoryHtmlUrl":50,"repositoryHomepageUrl":51,"ownerHtmlUrl":52,"ownerBlogUrl":53,"ownerDescription":54,"updatedAt":55,"install":56,"resources":69},"01M71K8V4GDMF522ZQBHJG8WC0","file-converter",null,"Convert, merge, split, and compress files across formats — documents, images, audio, and more.","clawdsolana\u002FOpenClawd","clawdsolana","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F279036008?v=4","01M71JTVF9FNGKE2SVJRPTQM31","OpenClawd","01M71JTVGCKWBSS8ZYC1HYWVA5",2,"MIT","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsecondary_skills\u002Ffile-converter\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000002","文档处理","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体",[27,28,29,30,31,32,33,34,35,36,37,38,39,40],"file","file converter","converter","images","Audio","more","files","documents","split","convert","across","merge","formats","compress","2026-11-10 10:12:30",false,"---\nname: file-converter\ndescription: Convert, merge, split, and compress files across formats — documents, images, audio, and more.\n---\n\n# File Converter\n\n## OpenClawd Operator Adaptation\n\nRun this skill as part of the OpenClawd operator deck. Preserve the skill-specific workflow below, but frame outputs for Solana-native agents when relevant: prefer OpenClawd language, note whether the work can support autonomous agent operations, and keep financial, legal, tax, hiring, medical, or other regulated outputs informational unless the skill already requires a stricter disclaimer. Use the Llobster Legend persona only as light operator framing; do not let branding override accuracy, safety, or the user's stated domain.\n\n\nConvert between data, document, image, audio formats, and ZIP archives. One-liners for each conversion pair.\n\n## Tool Map\n\n| Domain | Tool | Install |\n\n|---|---|---|\n\n| CSV\u002FJSON\u002FExcel\u002FParquet | `pandas` | `pip install pandas openpyxl pyarrow` |\n\n| YAML | `pyyaml` | `pip install pyyaml` |\n\n| XML ↔ dict | `xmltodict` | `pip install xmltodict` |\n\n| Any doc format ↔ any | **pandoc** (CLI) | `apt install pandoc` or `pip install pypandoc_binary` |\n\n| Markdown → HTML | `markdown` | `pip install markdown` |\n\n| HTML → Markdown | `markdownify` | `pip install markdownify` |\n\n| .docx read\u002Fwrite | `python-docx` | `pip install python-docx` |\n\n| PDF → text\u002Ftables | `pdfplumber` | `pip install pdfplumber` |\n\n| PDF → images | `pdf2image` | `pip install pdf2image` + `apt install poppler-utils` |\n\n| PDF manipulation | `pypdf` | `pip install pypdf` |\n\n| Images | `Pillow` | `pip install Pillow` |\n\n| SVG → PNG | `cairosvg` | `pip install cairosvg` |\n\n| HEIC → JPG | `pillow-heif` | `pip install pillow-heif` |\n\n| Audio formats | `pydub` | `pip install pydub` + `apt install ffmpeg` |\n\n| EPUB ↔ other | **pandoc** or `ebooklib` | `pip install ebooklib` |\n\n| HTML → PDF | `weasyprint` | `pip install weasyprint` |\n\n| GIF creation | `Pillow` or `imageio` | `pip install imageio[ffmpeg]` |\n\n| PDF → SVG | `pdf2image` + `potrace` or `pymupdf` | `pip install pymupdf` |\n\n| ZIP archives | `zipfile` (stdlib) | built-in, no install needed |\n\n## File Input Handling\n\nWhen 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\u002F` or the project root. Always check both locations. If the file isn't found, ask the user where they saved it.\n\nCommon unsupported-in-chat but convertible formats: `.heic`, `.avif`, `.webp`, `.flac`, `.ogg`, `.m4a`, `.wma`, `.aiff`, `.epub`, `.parquet`, `.psd`, `.svg`, `.zip`\n\n## Data Formats\n\n```python\n\nimport pandas as pd, json, yaml, xmltodict\n\n# --- CSV ↔ JSON ---\n\npd.read_csv(\"in.csv\").to_json(\"out.json\", orient=\"records\", indent=2)\n\npd.read_json(\"in.json\").to_csv(\"out.csv\", index=False)\n\n# --- CSV → Excel \u002F Excel → CSV ---\n\npd.read_csv(\"in.csv\").to_excel(\"out.xlsx\", index=False, engine=\"openpyxl\")\n\npd.read_excel(\"in.xlsx\", sheet_name=\"Sheet1\").to_csv(\"out.csv\", index=False)\n\n# All sheets: pd.read_excel(\"in.xlsx\", sheet_name=None) → dict of DataFrames\n\n# --- CSV → Parquet (columnar, compressed) ---\n\npd.read_csv(\"in.csv\").to_parquet(\"out.parquet\", engine=\"pyarrow\", compression=\"snappy\")\n\n# --- YAML ↔ JSON ---\n\ndata = yaml.safe_load(open(\"in.yaml\")) \\# ALWAYS safe_load, never load()\n\njson.dump(data, open(\"out.json\", \"w\"), indent=2)\n\nyaml.safe_dump(json.load(open(\"in.json\")), open(\"out.yaml\", \"w\"), sort_keys=False)\n\n# --- XML ↔ JSON ---\n\ndata = xmltodict.parse(open(\"in.xml\").read())\n\njson.dump(data, open(\"out.json\", \"w\"), indent=2)\n\nopen(\"out.xml\", \"w\").write(xmltodict.unparse(data, pretty=True))\n\n# --- JSONL (one JSON object per line) ---\n\npd.read_json(\"in.jsonl\", lines=True).to_csv(\"out.csv\", index=False)\n\n```\n\n**Encoding gotchas:**\n\n- `pd.read_csv(\"f.csv\", encoding=\"utf-8-sig\")` strips the BOM that Excel inserts\n- Auto-detect: `import chardet; enc = chardet.detect(open(\"f.csv\",\"rb\").read())[\"encoding\"]`\n\n- CSV delimiter sniffing: `pd.read_csv(\"f.csv\", sep=None, engine=\"python\")`\n\n**Nested JSON → flat CSV:**\n\n```python\n\npd.json_normalize(data, sep=\".\").to_csv(\"out.csv\", index=False) \\# {\"a\":{\"b\":1}} → column \"a.b\"\n\n```\n\n## Document Formats — pandoc is the Swiss Army knife\n\n```bash\n\n# Markdown → PDF (requires LaTeX: apt install texlive-xetex)\n\npandoc input.md -o output.pdf --pdf-engine=xelatex\n\n# Markdown → DOCX\n\npandoc input.md -o output.docx\n\n# DOCX → Markdown (extracts images to .\u002Fmedia\u002F)\n\npandoc input.docx -o output.md --extract-media=.\n\n# HTML → Markdown\n\npandoc input.html -o output.md -t gfm\n\n# Any → Any (pandoc supports ~40 formats)\n\npandoc -f docx -t rst input.docx -o output.rst\n\n```\n\n```python\n\n# From Python\n\nimport pypandoc\n\npypandoc.convert_file(\"in.md\", \"docx\", outputfile=\"out.docx\")\n\n```\n\n**Without pandoc (pure Python):**\n\n```python\n\n# Markdown → HTML\n\nimport markdown\n\nhtml = markdown.markdown(open(\"in.md\").read(), extensions=[\"tables\", \"fenced_code\", \"toc\"])\n\n# HTML → Markdown\n\nfrom markdownify import markdownify\n\nmd = markdownify(html, heading_style=\"ATX\") \\# ATX = \\# headers, not underlines\n\n```\n\n## PDF Operations\n\n```python\n\n# --- Extract text + tables ---\n\nimport pdfplumber\n\nwith pdfplumber.open(\"in.pdf\") as pdf:\n\ntext = \"\\n\".join(p.extract_text() or \"\" for p in pdf.pages)\n\ntables = pdf.pages[0].extract_tables() \\# list of list-of-rows\n\n# --- PDF → images (one PNG per page) ---\n\nfrom pdf2image import convert_from_path\n\nfor i, img in enumerate(convert_from_path(\"in.pdf\", dpi=200)):\n\nimg.save(f\"page_{i+1}.png\")\n\n# --- Merge \u002F split \u002F rotate ---\n\nfrom pypdf import PdfReader, PdfWriter\n\nwriter = PdfWriter()\n\nfor path in [\"a.pdf\", \"b.pdf\"]:\n\nfor page in PdfReader(path).pages:\n\nwriter.add_page(page)\n\nwriter.write(\"merged.pdf\")\n\n# Extract pages 2–5\n\nreader = PdfReader(\"in.pdf\")\n\nwriter = PdfWriter()\n\nfor p in reader.pages[1:5]:\n\nwriter.add_page(p)\n\nwriter.write(\"pages_2-5.pdf\")\n\n```\n\n**PDF gotchas:**\n\n- `pdf2image` needs `poppler-utils` installed system-wide (not a pip package)\n- Scanned PDFs have no text layer — pdfplumber returns `None`. Use `pytesseract` OCR on pdf2image output.\n\n- `PyPDF2` is deprecated → use `pypdf` (same API, maintained fork)\n\n## Image Formats\n\n```python\n\nfrom PIL import Image\n\n# --- Basic conversion ---\n\nImage.open(\"in.png\").convert(\"RGB\").save(\"out.jpg\", quality=90)\n\n# convert(\"RGB\") is REQUIRED: JPEG can't store alpha channel, will raise OSError\n\n# --- WebP (best web format) ---\n\nImage.open(\"in.jpg\").save(\"out.webp\", quality=85, method=6) \\# method 0-6, 6=best compression\n\n# --- AVIF (smallest, Pillow 11+) ---\n\nImage.open(\"in.jpg\").save(\"out.avif\", quality=75)\n\n# --- HEIC (iPhone photos) → JPG ---\n\nfrom pillow_heif import register_heif_opener\n\nregister_heif_opener()\n\nImage.open(\"in.heic\").convert(\"RGB\").save(\"out.jpg\", quality=90)\n\n# --- SVG → PNG ---\n\nimport cairosvg\n\ncairosvg.svg2png(url=\"in.svg\", write_to=\"out.png\", output_width=1024)\n\n# --- Batch convert directory ---\n\nfrom pathlib import Path\n\nfor p in Path(\"imgs\").glob(\"*.png\"):\n\nImage.open(p).convert(\"RGB\").save(p.with_suffix(\".jpg\"), quality=85)\n\n```\n\n**Image gotchas:**\n\n- PNG → JPG: **must** `convert(\"RGB\")` first or transparency crashes the save\n- `quality` for PNG is meaningless (lossless) — use `optimize=True, compress_level=9`\n\n- Pillow can't open `.svg` natively — use `cairosvg` or `svglib`\n- GIF → MP4 is a video operation: `ffmpeg -i in.gif -pix_fmt yuv420p out.mp4`\n\n## Audio Formats\n\n```python\n\nfrom pydub import AudioSegment\n\n# --- MP3 ↔ WAV ---\n\nAudioSegment.from_mp3(\"in.mp3\").export(\"out.wav\", format=\"wav\")\n\nAudioSegment.from_wav(\"in.wav\").export(\"out.mp3\", format=\"mp3\", bitrate=\"192k\")\n\n# --- FLAC → MP3 ---\n\nAudioSegment.from_file(\"in.flac\", format=\"flac\").export(\"out.mp3\", format=\"mp3\", bitrate=\"320k\")\n\n# --- OGG → MP3 ---\n\nAudioSegment.from_ogg(\"in.ogg\").export(\"out.mp3\", format=\"mp3\", bitrate=\"192k\")\n\n# --- M4A \u002F AAC → MP3 ---\n\nAudioSegment.from_file(\"in.m4a\", format=\"m4a\").export(\"out.mp3\", format=\"mp3\", bitrate=\"256k\")\n\n# --- Any → Any (pydub supports mp3, wav, ogg, flac, m4a, aac, wma, aiff) ---\n\nAudioSegment.from_file(\"in.wma\", format=\"wma\").export(\"out.flac\", format=\"flac\")\n\n# --- Trim audio (first 30 seconds) ---\n\naudio = AudioSegment.from_file(\"in.mp3\")\n\naudio[:30000].export(\"first_30s.mp3\", format=\"mp3\") \\# milliseconds\n\n# --- Merge \u002F concatenate ---\n\ncombined = AudioSegment.from_file(\"a.mp3\") + AudioSegment.from_file(\"b.mp3\")\n\ncombined.export(\"merged.mp3\", format=\"mp3\")\n\n# --- Adjust volume ---\n\naudio = AudioSegment.from_file(\"in.mp3\")\n\nlouder = audio + 6 \\# +6 dB\n\nquieter = audio - 6 \\# -6 dB\n\nlouder.export(\"louder.mp3\", format=\"mp3\")\n\n# --- Get audio info ---\n\naudio = AudioSegment.from_file(\"in.mp3\")\n\nprint(f\"Duration: {len(audio)\u002F1000:.1f}s, Channels: {audio.channels}, \"\n\nf\"Sample rate: {audio.frame_rate}Hz, Sample width: {audio.sample_width*8}bit\")\n\n# --- Batch convert directory ---\n\nfrom pathlib import Path\n\nfor p in Path(\"audio\").glob(\"*.wav\"):\n\nAudioSegment.from_wav(str(p)).export(p.with_suffix(\".mp3\"), format=\"mp3\", bitrate=\"192k\")\n\n```\n\n**Audio gotchas:**\n\n- `pydub` requires `ffmpeg` installed system-wide for non-WAV formats\n- Bitrate options: \"128k\" (small\u002Flow quality), \"192k\" (balanced), \"256k\" (high), \"320k\" (max for MP3)\n\n- WAV files are uncompressed — expect 10x larger file sizes than MP3\n- For sample rate conversion: `audio.set_frame_rate(44100).export(\"out.wav\", format=\"wav\")`\n\n- Mono to stereo: `audio.set_channels(2)` \u002F Stereo to mono: `audio.set_channels(1)`\n\n## ZIP Archives\n\n```python\n\nimport zipfile\n\nfrom pathlib import Path\n\n# --- Create ZIP from files ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"w\", zipfile.ZIP_DEFLATED) as zf:\n\nzf.write(\"file1.txt\")\n\nzf.write(\"file2.csv\")\n\nzf.write(\"images\u002Fphoto.jpg\")\n\n# --- Create ZIP from entire directory ---\n\nimport shutil\n\nshutil.make_archive(\"archive\", \"zip\", root_dir=\"my_folder\") \\# creates archive.zip\n\n# --- Extract all ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\nzf.extractall(\"output_dir\")\n\n# --- Extract single file ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\nzf.extract(\"file1.txt\", \"output_dir\")\n\n# --- List contents without extracting ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\nfor info in zf.infolist():\n\nprint(f\"{info.filename} {info.file_size:,} bytes {info.compress_size:,} compressed\")\n\n# --- Read file from ZIP without extracting ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\ncontent = zf.read(\"file1.txt\").decode(\"utf-8\")\n\n# --- Add files to existing ZIP ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"a\") as zf:\n\nzf.write(\"new_file.txt\")\n\n# --- Create ZIP with password (read-only, use pyzipper for write) ---\n\n# pip install pyzipper\n\nimport pyzipper\n\nwith pyzipper.AESZipFile(\"secure.zip\", \"w\", compression=pyzipper.ZIP_DEFLATED,\n\nencryption=pyzipper.WZ_AES) as zf:\n\nzf.setpassword(b\"my_password\")\n\nzf.write(\"secret.txt\")\n\n# --- Batch: ZIP all PDFs in a directory ---\n\nwith zipfile.ZipFile(\"all_pdfs.zip\", \"w\", zipfile.ZIP_DEFLATED) as zf:\n\nfor p in Path(\".\").glob(\"**\u002F*.pdf\"):\n\nzf.write(p)\n\n```\n\n**ZIP gotchas:**\n\n- `zipfile` is in Python's standard library — no install needed\n- Always use `ZIP_DEFLATED` compression (default is `ZIP_STORED` = no compression)\n\n- For password-protected ZIPs, stdlib `zipfile` can only read (not write) — use `pyzipper` for encrypted writes\n- Max file size in standard ZIP is 4 GB; use `allowZip64=True` (default in Python 3) for larger files\n\n- `shutil.make_archive` is the simplest way to ZIP an entire directory tree\n\n## EPUB Formats\n\n```python\n\n# --- EPUB → other formats (via pandoc) ---\n\n# pandoc is the easiest way to convert EPUB\n\nimport pypandoc\n\n# EPUB → Markdown\n\npypandoc.convert_file(\"in.epub\", \"markdown\", outputfile=\"out.md\")\n\n# EPUB → HTML\n\npypandoc.convert_file(\"in.epub\", \"html\", outputfile=\"out.html\")\n\n# EPUB → DOCX\n\npypandoc.convert_file(\"in.epub\", \"docx\", outputfile=\"out.docx\")\n\n# EPUB → plain text\n\npypandoc.convert_file(\"in.epub\", \"plain\", outputfile=\"out.txt\")\n\n# --- Other formats → EPUB ---\n\n# Markdown → EPUB\n\npypandoc.convert_file(\"in.md\", \"epub\", outputfile=\"out.epub\",\n\nextra_args=[\"--metadata\", \"title=My Book\"])\n\n# HTML → EPUB\n\npypandoc.convert_file(\"in.html\", \"epub\", outputfile=\"out.epub\",\n\nextra_args=[\"--metadata\", \"title=My Book\"])\n\n# DOCX → EPUB\n\npypandoc.convert_file(\"in.docx\", \"epub\", outputfile=\"out.epub\")\n\n```\n\n```bash\n\n# --- CLI equivalents ---\n\npandoc in.epub -o out.md\n\npandoc in.epub -o out.pdf --pdf-engine=xelatex\n\npandoc in.md -o out.epub --metadata title=\"My Book\"\n\npandoc in.html -o out.epub --metadata title=\"My Book\" --epub-cover-image=cover.jpg\n\n```\n\n```python\n\n# --- Pure Python: read\u002Fwrite EPUB with ebooklib ---\n\nfrom ebooklib import epub\n\n# Read EPUB\n\nbook = epub.read_epub(\"in.epub\")\n\nfor item in book.get_items_of_type(9): \\# 9 = ITEM_DOCUMENT (HTML chapters)\n\nprint(item.get_name())\n\nhtml_content = item.get_content().decode(\"utf-8\")\n\n# Create EPUB from scratch\n\nbook = epub.EpubBook()\n\nbook.set_identifier(\"id123\")\n\nbook.set_title(\"My Book\")\n\nbook.set_language(\"en\")\n\nbook.add_author(\"Author Name\")\n\nch1 = epub.EpubHtml(title=\"Chapter 1\", file_name=\"ch1.xhtml\", lang=\"en\")\n\nch1.content = \"\u003Ch1>Chapter 1\u003C\u002Fh1>\u003Cp>Hello world.\u003C\u002Fp>\"\n\nbook.add_item(ch1)\n\nbook.toc = [epub.Link(\"ch1.xhtml\", \"Chapter 1\", \"ch1\")]\n\nbook.add_item(epub.EpubNcx())\n\nbook.add_item(epub.EpubNav())\n\nbook.spine = [\"nav\", ch1]\n\nepub.write_epub(\"out.epub\", book)\n\n```\n\n**EPUB gotchas:**\n\n- `pandoc` is the simplest for format-to-format EPUB conversion\n- Always add `--metadata title=\"...\"` when creating EPUB — readers require a title\n\n- EPUB is essentially a ZIP of HTML files — `ebooklib` gives you fine-grained control\n- For EPUB → PDF, pandoc needs a LaTeX engine (`texlive-xetex`)\n\n- Cover images: use `--epub-cover-image=cover.jpg` with pandoc\n\n## HTML to PDF\n\n```python\n\n# --- weasyprint (best CSS support, no browser needed) ---\n\nfrom weasyprint import HTML\n\n# Simple file conversion\n\nHTML(\"in.html\").write_pdf(\"out.pdf\")\n\n# From URL\n\nHTML(\"https:\u002F\u002Fexample.com\").write_pdf(\"page.pdf\")\n\n# From HTML string\n\nHTML(string=\"\u003Ch1>Hello\u003C\u002Fh1>\u003Cp>World\u003C\u002Fp>\").write_pdf(\"out.pdf\")\n\n# With custom CSS\n\nHTML(\"in.html\").write_pdf(\"out.pdf\", stylesheets=[\"custom.css\"])\n\n# With page size and margins\n\nfrom weasyprint import CSS\n\nHTML(\"in.html\").write_pdf(\"out.pdf\", stylesheets=[\n\nCSS(string=\"@page { size: A4; margin: 2cm; }\")\n\n])\n\n# Landscape orientation\n\nHTML(\"in.html\").write_pdf(\"out.pdf\", stylesheets=[\n\nCSS(string=\"@page { size: A4 landscape; margin: 1.5cm; }\")\n\n])\n\n```\n\n```bash\n\n# --- CLI alternatives ---\n\n# pandoc (simpler, less CSS fidelity)\n\npandoc in.html -o out.pdf --pdf-engine=xelatex\n\n# weasyprint CLI\n\nweasyprint in.html out.pdf\n\nweasyprint https:\u002F\u002Fexample.com page.pdf\n\n```\n\n**HTML to PDF gotchas:**\n\n- `weasyprint` supports CSS3 including flexbox, grid, and `@page` rules — best for styled documents\n- `weasyprint` does NOT run JavaScript — for JS-heavy pages, use `playwright` or `pyppeteer` instead\n\n- For JS-rendered pages: `playwright` → `page.pdf()` is the most reliable option\n- pandoc HTML → PDF goes through LaTeX, so complex CSS layouts may not render correctly\n\n- Large HTML files with many images: use `HTML(filename=\"in.html\", base_url=\".\")` so relative image paths resolve\n\n## GIF Creation\n\n```python\n\nfrom PIL import Image\n\nimport imageio.v3 as iio\n\nfrom pathlib import Path\n\n# --- Images → animated GIF (Pillow) ---\n\nframes = [Image.open(f\"frame_{i}.png\") for i in range(10)]\n\nframes[0].save(\"out.gif\", save_all=True, append_images=frames[1:],\n\nduration=100, loop=0) \\# duration in ms, loop=0 means infinite\n\n# --- Images → GIF with optimization ---\n\nframes = [Image.open(f\"frame_{i}.png\").convert(\"RGBA\") for i in range(10)]\n\nframes[0].save(\"out.gif\", save_all=True, append_images=frames[1:],\n\nduration=100, loop=0, optimize=True)\n\n# --- Directory of images → GIF ---\n\nframe_paths = sorted(Path(\"frames\").glob(\"*.png\"))\n\nframes = [Image.open(p) for p in frame_paths]\n\nframes[0].save(\"out.gif\", save_all=True, append_images=frames[1:],\n\nduration=100, loop=0)\n\n# --- GIF → individual frames ---\n\ngif = Image.open(\"in.gif\")\n\nfor i in range(gif.n_frames):\n\ngif.seek(i)\n\ngif.save(f\"frame_{i}.png\")\n\n# --- Resize GIF ---\n\ngif = Image.open(\"in.gif\")\n\nresized_frames = []\n\nfor i in range(gif.n_frames):\n\ngif.seek(i)\n\nresized_frames.append(gif.copy().resize((320, 240), Image.LANCZOS))\n\nresized_frames[0].save(\"small.gif\", save_all=True, append_images=resized_frames[1:],\n\nduration=gif.info.get(\"duration\", 100), loop=0)\n\n# --- Video → GIF (imageio + ffmpeg) ---\n\nimport imageio.v3 as iio\n\nframes = iio.imread(\"in.mp4\", plugin=\"pyav\")\n\niio.imwrite(\"out.gif\", frames, plugin=\"pillow\", duration=40, loop=0)\n\n# --- GIF → MP4 (ffmpeg CLI, much smaller file) ---\n\n# ffmpeg -i in.gif -pix_fmt yuv420p -vf \"scale=trunc(iw\u002F2)*2:trunc(ih\u002F2)*2\" out.mp4\n\n```\n\n**GIF gotchas:**\n\n- GIF is limited to 256 colors per frame — complex images lose quality\n- Use `optimize=True` to reduce file size, but large GIFs are still huge compared to MP4\n\n- `duration` is per-frame in milliseconds (100ms = 10 FPS, 40ms = 25 FPS)\n- `loop=0` means infinite loop; `loop=1` plays once then stops\n\n- For video → GIF, consider downscaling first — full-resolution GIFs are enormous\n- Pillow GIF output doesn't support transparency well — use `imageio` for better results\n\n- For best quality: create GIF from video with ffmpeg: `ffmpeg -i in.mp4 -vf \"fps=15,scale=480:-1\" out.gif`\n\n## PDF to SVG\n\n```python\n\n# --- PyMuPDF (fitz) — best quality, vector-preserving ---\n\nimport fitz \\# pip install pymupdf\n\n# Single page\n\ndoc = fitz.open(\"in.pdf\")\n\npage = doc[0]\n\nsvg_text = page.get_svg_image()\n\nwith open(\"page_1.svg\", \"w\") as f:\n\nf.write(svg_text)\n\n# All pages\n\ndoc = fitz.open(\"in.pdf\")\n\nfor i, page in enumerate(doc):\n\nsvg_text = page.get_svg_image()\n\nwith open(f\"page_{i+1}.svg\", \"w\") as f:\n\nf.write(svg_text)\n\ndoc.close()\n\n# With custom resolution (matrix scales the output)\n\ndoc = fitz.open(\"in.pdf\")\n\npage = doc[0]\n\nmat = fitz.Matrix(2, 2) \\# 2x scale for higher detail\n\nsvg_text = page.get_svg_image(matrix=mat)\n\nwith open(\"page_hires.svg\", \"w\") as f:\n\nf.write(svg_text)\n\n```\n\n```bash\n\n# --- CLI alternatives ---\n\n# pdf2svg (if installed)\n\npdf2svg in.pdf out.svg 1 \\# page number\n\n# Inkscape CLI\n\ninkscape in.pdf --export-type=svg --export-filename=out.svg\n\n# pdftocairo (from poppler-utils)\n\npdftocairo -svg in.pdf out.svg\n\n```\n\n**PDF to SVG gotchas:**\n\n- `pymupdf` (imported as `fitz`) produces true vector SVGs — text stays as text, paths stay as paths\n- Scanned PDFs produce SVGs with embedded raster images (no vector data to extract)\n\n- Large PDFs with complex graphics produce very large SVG files\n- `pdf2svg` CLI tool is simple but must be installed separately (`apt install pdf2svg`)\n\n- For rasterized SVG (simpler but not truly vector): render PDF to PNG first, then embed in SVG\n\n## Validation\n\nAlways verify output:\n\n```python\n\n# Row count parity\n\nassert len(pd.read_csv(\"out.csv\")) == len(pd.read_json(\"in.json\"))\n\n# JSON well-formed\n\njson.load(open(\"out.json\"))\n\n# Image opens\n\nImage.open(\"out.jpg\").verify()\n\n```\n","# File Converter\n\n## OpenClawd Operator Adaptation\n\nRun this skill as part of the OpenClawd operator deck. Preserve the skill-specific workflow below, but frame outputs for Solana-native agents when relevant: prefer OpenClawd language, note whether the work can support autonomous agent operations, and keep financial, legal, tax, hiring, medical, or other regulated outputs informational unless the skill already requires a stricter disclaimer. Use the Llobster Legend persona only as light operator framing; do not let branding override accuracy, safety, or the user's stated domain.\n\n\nConvert between data, document, image, audio formats, and ZIP archives. One-liners for each conversion pair.\n\n## Tool Map\n\n| Domain | Tool | Install |\n\n|---|---|---|\n\n| CSV\u002FJSON\u002FExcel\u002FParquet | `pandas` | `pip install pandas openpyxl pyarrow` |\n\n| YAML | `pyyaml` | `pip install pyyaml` |\n\n| XML ↔ dict | `xmltodict` | `pip install xmltodict` |\n\n| Any doc format ↔ any | **pandoc** (CLI) | `apt install pandoc` or `pip install pypandoc_binary` |\n\n| Markdown → HTML | `markdown` | `pip install markdown` |\n\n| HTML → Markdown | `markdownify` | `pip install markdownify` |\n\n| .docx read\u002Fwrite | `python-docx` | `pip install python-docx` |\n\n| PDF → text\u002Ftables | `pdfplumber` | `pip install pdfplumber` |\n\n| PDF → images | `pdf2image` | `pip install pdf2image` + `apt install poppler-utils` |\n\n| PDF manipulation | `pypdf` | `pip install pypdf` |\n\n| Images | `Pillow` | `pip install Pillow` |\n\n| SVG → PNG | `cairosvg` | `pip install cairosvg` |\n\n| HEIC → JPG | `pillow-heif` | `pip install pillow-heif` |\n\n| Audio formats | `pydub` | `pip install pydub` + `apt install ffmpeg` |\n\n| EPUB ↔ other | **pandoc** or `ebooklib` | `pip install ebooklib` |\n\n| HTML → PDF | `weasyprint` | `pip install weasyprint` |\n\n| GIF creation | `Pillow` or `imageio` | `pip install imageio[ffmpeg]` |\n\n| PDF → SVG | `pdf2image` + `potrace` or `pymupdf` | `pip install pymupdf` |\n\n| ZIP archives | `zipfile` (stdlib) | built-in, no install needed |\n\n## File Input Handling\n\nWhen 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\u002F` or the project root. Always check both locations. If the file isn't found, ask the user where they saved it.\n\nCommon unsupported-in-chat but convertible formats: `.heic`, `.avif`, `.webp`, `.flac`, `.ogg`, `.m4a`, `.wma`, `.aiff`, `.epub`, `.parquet`, `.psd`, `.svg`, `.zip`\n\n## Data Formats\n\n```python\n\nimport pandas as pd, json, yaml, xmltodict\n\n# --- CSV ↔ JSON ---\n\npd.read_csv(\"in.csv\").to_json(\"out.json\", orient=\"records\", indent=2)\n\npd.read_json(\"in.json\").to_csv(\"out.csv\", index=False)\n\n# --- CSV → Excel \u002F Excel → CSV ---\n\npd.read_csv(\"in.csv\").to_excel(\"out.xlsx\", index=False, engine=\"openpyxl\")\n\npd.read_excel(\"in.xlsx\", sheet_name=\"Sheet1\").to_csv(\"out.csv\", index=False)\n\n# All sheets: pd.read_excel(\"in.xlsx\", sheet_name=None) → dict of DataFrames\n\n# --- CSV → Parquet (columnar, compressed) ---\n\npd.read_csv(\"in.csv\").to_parquet(\"out.parquet\", engine=\"pyarrow\", compression=\"snappy\")\n\n# --- YAML ↔ JSON ---\n\ndata = yaml.safe_load(open(\"in.yaml\")) \\# ALWAYS safe_load, never load()\n\njson.dump(data, open(\"out.json\", \"w\"), indent=2)\n\nyaml.safe_dump(json.load(open(\"in.json\")), open(\"out.yaml\", \"w\"), sort_keys=False)\n\n# --- XML ↔ JSON ---\n\ndata = xmltodict.parse(open(\"in.xml\").read())\n\njson.dump(data, open(\"out.json\", \"w\"), indent=2)\n\nopen(\"out.xml\", \"w\").write(xmltodict.unparse(data, pretty=True))\n\n# --- JSONL (one JSON object per line) ---\n\npd.read_json(\"in.jsonl\", lines=True).to_csv(\"out.csv\", index=False)\n\n```\n\n**Encoding gotchas:**\n\n- `pd.read_csv(\"f.csv\", encoding=\"utf-8-sig\")` strips the BOM that Excel inserts\n- Auto-detect: `import chardet; enc = chardet.detect(open(\"f.csv\",\"rb\").read())[\"encoding\"]`\n\n- CSV delimiter sniffing: `pd.read_csv(\"f.csv\", sep=None, engine=\"python\")`\n\n**Nested JSON → flat CSV:**\n\n```python\n\npd.json_normalize(data, sep=\".\").to_csv(\"out.csv\", index=False) \\# {\"a\":{\"b\":1}} → column \"a.b\"\n\n```\n\n## Document Formats — pandoc is the Swiss Army knife\n\n```bash\n\n# Markdown → PDF (requires LaTeX: apt install texlive-xetex)\n\npandoc input.md -o output.pdf --pdf-engine=xelatex\n\n# Markdown → DOCX\n\npandoc input.md -o output.docx\n\n# DOCX → Markdown (extracts images to .\u002Fmedia\u002F)\n\npandoc input.docx -o output.md --extract-media=.\n\n# HTML → Markdown\n\npandoc input.html -o output.md -t gfm\n\n# Any → Any (pandoc supports ~40 formats)\n\npandoc -f docx -t rst input.docx -o output.rst\n\n```\n\n```python\n\n# From Python\n\nimport pypandoc\n\npypandoc.convert_file(\"in.md\", \"docx\", outputfile=\"out.docx\")\n\n```\n\n**Without pandoc (pure Python):**\n\n```python\n\n# Markdown → HTML\n\nimport markdown\n\nhtml = markdown.markdown(open(\"in.md\").read(), extensions=[\"tables\", \"fenced_code\", \"toc\"])\n\n# HTML → Markdown\n\nfrom markdownify import markdownify\n\nmd = markdownify(html, heading_style=\"ATX\") \\# ATX = \\# headers, not underlines\n\n```\n\n## PDF Operations\n\n```python\n\n# --- Extract text + tables ---\n\nimport pdfplumber\n\nwith pdfplumber.open(\"in.pdf\") as pdf:\n\ntext = \"\\n\".join(p.extract_text() or \"\" for p in pdf.pages)\n\ntables = pdf.pages[0].extract_tables() \\# list of list-of-rows\n\n# --- PDF → images (one PNG per page) ---\n\nfrom pdf2image import convert_from_path\n\nfor i, img in enumerate(convert_from_path(\"in.pdf\", dpi=200)):\n\nimg.save(f\"page_{i+1}.png\")\n\n# --- Merge \u002F split \u002F rotate ---\n\nfrom pypdf import PdfReader, PdfWriter\n\nwriter = PdfWriter()\n\nfor path in [\"a.pdf\", \"b.pdf\"]:\n\nfor page in PdfReader(path).pages:\n\nwriter.add_page(page)\n\nwriter.write(\"merged.pdf\")\n\n# Extract pages 2–5\n\nreader = PdfReader(\"in.pdf\")\n\nwriter = PdfWriter()\n\nfor p in reader.pages[1:5]:\n\nwriter.add_page(p)\n\nwriter.write(\"pages_2-5.pdf\")\n\n```\n\n**PDF gotchas:**\n\n- `pdf2image` needs `poppler-utils` installed system-wide (not a pip package)\n- Scanned PDFs have no text layer — pdfplumber returns `None`. Use `pytesseract` OCR on pdf2image output.\n\n- `PyPDF2` is deprecated → use `pypdf` (same API, maintained fork)\n\n## Image Formats\n\n```python\n\nfrom PIL import Image\n\n# --- Basic conversion ---\n\nImage.open(\"in.png\").convert(\"RGB\").save(\"out.jpg\", quality=90)\n\n# convert(\"RGB\") is REQUIRED: JPEG can't store alpha channel, will raise OSError\n\n# --- WebP (best web format) ---\n\nImage.open(\"in.jpg\").save(\"out.webp\", quality=85, method=6) \\# method 0-6, 6=best compression\n\n# --- AVIF (smallest, Pillow 11+) ---\n\nImage.open(\"in.jpg\").save(\"out.avif\", quality=75)\n\n# --- HEIC (iPhone photos) → JPG ---\n\nfrom pillow_heif import register_heif_opener\n\nregister_heif_opener()\n\nImage.open(\"in.heic\").convert(\"RGB\").save(\"out.jpg\", quality=90)\n\n# --- SVG → PNG ---\n\nimport cairosvg\n\ncairosvg.svg2png(url=\"in.svg\", write_to=\"out.png\", output_width=1024)\n\n# --- Batch convert directory ---\n\nfrom pathlib import Path\n\nfor p in Path(\"imgs\").glob(\"*.png\"):\n\nImage.open(p).convert(\"RGB\").save(p.with_suffix(\".jpg\"), quality=85)\n\n```\n\n**Image gotchas:**\n\n- PNG → JPG: **must** `convert(\"RGB\")` first or transparency crashes the save\n- `quality` for PNG is meaningless (lossless) — use `optimize=True, compress_level=9`\n\n- Pillow can't open `.svg` natively — use `cairosvg` or `svglib`\n- GIF → MP4 is a video operation: `ffmpeg -i in.gif -pix_fmt yuv420p out.mp4`\n\n## Audio Formats\n\n```python\n\nfrom pydub import AudioSegment\n\n# --- MP3 ↔ WAV ---\n\nAudioSegment.from_mp3(\"in.mp3\").export(\"out.wav\", format=\"wav\")\n\nAudioSegment.from_wav(\"in.wav\").export(\"out.mp3\", format=\"mp3\", bitrate=\"192k\")\n\n# --- FLAC → MP3 ---\n\nAudioSegment.from_file(\"in.flac\", format=\"flac\").export(\"out.mp3\", format=\"mp3\", bitrate=\"320k\")\n\n# --- OGG → MP3 ---\n\nAudioSegment.from_ogg(\"in.ogg\").export(\"out.mp3\", format=\"mp3\", bitrate=\"192k\")\n\n# --- M4A \u002F AAC → MP3 ---\n\nAudioSegment.from_file(\"in.m4a\", format=\"m4a\").export(\"out.mp3\", format=\"mp3\", bitrate=\"256k\")\n\n# --- Any → Any (pydub supports mp3, wav, ogg, flac, m4a, aac, wma, aiff) ---\n\nAudioSegment.from_file(\"in.wma\", format=\"wma\").export(\"out.flac\", format=\"flac\")\n\n# --- Trim audio (first 30 seconds) ---\n\naudio = AudioSegment.from_file(\"in.mp3\")\n\naudio[:30000].export(\"first_30s.mp3\", format=\"mp3\") \\# milliseconds\n\n# --- Merge \u002F concatenate ---\n\ncombined = AudioSegment.from_file(\"a.mp3\") + AudioSegment.from_file(\"b.mp3\")\n\ncombined.export(\"merged.mp3\", format=\"mp3\")\n\n# --- Adjust volume ---\n\naudio = AudioSegment.from_file(\"in.mp3\")\n\nlouder = audio + 6 \\# +6 dB\n\nquieter = audio - 6 \\# -6 dB\n\nlouder.export(\"louder.mp3\", format=\"mp3\")\n\n# --- Get audio info ---\n\naudio = AudioSegment.from_file(\"in.mp3\")\n\nprint(f\"Duration: {len(audio)\u002F1000:.1f}s, Channels: {audio.channels}, \"\n\nf\"Sample rate: {audio.frame_rate}Hz, Sample width: {audio.sample_width*8}bit\")\n\n# --- Batch convert directory ---\n\nfrom pathlib import Path\n\nfor p in Path(\"audio\").glob(\"*.wav\"):\n\nAudioSegment.from_wav(str(p)).export(p.with_suffix(\".mp3\"), format=\"mp3\", bitrate=\"192k\")\n\n```\n\n**Audio gotchas:**\n\n- `pydub` requires `ffmpeg` installed system-wide for non-WAV formats\n- Bitrate options: \"128k\" (small\u002Flow quality), \"192k\" (balanced), \"256k\" (high), \"320k\" (max for MP3)\n\n- WAV files are uncompressed — expect 10x larger file sizes than MP3\n- For sample rate conversion: `audio.set_frame_rate(44100).export(\"out.wav\", format=\"wav\")`\n\n- Mono to stereo: `audio.set_channels(2)` \u002F Stereo to mono: `audio.set_channels(1)`\n\n## ZIP Archives\n\n```python\n\nimport zipfile\n\nfrom pathlib import Path\n\n# --- Create ZIP from files ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"w\", zipfile.ZIP_DEFLATED) as zf:\n\nzf.write(\"file1.txt\")\n\nzf.write(\"file2.csv\")\n\nzf.write(\"images\u002Fphoto.jpg\")\n\n# --- Create ZIP from entire directory ---\n\nimport shutil\n\nshutil.make_archive(\"archive\", \"zip\", root_dir=\"my_folder\") \\# creates archive.zip\n\n# --- Extract all ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\nzf.extractall(\"output_dir\")\n\n# --- Extract single file ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\nzf.extract(\"file1.txt\", \"output_dir\")\n\n# --- List contents without extracting ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\nfor info in zf.infolist():\n\nprint(f\"{info.filename} {info.file_size:,} bytes {info.compress_size:,} compressed\")\n\n# --- Read file from ZIP without extracting ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n\ncontent = zf.read(\"file1.txt\").decode(\"utf-8\")\n\n# --- Add files to existing ZIP ---\n\nwith zipfile.ZipFile(\"archive.zip\", \"a\") as zf:\n\nzf.write(\"new_file.txt\")\n\n# --- Create ZIP with password (read-only, use pyzipper for write) ---\n\n# pip install pyzipper\n\nimport pyzipper\n\nwith pyzipper.AESZipFile(\"secure.zip\", \"w\", compression=pyzipper.ZIP_DEFLATED,\n\nencryption=pyzipper.WZ_AES) as zf:\n\nzf.setpassword(b\"my_password\")\n\nzf.write(\"secret.txt\")\n\n# --- Batch: ZIP all PDFs in a directory ---\n\nwith zipfile.ZipFile(\"all_pdfs.zip\", \"w\", zipfile.ZIP_DEFLATED) as zf:\n\nfor p in Path(\".\").glob(\"**\u002F*.pdf\"):\n\nzf.write(p)\n\n```\n\n**ZIP gotchas:**\n\n- `zipfile` is in Python's standard library — no install needed\n- Always use `ZIP_DEFLATED` compression (default is `ZIP_STORED` = no compression)\n\n- For password-protected ZIPs, stdlib `zipfile` can only read (not write) — use `pyzipper` for encrypted writes\n- Max file size in standard ZIP is 4 GB; use `allowZip64=True` (default in Python 3) for larger files\n\n- `shutil.make_archive` is the simplest way to ZIP an entire directory tree\n\n## EPUB Formats\n\n```python\n\n# --- EPUB → other formats (via pandoc) ---\n\n# pandoc is the easiest way to convert EPUB\n\nimport pypandoc\n\n# EPUB → Markdown\n\npypandoc.convert_file(\"in.epub\", \"markdown\", outputfile=\"out.md\")\n\n# EPUB → HTML\n\npypandoc.convert_file(\"in.epub\", \"html\", outputfile=\"out.html\")\n\n# EPUB → DOCX\n\npypandoc.convert_file(\"in.epub\", \"docx\", outputfile=\"out.docx\")\n\n# EPUB → plain text\n\npypandoc.convert_file(\"in.epub\", \"plain\", outputfile=\"out.txt\")\n\n# --- Other formats → EPUB ---\n\n# Markdown → EPUB\n\npypandoc.convert_file(\"in.md\", \"epub\", outputfile=\"out.epub\",\n\nextra_args=[\"--metadata\", \"title=My Book\"])\n\n# HTML → EPUB\n\npypandoc.convert_file(\"in.html\", \"epub\", outputfile=\"out.epub\",\n\nextra_args=[\"--metadata\", \"title=My Book\"])\n\n# DOCX → EPUB\n\npypandoc.convert_file(\"in.docx\", \"epub\", outputfile=\"out.epub\")\n\n```\n\n```bash\n\n# --- CLI equivalents ---\n\npandoc in.epub -o out.md\n\npandoc in.epub -o out.pdf --pdf-engine=xelatex\n\npandoc in.md -o out.epub --metadata title=\"My Book\"\n\npandoc in.html -o out.epub --metadata title=\"My Book\" --epub-cover-image=cover.jpg\n\n```\n\n```python\n\n# --- Pure Python: read\u002Fwrite EPUB with ebooklib ---\n\nfrom ebooklib import epub\n\n# Read EPUB\n\nbook = epub.read_epub(\"in.epub\")\n\nfor item in book.get_items_of_type(9): \\# 9 = ITEM_DOCUMENT (HTML chapters)\n\nprint(item.get_name())\n\nhtml_content = item.get_content().decode(\"utf-8\")\n\n# Create EPUB from scratch\n\nbook = epub.EpubBook()\n\nbook.set_identifier(\"id123\")\n\nbook.set_title(\"My Book\")\n\nbook.set_language(\"en\")\n\nbook.add_author(\"Author Name\")\n\nch1 = epub.EpubHtml(title=\"Chapter 1\", file_name=\"ch1.xhtml\", lang=\"en\")\n\nch1.content = \"\u003Ch1>Chapter 1\u003C\u002Fh1>\u003Cp>Hello world.\u003C\u002Fp>\"\n\nbook.add_item(ch1)\n\nbook.toc = [epub.Link(\"ch1.xhtml\", \"Chapter 1\", \"ch1\")]\n\nbook.add_item(epub.EpubNcx())\n\nbook.add_item(epub.EpubNav())\n\nbook.spine = [\"nav\", ch1]\n\nepub.write_epub(\"out.epub\", book)\n\n```\n\n**EPUB gotchas:**\n\n- `pandoc` is the simplest for format-to-format EPUB conversion\n- Always add `--metadata title=\"...\"` when creating EPUB — readers require a title\n\n- EPUB is essentially a ZIP of HTML files — `ebooklib` gives you fine-grained control\n- For EPUB → PDF, pandoc needs a LaTeX engine (`texlive-xetex`)\n\n- Cover images: use `--epub-cover-image=cover.jpg` with pandoc\n\n## HTML to PDF\n\n```python\n\n# --- weasyprint (best CSS support, no browser needed) ---\n\nfrom weasyprint import HTML\n\n# Simple file conversion\n\nHTML(\"in.html\").write_pdf(\"out.pdf\")\n\n# From URL\n\nHTML(\"https:\u002F\u002Fexample.com\").write_pdf(\"page.pdf\")\n\n# From HTML string\n\nHTML(string=\"\u003Ch1>Hello\u003C\u002Fh1>\u003Cp>World\u003C\u002Fp>\").write_pdf(\"out.pdf\")\n\n# With custom CSS\n\nHTML(\"in.html\").write_pdf(\"out.pdf\", stylesheets=[\"custom.css\"])\n\n# With page size and margins\n\nfrom weasyprint import CSS\n\nHTML(\"in.html\").write_pdf(\"out.pdf\", stylesheets=[\n\nCSS(string=\"@page { size: A4; margin: 2cm; }\")\n\n])\n\n# Landscape orientation\n\nHTML(\"in.html\").write_pdf(\"out.pdf\", stylesheets=[\n\nCSS(string=\"@page { size: A4 landscape; margin: 1.5cm; }\")\n\n])\n\n```\n\n```bash\n\n# --- CLI alternatives ---\n\n# pandoc (simpler, less CSS fidelity)\n\npandoc in.html -o out.pdf --pdf-engine=xelatex\n\n# weasyprint CLI\n\nweasyprint in.html out.pdf\n\nweasyprint https:\u002F\u002Fexample.com page.pdf\n\n```\n\n**HTML to PDF gotchas:**\n\n- `weasyprint` supports CSS3 including flexbox, grid, and `@page` rules — best for styled documents\n- `weasyprint` does NOT run JavaScript — for JS-heavy pages, use `playwright` or `pyppeteer` instead\n\n- For JS-rendered pages: `playwright` → `page.pdf()` is the most reliable option\n- pandoc HTML → PDF goes through LaTeX, so complex CSS layouts may not render correctly\n\n- Large HTML files with many images: use `HTML(filename=\"in.html\", base_url=\".\")` so relative image paths resolve\n\n## GIF Creation\n\n```python\n\nfrom PIL import Image\n\nimport imageio.v3 as iio\n\nfrom pathlib import Path\n\n# --- Images → animated GIF (Pillow) ---\n\nframes = [Image.open(f\"frame_{i}.png\") for i in range(10)]\n\nframes[0].save(\"out.gif\", save_all=True, append_images=frames[1:],\n\nduration=100, loop=0) \\# duration in ms, loop=0 means infinite\n\n# --- Images → GIF with optimization ---\n\nframes = [Image.open(f\"frame_{i}.png\").convert(\"RGBA\") for i in range(10)]\n\nframes[0].save(\"out.gif\", save_all=True, append_images=frames[1:],\n\nduration=100, loop=0, optimize=True)\n\n# --- Directory of images → GIF ---\n\nframe_paths = sorted(Path(\"frames\").glob(\"*.png\"))\n\nframes = [Image.open(p) for p in frame_paths]\n\nframes[0].save(\"out.gif\", save_all=True, append_images=frames[1:],\n\nduration=100, loop=0)\n\n# --- GIF → individual frames ---\n\ngif = Image.open(\"in.gif\")\n\nfor i in range(gif.n_frames):\n\ngif.seek(i)\n\ngif.save(f\"frame_{i}.png\")\n\n# --- Resize GIF ---\n\ngif = Image.open(\"in.gif\")\n\nresized_frames = []\n\nfor i in range(gif.n_frames):\n\ngif.seek(i)\n\nresized_frames.append(gif.copy().resize((320, 240), Image.LANCZOS))\n\nresized_frames[0].save(\"small.gif\", save_all=True, append_images=resized_frames[1:],\n\nduration=gif.info.get(\"duration\", 100), loop=0)\n\n# --- Video → GIF (imageio + ffmpeg) ---\n\nimport imageio.v3 as iio\n\nframes = iio.imread(\"in.mp4\", plugin=\"pyav\")\n\niio.imwrite(\"out.gif\", frames, plugin=\"pillow\", duration=40, loop=0)\n\n# --- GIF → MP4 (ffmpeg CLI, much smaller file) ---\n\n# ffmpeg -i in.gif -pix_fmt yuv420p -vf \"scale=trunc(iw\u002F2)*2:trunc(ih\u002F2)*2\" out.mp4\n\n```\n\n**GIF gotchas:**\n\n- GIF is limited to 256 colors per frame — complex images lose quality\n- Use `optimize=True` to reduce file size, but large GIFs are still huge compared to MP4\n\n- `duration` is per-frame in milliseconds (100ms = 10 FPS, 40ms = 25 FPS)\n- `loop=0` means infinite loop; `loop=1` plays once then stops\n\n- For video → GIF, consider downscaling first — full-resolution GIFs are enormous\n- Pillow GIF output doesn't support transparency well — use `imageio` for better results\n\n- For best quality: create GIF from video with ffmpeg: `ffmpeg -i in.mp4 -vf \"fps=15,scale=480:-1\" out.gif`\n\n## PDF to SVG\n\n```python\n\n# --- PyMuPDF (fitz) — best quality, vector-preserving ---\n\nimport fitz \\# pip install pymupdf\n\n# Single page\n\ndoc = fitz.open(\"in.pdf\")\n\npage = doc[0]\n\nsvg_text = page.get_svg_image()\n\nwith open(\"page_1.svg\", \"w\") as f:\n\nf.write(svg_text)\n\n# All pages\n\ndoc = fitz.open(\"in.pdf\")\n\nfor i, page in enumerate(doc):\n\nsvg_text = page.get_svg_image()\n\nwith open(f\"page_{i+1}.svg\", \"w\") as f:\n\nf.write(svg_text)\n\ndoc.close()\n\n# With custom resolution (matrix scales the output)\n\ndoc = fitz.open(\"in.pdf\")\n\npage = doc[0]\n\nmat = fitz.Matrix(2, 2) \\# 2x scale for higher detail\n\nsvg_text = page.get_svg_image(matrix=mat)\n\nwith open(\"page_hires.svg\", \"w\") as f:\n\nf.write(svg_text)\n\n```\n\n```bash\n\n# --- CLI alternatives ---\n\n# pdf2svg (if installed)\n\npdf2svg in.pdf out.svg 1 \\# page number\n\n# Inkscape CLI\n\ninkscape in.pdf --export-type=svg --export-filename=out.svg\n\n# pdftocairo (from poppler-utils)\n\npdftocairo -svg in.pdf out.svg\n\n```\n\n**PDF to SVG gotchas:**\n\n- `pymupdf` (imported as `fitz`) produces true vector SVGs — text stays as text, paths stay as paths\n- Scanned PDFs produce SVGs with embedded raster images (no vector data to extract)\n\n- Large PDFs with complex graphics produce very large SVG files\n- `pdf2svg` CLI tool is simple but must be installed separately (`apt install pdf2svg`)\n\n- For rasterized SVG (simpler but not truly vector): render PDF to PNG first, then embed in SVG\n\n## Validation\n\nAlways verify output:\n\n```python\n\n# Row count parity\n\nassert len(pd.read_csv(\"out.csv\")) == len(pd.read_json(\"in.json\"))\n\n# JSON well-formed\n\njson.load(open(\"out.json\"))\n\n# Image opens\n\nImage.open(\"out.jpg\").verify()\n\n```\n",{"name":5,"description":7},"main","6b4a291df75c639681e93b4b5b812126443fd6ed","secondary_skills\u002Ffile-converter",3,"https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd","https:\u002F\u002Fsolanaclawd.com\u002Fopenclawd","https:\u002F\u002Fgithub.com\u002Fclawdsolana","https:\u002F\u002Fmusebook.trade","Welcome to Open Clawd. The future of Ai on and off chain. Bringing webMCP to web 3 using X402, A2A, MPP and so much more!","2026-08-01 09:30:51",{"source":8,"skillName":5,"commands":57,"prompt":67,"zipAvailable":68},[58,61,64],{"tool":59,"command":60},"npx","npx skills add clawdsolana\u002FOpenClawd --skill file-converter",{"tool":62,"command":63},"bunx","bunx skills add clawdsolana\u002FOpenClawd --skill file-converter",{"tool":65,"command":66},"pnpm","pnpm dlx skills add clawdsolana\u002FOpenClawd --skill file-converter","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsecondary_skills\u002Ffile-converter\u002FSKILL.md",true,[70],{"path":71,"resourceType":72,"resourceTypeDesc":73,"sizeBytes":74,"sourceHtmlUrl":75},".fingerprint","OTHER","其他",32,"https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsecondary_skills\u002Ffile-converter\u002F.fingerprint",[77,108,136,162,172,196],{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":86,"publishedAt":107},"01M71KRBD0W01EAXJ8YM4M1SJ2","solana-dev","End-to-end Solana development playbook (Jan 2026). Prefer Solana Foundation framework-kit (@solana\u002Fclient + @solana\u002Freact-hooks) for React\u002FNext.js UI. Prefer @solana\u002Fkit for all new client\u002FRPC\u002Ftransaction code. When legacy dependencies require web3.js, isolate it behind @solana\u002Fweb3-compat (or @solana\u002Fweb3.js as a true legacy fallback). Covers wallet-standard-first connection (incl. ConnectorKit), Anchor\u002FPinocchio programs, Codama-based client generation, LiteSVM\u002FMollusk\u002FSurfpool testing, and security checklists.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsrc\u002Fskills\u002Fsolana-dev\u002FSKILL.md",[83],{"categoryNo":84,"categoryName":85,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",[87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106],"solana dev","dev","solana","Wallet","end","testing","hooks","kit","new","when","web3","code","react","next","2026","playbook","security","ui","fallback","framework","2026-11-10 10:56:23",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":117,"publishedAt":107},"01M71KR8QPESSY61SVE8ZTFNVT","xurl","A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fxurl\u002FSKILL.md",[114],{"categoryNo":115,"categoryName":116,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[110,118,119,120,121,122,123,96,124,125,126,127,128,129,130,131,132,133,134,135],"posts","Media","x api","search","send","tap","you","api","cli","read","install","cli tool","endpoint","brew","for","manage","tool","twitter",{"skillNo":137,"name":138,"nameZh":6,"description":139,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":140,"categories":141,"keywords":143,"publishedAt":107},"01M71KR6FSXV88YDKWP7NBY3SH","wurk-integration","WURK API integration for monetizing agents with x402 payments on Solana\u002FBase - create social jobs, custom challenges, and agent help tasks","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fwurk-integration\u002FSKILL.md",[142],{"categoryNo":115,"categoryName":116,"taxonomyType":21,"taxonomyTypeDesc":22},[144,145,146,147,148,149,150,151,125,152,153,154,155,156,157,158,132,159,160,161],"wurk","wurk integration","integration","x402","social","payments","tasks","team","create","social-jobs","command","agent-help","custom-jobs","clawd","safe","api integration","custom","true",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":168,"publishedAt":107},"01M71KR4HCX41BPAK9GP7RPRRP","workflows","Manage application workflows including configuration, restart, and removal.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fworkflows\u002FSKILL.md",[],[164,133,169,170,171],"configuration","application","restart",{"skillNo":173,"name":174,"nameZh":6,"description":175,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":176,"categories":177,"keywords":178,"publishedAt":107},"01M71KR1VM0F5GPEQDNG5J7A0H","web-search","Search the web, fetch content, extract branding profiles, and capture screenshots from URLs. Use for real-time information, API documentation, current events, design matching, and visual reference.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fweb-search\u002FSKILL.md",[],[179,121,180,181,182,132,183,184,185,186,187,188,189,125,190,191,192,193,194,195],"web search","web","fetch","api documentation","real-time","time","profiles","design","use","information","visual","documentation","branding","matching","reference","capture","content",{"skillNo":197,"name":198,"nameZh":6,"description":199,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":200,"categories":201,"keywords":202,"publishedAt":107},"01M71KQZN5MR1JFQHG5GWXYAHS","weather","Get current weather and forecasts via wttr.in or Open-Meteo. Use when: user asks about weather, temperature, or forecasts for any location. NOT for: historical weather data, severe weather alerts, or detailed meteorological analysis. No API key needed.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fweather\u002FSKILL.md",[],[198,203,132,204,187,96,205,125,206,207,208,209,210,211,212,213,214,215],"get","open","analysis","alerts","temperature","curl","via","help","about","https","location","data","user"]