ecompress 2.0.0__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 (51) hide show
  1. ecompress-2.0.0/.gitignore +21 -0
  2. ecompress-2.0.0/CHANGELOG.md +190 -0
  3. ecompress-2.0.0/LICENSE +21 -0
  4. ecompress-2.0.0/PKG-INFO +537 -0
  5. ecompress-2.0.0/README.md +471 -0
  6. ecompress-2.0.0/pyproject.toml +160 -0
  7. ecompress-2.0.0/src/ecompress/__init__.py +57 -0
  8. ecompress-2.0.0/src/ecompress/__main__.py +10 -0
  9. ecompress-2.0.0/src/ecompress/api.py +332 -0
  10. ecompress-2.0.0/src/ecompress/backends/__init__.py +19 -0
  11. ecompress-2.0.0/src/ecompress/backends/audio.py +354 -0
  12. ecompress-2.0.0/src/ecompress/backends/base.py +189 -0
  13. ecompress-2.0.0/src/ecompress/backends/image.py +385 -0
  14. ecompress-2.0.0/src/ecompress/backends/pdf.py +448 -0
  15. ecompress-2.0.0/src/ecompress/backends/video.py +400 -0
  16. ecompress-2.0.0/src/ecompress/cli.py +271 -0
  17. ecompress-2.0.0/src/ecompress/detect.py +256 -0
  18. ecompress-2.0.0/src/ecompress/diagnostics.py +176 -0
  19. ecompress-2.0.0/src/ecompress/errors.py +98 -0
  20. ecompress-2.0.0/src/ecompress/ffmpeg.py +409 -0
  21. ecompress-2.0.0/src/ecompress/naming.py +164 -0
  22. ecompress-2.0.0/src/ecompress/process.py +114 -0
  23. ecompress-2.0.0/src/ecompress/py.typed +0 -0
  24. ecompress-2.0.0/src/ecompress/quality.py +191 -0
  25. ecompress-2.0.0/src/ecompress/reporting.py +59 -0
  26. ecompress-2.0.0/src/ecompress/result.py +139 -0
  27. ecompress-2.0.0/src/ecompress/search.py +230 -0
  28. ecompress-2.0.0/src/ecompress/units.py +172 -0
  29. ecompress-2.0.0/src/ecompress/validation.py +163 -0
  30. ecompress-2.0.0/tests/__init__.py +1 -0
  31. ecompress-2.0.0/tests/conftest.py +314 -0
  32. ecompress-2.0.0/tests/test_api.py +283 -0
  33. ecompress-2.0.0/tests/test_audio.py +150 -0
  34. ecompress-2.0.0/tests/test_boundaries.py +115 -0
  35. ecompress-2.0.0/tests/test_bundled_ffmpeg.py +257 -0
  36. ecompress-2.0.0/tests/test_cli.py +205 -0
  37. ecompress-2.0.0/tests/test_detect.py +127 -0
  38. ecompress-2.0.0/tests/test_fallbacks.py +161 -0
  39. ecompress-2.0.0/tests/test_ffmpeg.py +135 -0
  40. ecompress-2.0.0/tests/test_image.py +228 -0
  41. ecompress-2.0.0/tests/test_integration.py +197 -0
  42. ecompress-2.0.0/tests/test_naming.py +169 -0
  43. ecompress-2.0.0/tests/test_pdf.py +201 -0
  44. ecompress-2.0.0/tests/test_process.py +112 -0
  45. ecompress-2.0.0/tests/test_quality.py +207 -0
  46. ecompress-2.0.0/tests/test_regressions.py +195 -0
  47. ecompress-2.0.0/tests/test_search.py +250 -0
  48. ecompress-2.0.0/tests/test_size_range.py +199 -0
  49. ecompress-2.0.0/tests/test_units.py +88 -0
  50. ecompress-2.0.0/tests/test_validation.py +125 -0
  51. ecompress-2.0.0/tests/test_video.py +238 -0
