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.
- ecompress-2.0.0/.gitignore +21 -0
- ecompress-2.0.0/CHANGELOG.md +190 -0
- ecompress-2.0.0/LICENSE +21 -0
- ecompress-2.0.0/PKG-INFO +537 -0
- ecompress-2.0.0/README.md +471 -0
- ecompress-2.0.0/pyproject.toml +160 -0
- ecompress-2.0.0/src/ecompress/__init__.py +57 -0
- ecompress-2.0.0/src/ecompress/__main__.py +10 -0
- ecompress-2.0.0/src/ecompress/api.py +332 -0
- ecompress-2.0.0/src/ecompress/backends/__init__.py +19 -0
- ecompress-2.0.0/src/ecompress/backends/audio.py +354 -0
- ecompress-2.0.0/src/ecompress/backends/base.py +189 -0
- ecompress-2.0.0/src/ecompress/backends/image.py +385 -0
- ecompress-2.0.0/src/ecompress/backends/pdf.py +448 -0
- ecompress-2.0.0/src/ecompress/backends/video.py +400 -0
- ecompress-2.0.0/src/ecompress/cli.py +271 -0
- ecompress-2.0.0/src/ecompress/detect.py +256 -0
- ecompress-2.0.0/src/ecompress/diagnostics.py +176 -0
- ecompress-2.0.0/src/ecompress/errors.py +98 -0
- ecompress-2.0.0/src/ecompress/ffmpeg.py +409 -0
- ecompress-2.0.0/src/ecompress/naming.py +164 -0
- ecompress-2.0.0/src/ecompress/process.py +114 -0
- ecompress-2.0.0/src/ecompress/py.typed +0 -0
- ecompress-2.0.0/src/ecompress/quality.py +191 -0
- ecompress-2.0.0/src/ecompress/reporting.py +59 -0
- ecompress-2.0.0/src/ecompress/result.py +139 -0
- ecompress-2.0.0/src/ecompress/search.py +230 -0
- ecompress-2.0.0/src/ecompress/units.py +172 -0
- ecompress-2.0.0/src/ecompress/validation.py +163 -0
- ecompress-2.0.0/tests/__init__.py +1 -0
- ecompress-2.0.0/tests/conftest.py +314 -0
- ecompress-2.0.0/tests/test_api.py +283 -0
- ecompress-2.0.0/tests/test_audio.py +150 -0
- ecompress-2.0.0/tests/test_boundaries.py +115 -0
- ecompress-2.0.0/tests/test_bundled_ffmpeg.py +257 -0
- ecompress-2.0.0/tests/test_cli.py +205 -0
- ecompress-2.0.0/tests/test_detect.py +127 -0
- ecompress-2.0.0/tests/test_fallbacks.py +161 -0
- ecompress-2.0.0/tests/test_ffmpeg.py +135 -0
- ecompress-2.0.0/tests/test_image.py +228 -0
- ecompress-2.0.0/tests/test_integration.py +197 -0
- ecompress-2.0.0/tests/test_naming.py +169 -0
- ecompress-2.0.0/tests/test_pdf.py +201 -0
- ecompress-2.0.0/tests/test_process.py +112 -0
- ecompress-2.0.0/tests/test_quality.py +207 -0
- ecompress-2.0.0/tests/test_regressions.py +195 -0
- ecompress-2.0.0/tests/test_search.py +250 -0
- ecompress-2.0.0/tests/test_size_range.py +199 -0
- ecompress-2.0.0/tests/test_units.py +88 -0
- ecompress-2.0.0/tests/test_validation.py +125 -0
- 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
|
ecompress-2.0.0/LICENSE
ADDED
|
@@ -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.
|