morphconv 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. morphconv-1.0.1/LICENSE +21 -0
  2. morphconv-1.0.1/PKG-INFO +396 -0
  3. morphconv-1.0.1/README.md +357 -0
  4. morphconv-1.0.1/morph/__init__.py +0 -0
  5. morphconv-1.0.1/morph/batch.py +492 -0
  6. morphconv-1.0.1/morph/cli.py +703 -0
  7. morphconv-1.0.1/morph/converters/__init__.py +19 -0
  8. morphconv-1.0.1/morph/converters/archives.py +85 -0
  9. morphconv-1.0.1/morph/converters/audio.py +58 -0
  10. morphconv-1.0.1/morph/converters/csv_json.py +28 -0
  11. morphconv-1.0.1/morph/converters/csv_to_xlsx.py +290 -0
  12. morphconv-1.0.1/morph/converters/documents.py +252 -0
  13. morphconv-1.0.1/morph/converters/ebooks.py +36 -0
  14. morphconv-1.0.1/morph/converters/fonts.py +72 -0
  15. morphconv-1.0.1/morph/converters/images.py +119 -0
  16. morphconv-1.0.1/morph/converters/json_yaml.py +26 -0
  17. morphconv-1.0.1/morph/converters/models_3d.py +149 -0
  18. morphconv-1.0.1/morph/converters/ocr.py +49 -0
  19. morphconv-1.0.1/morph/converters/pandas_extra.py +127 -0
  20. morphconv-1.0.1/morph/converters/svg.py +63 -0
  21. morphconv-1.0.1/morph/converters/video.py +153 -0
  22. morphconv-1.0.1/morph/converters/vtracer_converter.py +84 -0
  23. morphconv-1.0.1/morph/converters/web.py +92 -0
  24. morphconv-1.0.1/morph/converters/xlsx_to_csv.py +272 -0
  25. morphconv-1.0.1/morph/deps.py +228 -0
  26. morphconv-1.0.1/morph/ffmpeg_utils.py +79 -0
  27. morphconv-1.0.1/morph/history.py +136 -0
  28. morphconv-1.0.1/morph/progress.py +78 -0
  29. morphconv-1.0.1/morph/registry.py +236 -0
  30. morphconv-1.0.1/morph/tui.py +792 -0
  31. morphconv-1.0.1/morphconv.egg-info/PKG-INFO +396 -0
  32. morphconv-1.0.1/morphconv.egg-info/SOURCES.txt +40 -0
  33. morphconv-1.0.1/morphconv.egg-info/dependency_links.txt +1 -0
  34. morphconv-1.0.1/morphconv.egg-info/entry_points.txt +2 -0
  35. morphconv-1.0.1/morphconv.egg-info/requires.txt +30 -0
  36. morphconv-1.0.1/morphconv.egg-info/top_level.txt +1 -0
  37. morphconv-1.0.1/pyproject.toml +51 -0
  38. morphconv-1.0.1/setup.cfg +4 -0
  39. morphconv-1.0.1/tests/test_cli.py +66 -0
  40. morphconv-1.0.1/tests/test_converters.py +197 -0
  41. morphconv-1.0.1/tests/test_registry.py +47 -0
  42. morphconv-1.0.1/tests/test_web.py +46 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 morph contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,396 @@