@@ -0,0 +1,21 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ test_env/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ .coverage.*
15
+ htmlcov/
16
+ coverage.xml
17
+ .compress-*/
18
+ *_compressed.*
19
+ *_compressed_*.*
20
+ .DS_Store
21
+ Thumbs.db
@@ -0,0 +1,190 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.0.0] - 2026-08-17
9
+
10
+ Renamed. **No functional changes** - every behaviour in 1.3.0 is identical.
11
+
12
+ ### Changed
13
+
14
+ - **The project is now ecompress.** The PyPI name, the console command and
15
+ the import name all changed together:
16
+
17
+ | | Before (1.3.0) | Now (2.0.0) |
18
+ | --- | --- | --- |
19
+ | Install | pip install compress-cli | pip install ecompress |
20
+ | Command | compress file.mp4 50 | ecompress file.mp4 50 |
21
+ | Import |
22
+ rom compress import compress |
23
+ rom ecompress import compress |
24
+
25
+ The function is still called compress().
26
+
27
+ Two reasons. The import name compress shadowed an unrelated package of that
28
+ name on PyPI, so installing both broke one of them. More seriously, the
29
+ command compress collided with /usr/bin/compress - the classic Unix .Z
30
+ tool - on Linux and macOS, where either could shadow the other depending on
31
+ PATH order.
32
+
33
+ ### Migration
34
+
35
+ Replace compress with ecompress in install commands, shell invocations and
36
+ imports. Calls to compress(...) in Python are unchanged.
37
+
38
+ compress-cli 1.1.0-1.3.0 remain on PyPI and keep working; they will not be
39
+ updated further. Entries below describe those releases as they shipped, under
40
+ their original names.
41
+
42
+ ## [1.3.0] - 2026-08-17
43
+
44
+ ### Added
45
+
46
+ - **Size ranges.** `compress "movie.mp4" 40-50` sets a floor as well as a
47
+ ceiling, so the budget gets used instead of undershot. `[40,50]`, `40..50`,
48
+ `40,50` and `--min 40` are all accepted, as are `(40, 50)` and `min_mb=` from
49
+ Python. The maximum stays a hard limit; the minimum is a quality floor that
50
+ the search climbs towards. When a file genuinely cannot reach the floor — a
51
+ small source, or a format with nothing left to give — the result is still
52
+ returned with an explanatory note. Files are never padded to hit a number.
53
+ - `CompressionResult.min_size_bytes`, `.min_size_mb` and
54
+ `.within_requested_range`, plus the same fields in `--json` output.
55
+
56
+ ### Changed
57
+
58
+ - **Video now trades frame rate and resolution together.** Previously only
59
+ resolution was reduced, which starved high-frame-rate sources: a 5-minute 4K
60
+ 62 fps clip targeting 50 MB fell all the way to 640x360 while keeping all 62
61
+ frames per second. The cost of each (resolution, frame rate) combination is
62
+ now modelled on published streaming ladders — doubling frame rate costs about
63
+ 1.5x the bitrate, not 2x — every affordable combination is ranked with
64
+ resolution weighted above frame rate, and the best is encoded. The same clip
65
+ now produces 1024x576 at 31 fps: 2.6x the pixels per frame.
66
+
67
+ Frame rate is never raised, never reduced below 24 fps, and left untouched
68
+ when the budget comfortably covers the source. Duration is unchanged, and the
69
+ existing validation still verifies it on every candidate.
70
+ - The resolution ladder gained intermediate tiers (432, 288, 240, 216). Gaps in
71
+ it made the search fall further than necessary when no tier in between was
72
+ affordable.
73
+
74
+ ### Notes
75
+
76
+ - `compress.quality` is a new module holding the bitrate cost model and the
77
+ ranking, so the heuristics are testable in isolation and documented in one
78
+ place. Both constants are tuned to established encoding practice rather than
79
+ measured per clip; unusual footage will not match them exactly.
80
+
81
+ ## [1.2.0] - 2026-08-17
82
+
83
+ **No functional changes.** Everything under `src/compress/` is byte-identical
84
+ to 1.1.0, so upgrading from 1.1.0 changes nothing about how the tool behaves.
85
+ This release exists to exercise the tag-driven release path end to end; the
86
+ work below only affects the repository, never the installed package.
87
+
88
+ ### Changed
89
+
90
+ - Pinned `ruff` to the 0.16 series in the `dev` extra. Ruff 0.16 began
91
+ formatting Python code blocks inside Markdown, which 0.15 did not, so an
92
+ unpinned range let CI and a developer's machine disagree about
93
+ `ruff format --check`.
94
+ - Release workflow now fails immediately with an actionable message when a
95
+ PyPI API token secret is missing, instead of falling through to Trusted
96
+ Publishing and reporting an opaque OIDC error.
97
+ - Release workflow skips a version already present on the index, so re-running
98
+ a release (or pushing a tag after a manual run) is idempotent rather than a
99
+ hard failure.
100
+ - CI installs FFmpeg only as a package dependency rather than from the system,
101
+ so a broken bundle cannot be masked; a separate job uninstalls it to cover
102
+ the `PATH` fallback used on platforms without a prebuilt wheel.
103
+ - README documents the automatic FFmpeg install, `compress --check`, and where
104
+ each component is installed.
105
+
106
+ ## [1.1.0] - 2026-08-17
107
+
108
+ ### Added
109
+
110
+ - **FFmpeg is now installed automatically.** `ffmpeg-binaries` (which ships
111
+ both `ffmpeg` and `ffprobe`) is a regular dependency on every platform with a
112
+ prebuilt wheel — Windows x64, macOS, and Linux x86_64 — so a plain
113
+ `pip install compress-cli` is all a user needs for video and audio. The
114
+ binaries land inside the environment's `site-packages`; nothing is written to
115
+ system directories and nothing needs to be on `PATH`.
116
+ - `compress --check` reports what is installed: which FFmpeg was found and
117
+ where it came from, which encoders it supports, Pillow and its formats,
118
+ pikepdf, and Ghostscript. Exits `0` when everything needed is present and `3`
119
+ when something is missing. `compress --check --json` gives the same
120
+ information for scripts.
121
+ - `compress.ffmpeg.bundled_binary()` locates the bundled executables through
122
+ installed-distribution metadata rather than by importing them, so the
123
+ `ffmpeg-binaries` import name (`ffmpeg`) cannot be shadowed by the unrelated
124
+ `ffmpeg-python` package. The real executable is preferred over the
125
+ console-script shim pip installs alongside it.
126
+ - `compress-cli[ffmpeg]` extra to force the bundled FFmpeg on platforms the
127
+ automatic marker skips (Linux ARM, musl, 32-bit Windows).
128
+
129
+ ### Changed
130
+
131
+ - FFmpeg discovery order is now: `COMPRESS_FFMPEG`/`COMPRESS_FFPROBE` override,
132
+ then the bundled copy, then `PATH`, then well-known install locations. The
133
+ bundled copy is preferred over `PATH` so behaviour is identical on every
134
+ machine regardless of what system FFmpeg happens to be installed.
135
+ - The "FFmpeg is not installed" message now leads with
136
+ `pip install "compress-cli[ffmpeg]"` before system package managers.
137
+ - On Linux and macOS the bundled binaries are made executable if the
138
+ executable bit did not survive installation.
139
+
140
+ ## 1.0.0 - 2026-08-17
141
+
142
+ Never published to PyPI; superseded by 1.1.0 before release. Requires Python
143
+ 3.10 or newer.
144
+
145
+ ### Added
146
+
147
+ - `compress "PATH" TARGET_MB` command. The second argument is the maximum size
148
+ of the result in decimal megabytes (`1 MB = 1,000,000 bytes`).
149
+ - Hard target-size guarantee: every candidate is measured on disk and re-parsed
150
+ by an independent reader (Pillow, ffprobe or pikepdf) before it can be
151
+ reported as a success. A missed target raises `TargetNotAchievableError` and
152
+ exits with status 1 instead of being reported as success.
153
+ - Automatic media detection from file contents, with the extension only as a
154
+ fallback. Containers that can hold either audio or video (MP4, MKV, WebM,
155
+ Ogg, AVI, MOV) are disambiguated with `ffprobe`.
156
+ - Image backend (Pillow) for JPEG, PNG, WebP, AVIF, GIF, BMP and TIFF: binary
157
+ search over encoder quality, then palette reduction, then a resolution
158
+ ladder.
159
+ - Video backend (FFmpeg) for MP4, MKV, MOV, WebM, AVI and more: analytic
160
+ bitrate seeding followed by proportional correction from measured output,
161
+ with an automatic resolution ladder. H.264 + AAC for MP4/MOV/MKV, VP9 + Opus
162
+ for WebM, H.264 + MP3 for AVI.
163
+ - Audio backend (FFmpeg) for MP3, WAV, M4A, AAC, FLAC, OGG, Opus and more.
164
+ Lossless FLAC is tried first for lossless sources; lossy sources keep their
165
+ own codec; Opus is used when the budget drops below roughly 48 kbps.
166
+ - PDF backend (pikepdf, optionally Ghostscript): lossless structural
167
+ optimisation first, then image re-encoding, with page-count validation on
168
+ every candidate.
169
+ - Python API: `from compress import compress`, returning `CompressionResult`
170
+ with `output_path`, byte-accurate sizes, `reduction_percent`, `attempts`,
171
+ `target_achieved`, `skipped` and `format_changed`.
172
+ - Files already below the target are left completely untouched and reported as
173
+ `skipped`.
174
+ - Output naming: `<name>_compressed<ext>` next to the original, falling back to
175
+ `<name>_compressed_1<ext>`, `_2`, ... Names are reserved atomically with
176
+ `O_EXCL`, so concurrent runs cannot collide and the original is never
177
+ overwritten.
178
+ - `--output`, `--overwrite`, `--quiet`, `--json` and `--timeout` for scripting.
179
+
180
+ ### Security
181
+
182
+ - Every external tool is invoked with an argument list and `shell=False`. No
183
+ user-controlled string is ever interpolated into a shell command line.
184
+ - Scratch files are created in a private per-run directory with
185
+ `tempfile.mkdtemp`.
186
+
187
+ [2.0.0]: https://github.com/priyadip/compress-cli/releases/tag/v2.0.0
188
+ [1.3.0]: https://github.com/priyadip/compress-cli/releases/tag/v1.3.0
189
+ [1.2.0]: https://github.com/priyadip/compress-cli/releases/tag/v1.2.0
190
+ [1.1.0]: https://github.com/priyadip/compress-cli/releases/tag/v1.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Priyadip Sau
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.