Skip to content

Releases: priyadip/ecompress

ecompress 2.5.0 - cut and combine PDFs and videos

Choose a tag to compare

@priyadip priyadip released this 14 Sep 11:57

Install or upgrade:

pip install -U ecompress

Added

  • Cut and combine PDFs and videos. Four commands - add_pdf, cut_pdf,
    add_video, cut_video - share one grammar, so a person or an LLM agent
    only has to learn it once:

    add_pdf   "D:/a.pdf"[2-9] "D:/b.pdf"[7-16] "D:/out"
    cut_pdf   "C:/My Documents/book.pdf"[2-5,8-12,20] "D:/out"
    add_video "a.mp4"[00:00-00:30] "b.mp4"[01:10-02:00] "D:/out"
    cut_video "movie.mp4"[00:02:10-00:05:30]
    

    The folder at the end is optional; without it the result is saved beside
    the input under a new name, never replacing a file.

    The same operations are available from Python as execute(), cut_pdf(),
    add_pdf(), cut_video() and add_video(), returning an EditResult.

  • PDF pages are copied as objects, so nothing is rasterised. Video is
    re-encoded at visually lossless quality so cuts are frame-accurate and clips
    of different sizes, frame rates and audio layouts can be joined; a clip with
    no audio contributes silence to keep sound in sync. cut_video --copy cuts
    without re-encoding when speed matters more than a frame-exact start.

  • Every range is validated before anything is written, and every result is
    built in a temporary file and checked with pikepdf or ffprobe before it is
    moved into place, so a mistake never leaves a partial file behind.

  • CommandSyntaxError, raised with a message naming the offending text and
    the expected form.

Notes

  • There is deliberately no arrow before the output: every shell treats > as
    output redirection. PowerShell reads "x"[2-9] as string indexing and zsh
    globs [...], so in those shells the range goes inside the quotes
    ("book.pdf[7-16]", which works everywhere) or PowerShell gets --%.
    Quotes around paths are optional, because shells strip them anyway.
  • Probing now reads a video's display rotation, so portrait phone footage keeps
    its orientation when joined with other clips.

ecompress 2.4.0

Choose a tag to compare

@priyadip priyadip released this 18 Aug 14:41
pip install --upgrade ecompress

Added