1
+ Metadata-Version: 2.4
2
+ Name: morphconv
3
+ Version: 1.0.1
4
+ Summary: morph — convert anything to anything, from the CLI
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: pandas>=2.2.0
10
+ Requires-Dist: openpyxl>=3.1.2
11
+ Requires-Dist: xlsxwriter>=3.2.0
12
+ Requires-Dist: xlrd>=2.0.1
13
+ Requires-Dist: pyarrow>=15.0.0
14
+ Requires-Dist: odfpy>=1.4.1
15
+ Requires-Dist: lxml>=5.1.0
16
+ Requires-Dist: SQLAlchemy>=2.0.0
17
+ Requires-Dist: rich>=13.7.0
18
+ Requires-Dist: typer>=0.12.0
19
+ Requires-Dist: chardet>=5.2.0
20
+ Requires-Dist: python-dateutil>=2.9.0
21
+ Requires-Dist: click>=8.1.7
22
+ Requires-Dist: pyyaml>=6.0.1
23
+ Requires-Dist: textual>=0.60.0
24
+ Requires-Dist: Pillow>=10.0.0
25
+ Requires-Dist: pillow-heif>=0.15.0
26
+ Requires-Dist: pillow-avif-plugin>=1.4.3
27
+ Requires-Dist: cairosvg>=2.7.1
28
+ Requires-Dist: fonttools[woff]>=4.50.0
29
+ Requires-Dist: pytesseract>=0.3.10
30
+ Requires-Dist: trafilatura>=1.8.0
31
+ Requires-Dist: crawl4ai>=0.4.0
32
+ Requires-Dist: playwright>=1.40.0
33
+ Requires-Dist: vtracer>=0.6.0
34
+ Requires-Dist: bpy>=5.0.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
37
+ Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ <div align="center">
41
+
42
+ # morph
43
+
44
+ **Convert anything to anything, from the CLI.**
45
+
46
+ One command. No format-specific tools to remember. No cloud upload. No API keys.
47
+
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
49
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
50
+ [![Built with Typer](https://img.shields.io/badge/CLI-Typer-6C47FF.svg)](https://typer.tiangolo.com/)
51
+ [![Powered by Rich](https://img.shields.io/badge/output-Rich-8A2BE2.svg)](https://github.com/Textualize/rich)
52
+ [![TUI: Textual](https://img.shields.io/badge/TUI-Textual-4B0082.svg)](https://textual.textualize.io/)
53
+
54
+ ```
55
+ morph report.docx report.pdf
56
+ morph data.csv data.xlsx --table-style TableStyleMedium2 --header-bg 2E7D32
57
+ morph clip.mp4 clip.gif --fps 10 --width 480
58
+ morph batch '*.mp4' mp3 --workers 4
59
+ ```
60
+
61
+ </div>
62
+
63
+ ---
64
+
65
+ ## Why morph exists
66
+
67
+ `ffmpeg` converts media. `pandoc` converts documents. `ImageMagick` converts images.
68
+ Each is excellent at its one job and painful to use next to the others — different
69
+ flag dialects, different mental models, different install stories. Nobody had wired
70
+ them into **one coherent tool with one grammar and a genuinely good CLI/TUI**.
71
+
72
+ morph doesn't reinvent codecs or file formats — it orchestrates the best tool for
73
+ each job (ffmpeg, pandoc, Pillow, or plain Python) behind a single, predictable
74
+ interface, and handles the annoying parts for you: detecting what's missing,
75
+ telling you exactly what it'll install and asking first, and routing through
76
+ intermediate formats automatically when there's no direct converter.
77
+
78
+ ## Features
79
+
80
+ - **One verb.** `morph <input> <output>` — not `morph convert`. morph *is* the verb.
81
+ - **Smart multi-hop routing.** No direct `csv → yaml` converter? morph finds
82
+ `csv → json → yaml` on its own. By utilizing a Breadth-First Search (BFS) directed graph, morph instantly unlocks thousands of implicit conversions. Currently supporting **66 formats and over 1,000+ multi-hop routes** across data, documents, images, audio, and video!
83
+ - **Full control, contextually.** `morph data.csv data.xlsx --help` shows exactly
84
+ the flags relevant to *that* pair — table styles, freeze panes, passwords —
85
+ and nothing from unrelated converters.
86
+ - **Batch conversion.** `morph batch '*.mp4' mp3` converts many files in parallel
87
+ with a live Rich progress table, smart output strategies, and a clean summary.
88
+ - **Conversion history.** Every conversion is logged to `~/.morph_history.jsonl`.
89
+ `morph history` shows a searchable, filterable table of past jobs.
90
+ - **Cross-platform dependency management.** Missing `ffmpeg` or `pandoc`? morph
91
+ detects your package manager (brew/apt/dnf/pacman/winget/choco/…), shows you the
92
+ *exact* install command, and never runs it without asking first.
93
+ - **A real interactive TUI**, fully keyboard-driven — type a path (with autocomplete
94
+ dropdown for filesystem suggestions), arrow through formats (options update live),
95
+ and watch a real progress bar while it converts. No mouse needed.
96
+ - **Live execution feedback.** ffmpeg hops show a real progress bar driven by
97
+ ffmpeg's own `-progress` stream. Everything else shows a spinner labeled with
98
+ which tool is working (`via pandoc`, `via pillow`, ...).
99
+ - **Pluggable by design.** Drop a file in `morph/converters/`, call `register()`,
100
+ and it's live — no central registry to edit.
101
+ - **Local-first.** Nothing leaves your machine. No accounts, no upload, no API keys.
102
+ - **Transparent Remote Downloads.** Pass any `http://` link ending in a file extension (e.g., `morph https://domain.com/data.csv out.json`) and morph will stream it to a local temp file, render a live progress bar, convert it, and clean it up automatically.
103
+ - **Global Configuration (`~/.morphrc`).** Generate a fully-commented YAML configuration file containing every single flag for every converter using `morph config`. CLI arguments seamlessly override these base defaults, giving you maximum control.
104
+
105
+ ## Installation
106
+
107
+ ```bash
108
+ git clone https://github.com/hariharen9/morph.git
109
+ cd morph
110
+ pip install -e .
111
+ ```
112
+
113
+ That's it for **data, image, and archive** conversions — pure Python, zero external
114
+ binaries. **Documents** (docx/pdf/html/…) need `pandoc`; **audio/video** need
115
+ `ffmpeg`. You don't need to install these up front — morph detects what a given
116
+ conversion needs and prompts you the first time you hit it.
117
+
118
+ Check what's available any time:
119
+
120
+ ```bash
121
+ morph deps
122
+ ```
123
+
124
+ ## Quickstart
125
+
126
+ ```bash
127
+ # Data
128
+ morph data.csv data.xlsx # styled workbook: table, banding, autofit
129
+ morph data.csv data.json
130
+ morph data.csv data.yaml # routed through json automatically
131
+
132
+ # Documents (pandoc)
133
+ morph notes.md notes.docx
134
+ morph report.docx report.pdf
135
+ morph book.epub book.html
136
+
137
+ # Images (Pillow + cairosvg)
138
+ morph photo.png photo.webp --quality 80 --resize 1200x
139
+ morph icon.png icon.ico # multi-resolution ICO
140
+ morph diagram.svg diagram.png
141
+
142
+ # Fonts (fontTools)
143
+ morph font.ttf font.woff2
144
+ morph font.woff font.otf
145
+
146
+ # Remote Network Files
147
+ morph https://example.com/demo.mp4 demo.gif # streams to a temp file, converts, and cleans up
148
+ morph https://example.com/article.html doc.pdf --js # scrapes via crawl4ai if no format is detected
149
+
150
+ # Ebooks (Calibre)
151
+ morph book.epub book.mobi
152
+ morph book.epub book.azw3
153
+
154
+ # Audio / video (ffmpeg)
155
+ morph song.wav song.mp3 --bitrate 192k
156
+ morph clip.mov clip.mp4 --resolution 1280x720 --fps 30
157
+ morph clip.mp4 clip.mp3 # extract the audio track
158
+ morph clip.mp4 clip.gif --fps 10 --width 480
159
+
160
+ # Archives
161
+ morph project.zip project.tar.gz
162
+
163
+ # No args — interactive TUI
164
+ morph
165
+ ```
166
+
167
+ ### Batch conversion
168
+
169
+ Convert many files at once with a live progress display:
170
+
171
+ ```bash
172
+ # All MP4s → MP3, 4 parallel workers
173
+ morph batch '*.mp4' mp3 --workers 4
174
+
175
+ # Whole directory, recursive, into a separate output folder
176
+ morph batch ./raw/ mp3 --recursive --out-dir ./converted/
177
+
178
+ # Mirror the input directory structure
179
+ morph batch ./raw/ mp3 --recursive --out-dir ./converted/ --mirror
180
+
181
+ # Custom output name template
182
+ morph batch '*.mp4' mp3 --rename '{stem}_audio'
183
+ # → myvideo_audio.mp3
184
+
185
+ # Skip files whose output already exists
186
+ morph batch '*.mp4' mp3 --skip-existing
187
+
188
+ # Only re-convert if input is newer than output (like make)
189
+ morph batch '*.mp4' mp3 --newer-only
190
+
191
+ # Preview what would happen — no conversion
192
+ morph batch '*.mp4' mp3 --dry-run
193
+
194
+ # Multiple patterns at once
195
+ morph batch '*.flac' '*.wav' mp3
196
+
197
+ # Pass converter flags through
198
+ morph batch '*.mp4' mp3 --bitrate 192k
199
+ ```
200
+
201
+ During conversion, morph shows a live table:
202
+
203
+ ```
204
+ ╭─ morph batch — 8 files → mp3 ─────────────────────────────────────╮
205
+ │ Status File Time Size │
206
+ │ ✓ done concert_01.flac 0:00:04 8.3MB → 4.1MB │
207
+ │ ✓ done concert_02.flac 0:00:03 7.9MB → 3.8MB │
208
+ │ ▶ converting… concert_03.flac 0:00:01 │
209
+ │ · waiting concert_04.wav │
210
+ │ │
211
+ │ Overall ████████░░░░░░░░ 2/8 [0:00:07] │
212
+ ╰──────────────────────────────────────────────────────────────────────╯
213
+ ```
214
+
215
+ ### Conversion history
216
+
217
+ Every conversion (single and batch) is automatically logged:
218
+
219
+ ```bash
220
+ morph history # last 20 conversions
221
+ morph history -n 50 # last 50
222
+ morph history --failed # only failures
223
+ morph history --fmt mp4 # filter by format
224
+ morph history --clear # wipe history
225
+ morph history --json # raw JSONL output (for scripting)
226
+ ```
227
+
228
+ ```
229
+ ┌──────────┬──────┬──────┬────────────────┬────────┬────────────┐
230
+ │ When │ From │ To │ File │ Mode │ Status │
231
+ ├──────────┼──────┼──────┼────────────────┼────────┼────────────┤
232
+ │ 2m ago │ md │ docx │ README.md │ single │ ✓ 1.8s │
233
+ │ 1h ago │ mp4 │ mp3 │ intro.mp4 │ batch │ ✓ 4.2s │
234
+ │ yesterday│ csv │ xlsx │ data.csv │ single │ ✓ 0.1s │
235
+ └──────────┴──────┴──────┴────────────────┴────────┴────────────┘
236
+ ```
237
+
238
+ History is stored at `~/.morph_history.jsonl` — one JSON object per line,
239
+ easy to parse with `jq` or Python.
240
+
241
+ ### Every conversion is self-documenting
242
+
243
+ ```bash
244
+ $ morph data.csv data.xlsx --help
245
+ ```
246
+ ```
247
+ ╭─ morph — conversion route ─╮
248
+ │ csv → xlsx │
249
+ ╰─────────────────────────────╯
250
+
251
+ Options for this conversion (csv → xlsx):
252
+ -d, --delimiter Field delimiter. Auto-detected if omitted.
253
+ --table-style Excel table style name. (default: TableStyleMedium9)
254
+ --header-bg Header background hex colour (no #). (default: 1F3864)
255
+ --freeze-cols Left-most columns to freeze.
256
+ --password Password-protect the output sheet.
257
+ ... (more)
258
+ ```
259
+
260
+ The exact same file pointed at `report.pdf` shows a completely different,
261
+ shorter flag set — because pandoc's PDF pipeline genuinely only has one knob
262
+ worth exposing.
263
+
264
+ ### See what's reachable
265
+
266
+ ```bash
267
+ # Tree view grouped by family — great for discovery
268
+ $ morph formats
269
+ morph — 45 formats across 8 families
270
+
271
+ * document (11 formats, via pandoc, 82 direct routes)
272
+ ├── md → docx epub html latex odt pdf rst rtf txt
273
+ ├── docx → epub html latex md odt pdf rst rtf txt
274
+ └── ...
275
+
276
+ * audio (6 formats, via ffmpeg, 30 direct routes)
277
+ ├── mp3 → aac flac m4a ogg wav
278
+ └── ...
279
+
280
+ # Per-format table — see everything reachable from one format
281
+ $ morph formats mp4
282
+ ┌────────┬──────────────────┬──────┐
283
+ │ Target │ Route │ Hops │
284
+ ├────────┼──────────────────┼──────┤
285
+ │ mp3 │ mp4 → mp3 │ 1 │
286
+ │ gif │ mp4 → gif │ 1 │
287
+ │ webm │ mp4 → webm │ 1 │
288
+ │ ... │ ... │ ... │
289
+ └────────┴──────────────────┴──────┘
290
+ ```
291
+
292
+ ## Supported formats
293
+
294
+ | Domain | Formats | Backend |
295
+ |---|---|---|
296
+ | **Data** | csv, xlsx, json, yaml, parquet, feather, ods, xml, html, sqlite | native (pandas / openpyxl) |
297
+ | **Documents** | md, html, docx, odt, rtf, epub, latex, rst, txt, ipynb, pptx, adoc, org, opml, man, pdf* | pandoc |
298
+ | **Images** | png, jpg/jpeg, webp, bmp, gif, tiff, ico, heic, avif, icns, pdf, svg (input only) | Pillow / cairosvg |
299
+ | **Fonts** | ttf, otf, woff, woff2 | fontTools |
300
+ | **Ebooks** | epub, mobi, azw3 | Calibre (`ebook-convert`) |
301
+ | **Audio** | mp3, wav, flac, ogg, aac, m4a, opus, wma | ffmpeg |
302
+ | **Video** | mp4, mkv, mov, webm, avi, flv, wmv, mpeg (+ → audio, + → gif/webp, + → srt/vtt) | ffmpeg |
303
+ | **OCR** | png, jpg, webp, pdf, etc. → txt | tesseract |
304
+ | **Archives** | zip, tar, tar.gz, tar.bz2, tar.xz | stdlib |
305
+
306
+ <sub>*pdf is output-only — morph doesn't read PDFs back into structured content,
307
+ since that's a fundamentally lossier operation than every other conversion here.</sub>
308
+
309
+ ## Architecture
310
+
311
+ ```
312
+ morph/
313
+ ├── cli.py # dispatch + all subcommands (run, batch, formats, history, deps)
314
+ ├── registry.py # the graph: register(src, dst) edges + BFS routing
315
+ ├── batch.py # parallel batch engine: ThreadPoolExecutor + Rich Live display
316
+ ├── history.py # JSONL log at ~/.morph_history.jsonl
317
+ ├── deps.py # cross-platform dep detection & guarded installer
318
+ ├── progress.py # Rich progress bars for the CLI
319
+ ├── ffmpeg_utils.py # ffmpeg -progress pipe parser for real % tracking
320
+ ├── tui.py # Textual keyboard-driven TUI (same engine as CLI)
321
+ └── converters/
322
+ ├── __init__.py # auto-discovers every file below — nothing to register by hand
323
+ ├── csv_to_xlsx.py # self-contained: engine + OptionSpecs + register() in one file
324
+ ├── xlsx_to_csv.py
325
+ ├── csv_json.py
326
+ ├── json_yaml.py
327
+ ├── documents.py # one generic pandoc engine, many pairs
328
+ ├── images.py # one generic Pillow engine, many pairs
329
+ ├── svg.py # cairosvg for SVG → raster/PDF
330
+ ├── audio.py # ffmpeg with real progress tracking
331
+ ├── video.py # ffmpeg, includes → gif + audio extraction
332
+ ├── archives.py
333
+ ├── fonts.py # fontTools: ttf/otf/woff/woff2
334
+ └── ebooks.py # Calibre ebook-convert
335
+ ```
336
+
337
+ Every converter is a function `(input_path, output_path, **options) -> ConversionResult`,
338
+ registered once with `@register(src, dst, backend=..., options=[...])`. The CLI
339
+ never hardcodes a flag — it asks the registry which `OptionSpec`s apply to the
340
+ route it just resolved and builds the parser from that, which is also what makes
341
+ per-pair `--help` possible.
342
+
343
+ ### Adding a new conversion
344
+
345
+ Drop a file in `morph/converters/`:
346
+
347
+ ```python
348
+ # morph/converters/my_converter.py
349
+ from pathlib import Path
350
+ from ..registry import ConversionResult, OptionSpec, register
351
+
352
+ OPTIONS = [
353
+ OptionSpec("quality", ("--quality",), "Output quality (1-100).", default=85, type=int),
354
+ ]
355
+
356
+ @register("foo", "bar", backend="sometool", family="image", options=OPTIONS)
357
+ def foo_to_bar(input_path: Path, output_path: Path, *, quality=85, **_) -> ConversionResult:
358
+ # ... do the conversion ...
359
+ return ConversionResult(output=output_path)
360
+ ```
361
+
362
+ That's the whole integration. No imports to add elsewhere, no CLI wiring —
363
+ `converters/__init__.py` picks it up automatically, `morph x.foo y.bar` works
364
+ immediately, and it slots into the routing graph so anything that could reach
365
+ `.foo` can now reach `.bar` too.
366
+
367
+ ## CLI reference
368
+
369
+ ```
370
+ morph <input> <output> [OPTIONS] # convert a single file
371
+ morph <input> <output> --help # show flags for THIS conversion only
372
+ morph batch <patterns...> <format> # batch convert (see morph batch --help)
373
+ morph formats # tree of all formats by family
374
+ morph formats <fmt> # everything reachable from <fmt>
375
+ morph history # recent conversion log
376
+ morph deps # check / install external tools
377
+ morph # launch the interactive TUI
378
+ ```
379
+
380
+ ## Roadmap
381
+
382
+ - [ ] Per-pair codec tuning for audio/video (currently relies on ffmpeg defaults)
383
+ - [ ] HEIC/AVIF image support (pillow-heif / pillow-avif)
384
+ - [ ] `~/.morphrc` config file for persistent flag defaults
385
+ - [ ] Automated test suite
386
+
387
+ ## Contributing
388
+
389
+ Issues and PRs welcome. If you're adding a converter, see
390
+ ["Adding a new conversion"](#adding-a-new-conversion) above — that's the entire
391
+ contract. Please include a quick sanity check (a real input → real output,
392
+ not just "it imports") in your PR description.
393
+
394
+ ## License
395
+
396
+ [MIT](LICENSE)