A progress bar while encoding. Long encodes printed nothing for minutes, so a run was indistinguishable from a hang. FFmpeg's own -progress stream now drives a bar with a percentage, a time estimate, and what is being encoded:

  [#####...................]  21%  0:09 left  2560x1440 @ 24 fps

Drawn only to a real terminal, so piping the output or using --quiet / --json stays clean. The estimate is withheld for the first few percent, where start-up cost would make it read wildly wrong.

Fixed

Quality targeting could replace a better result with a worse one. On a 4K screen recording the bitrate search reached 38.3 MB, the CRF fill managed only 34.4 MB - and the fill's result was adopted anyway, delivering the smaller of the two against a 40 MB floor. The fill now reports the size it achieved and is taken only if it improves on what came before.

The CRF ladder stopped at 16, not always enough to fill a window on very compressible 4K; it now runs to 10.

--timeout was not enforced during an encode: reading FFmpeg's progress stream blocks until it exits, so the deadline could only be noticed after the work had finished. A watchdog now stops it on time.

448 tests, 84% coverage, verified on Linux, macOS and Windows across Python 3.10-3.13.

ecompress 2.3.0

Choose a tag to compare

@priyadip priyadip released this 17 Aug 21:13
pip install --upgrade ecompress

Fixed

A bitrate proven to work is no longer thrown away when the resolution changes.

Every rung of the ladder restarted its search from the original opening estimate. Having learned that 3.13 Mbps worked at 1440p, the search dropped back to 965 kbps on moving to 2160p - so the higher resolution produced a smaller file than the rung below it:

Attempt 2: 21.0 MB [3.13 Mbps @ 2560x1440]   learned 3.13 Mbps works
Attempt 3: 16.2 MB [965 kbps  @ 3840x2160]   threw it away

The learned rate now carries forward, so each climb makes the file larger, monotonically, and the size floor is reached in fewer attempts.

Also: the climb decision now looks at what the current frame size achieved rather than a global best from another rung, and saturation is judged within a rung.

Changed

Long sources are searched on a 30-second sample. Each search pass had to encode the whole file. Benchmarked: 4K -> 4K runs at 19 fps against 65 fps for 4K -> 1080p, so a five-pass run on a six-minute 4K source could exceed an hour. Above 90 seconds the search now runs on a slice taken a quarter of the way in, and only the winning settings get a full-length encode.

The prediction is never the result - the full encode is measured and validated exactly as before, and corrected if the sample misjudged the file. Quality is unaffected.

Two measurements worth recording, both negative: -hwaccel auto made 4K decoding three times slower, not faster, because every frame is copied back from the GPU; and the scaler choice (lanczos, bicubic, fast_bilinear) makes no measurable difference. Neither is worth doing.

427 tests, 84% coverage, verified on Linux, macOS and Windows across Python 3.10-3.13.

ecompress 2.2.0

Choose a tag to compare

@priyadip priyadip released this 17 Aug 19:57
pip install --upgrade ecompress

Fixed

A size floor is now reachable even at source resolution.

2.0.1 taught the search to climb when the encoder could not spend its budget, and 2.1.0 made the opening guess accurate. Both worked through -b:v, an average bitrate target - and libx264 will not pad a stream with bits the content does not call for. On very compressible video it undershoots whatever number it is given, so once the climb reached the source resolution there was nowhere left to go and the result still fell short of the floor.

The search now switches to -crf, which fixes a quality level rather than an average rate, so lowering it always produces more bits. Measured on static but detailed 4K:

ABR 400 kbps  ->  280 kbps   (saturated, will not spend more)
CRF 24        ->  564 kbps
CRF 16        ->  786 kbps   (2.8x)

A clip that previously stopped at 752 KB against a 1.01 MB floor now lands at 1.18 MB, still at full 3840x2160.

Changed

When saturation is detected the climb jumps straight to the source resolution instead of stepping one rung at a time. For content that cannot absorb bits the source is both the largest file and the best quality, so the intermediate rungs cannot help - and skipping them saves several encodes on large sources.

424 tests, 86% coverage, verified on Linux, macOS and Windows across Python 3.10-3.13.

ecompress 2.1.0

Choose a tag to compare

@priyadip priyadip released this 17 Aug 18:53
pip install --upgrade ecompress

Changed

The opening guess is now calibrated from your own file, not a generic constant.

2.0.1 could recover from a bad first guess by climbing, but it still started from an assumption about typical footage - then spent several encodes discovering the assumption was wrong.

Your source file already answers the question. Its bitrate, resolution and frame rate give a measured bits-per-pixel for that specific content, free, before anything is re-encoded. A 4K 62.5 fps screen recording at 3.3 Mbps measures 0.26 against the generic 1.5 - 5.7x more compressible - and the starting plan moves accordingly:

Starting plan Pixels per frame
Generic constant 1024x576 @ 31.25 fps 589,824
Source-calibrated 2560x1440 @ 24 fps 3,686,400 (6.2x)

which is where the climb was heading anyway, reached in one step instead of several.

The measurement can only ever lower the requirement, never raise it: a visually-lossless source shows the content can absorb bits, not that it needs them. Without that cap a high-quality clip would be judged unable to hold its own resolution at 90% of its own bitrate, and would lose frame rate for no reason.

The measurement-driven climb from 2.0.1 is unchanged and still corrects whatever the opening guess gets wrong - it now has far less to correct.

423 tests, 86% coverage, verified on Linux, macOS and Windows across Python 3.10-3.13.

ecompress 2.0.1

Choose a tag to compare

@priyadip priyadip released this 17 Aug 18:21
pip install --upgrade ecompress

Fixed

Easily-compressed video no longer settles far below the requested size.

Content with little movement - a screen recording, a slide deck, a talking head on a static background - cannot absorb the bitrate it is given. Past a point the encoder is already at its best quality for that frame size, and more bits change nothing:

Attempt 1:  965 kbps -> 12.4 MB
Attempt 2: 3.13 Mbps -> 12.5 MB

The search only knew how to move down the quality ladder, so it accepted that undersized result. A 151 MB 4K recording asked for 40-50 MB came back as 12.5 MB of 576p.

It now recognises a saturated encoder and spends the spare budget on pixels instead of bitrate, climbing back towards the source resolution and frame rate. The step size comes from the measured bits-per-pixel of the encode that undershot, so it converges in a few attempts rather than one rung at a time.

The ceiling is still absolute - the climb stops before crossing it.

Also: derived frame rates no longer carry false precision (a 62.4999 fps source halves to 31.25 fps, not 31.2499 fps).

417 tests, 86% coverage, verified on Linux, macOS and Windows across Python 3.10-3.13.

ecompress 2.0.0

Choose a tag to compare

@priyadip priyadip released this 17 Aug 17:43
pip install ecompress
ecompress "video.mp4" 50

Renamed from compress-cli. No functional changes - every behaviour in 1.3.0 is identical, only the names moved.

Before (1.3.0) Now (2.0.0)
Install pip install compress-cli pip install ecompress
Command compress file.mp4 50 ecompress file.mp4 50
Import from compress import compress from ecompress import compress

The function is still compress().

The old import name shadowed an unrelated compress package on PyPI, and the old command collided with /usr/bin/compress - the classic Unix .Z tool - on Linux and macOS. compressor was already taken, so ecompress it is.

compress-cli 1.1.0-1.3.0 stay published and keep working.

Verified on Linux, macOS and Windows across Python 3.10-3.13.

compress-cli 1.2.0

Choose a tag to compare

@priyadip priyadip released this 17 Aug 14:19
pip install --upgrade compress-cli

No functional changes. Everything under src/compress/ is byte-identical to 1.1.0, so upgrading changes nothing about how the tool behaves. This release carries repository-only work and exercises the tag-driven release path end to end.

Changed

  • Pinned ruff to the 0.16 series in the dev extra. Ruff 0.16 began formatting Python code blocks inside Markdown, which 0.15 did not, so an unpinned range let CI and a developer machine disagree about ruff format --check.
  • The release workflow now fails immediately with an actionable message when a PyPI API token secret is missing, rather than falling through to Trusted Publishing and reporting an opaque OIDC error.
  • Publishing a version already present on an index is skipped instead of failing, so re-running a release is idempotent.
  • CI installs FFmpeg only as a package dependency so a broken bundle cannot be masked, with a separate job covering the system-PATH fallback.
  • README documents the automatic FFmpeg install, compress --check, and where each component is installed.

Verified on Linux, macOS and Windows across Python 3.10-3.13.

compress-cli 1.1.0

Choose a tag to compare

@priyadip priyadip released this 17 Aug 13:55

First public release.

pip install compress-cli
compress "video.mp4" 50

Compress any file below a target size in MB. The second argument is the maximum size of the result (1 MB = 1,000,000 bytes), and it is a hard ceiling: every candidate encode is measured on disk and re-parsed by an independent reader (Pillow, ffprobe, pikepdf) before it can be reported as a success. A missed target raises an error instead of being passed off as one.

Supported: images (JPEG, PNG, WebP, AVIF, GIF, BMP, TIFF), video (MP4, MKV, MOV, WebM, AVI and more), audio (MP3, WAV, M4A, AAC, FLAC, OGG, Opus), and PDF.

FFmpeg is installed automatically on Windows x64, macOS and Linux x86_64 - no separate install, nothing on your PATH. Run compress --check to see what was found.

The original file is never modified. Output goes next to it as <name>_compressed<ext>.

Verified on Linux, macOS and Windows across Python 3.10-3.13.