diff --git a/.specstory/history/2025-12-11_18-21Z-compress-png-sequences-with-multithreading.md b/.specstory/history/2025-12-11_18-21Z-compress-png-sequences-with-multithreading.md
index 6e948f9..7bf0e9b 100644
--- a/.specstory/history/2025-12-11_18-21Z-compress-png-sequences-with-multithreading.md
+++ b/.specstory/history/2025-12-11_18-21Z-compress-png-sequences-with-multithreading.md
@@ -2198,3 +2198,313 @@ The script now preserves 16-bit depth where possible and avoids unnecessary conv
---
+_**User (2025-12-11 21:39Z)**_
+
+It tells time elapsed, but can you have it estimate time remaining, too?
+
+---
+
+_**Agent (model default, mode Agent)**_
+
+
+
+---
+
+
+Checking the current ETA implementation and improving its visibility and formatting:
+
+
+---
+
+
+Tool use: **read_file** • Read file: compress_pngs.py
+
+
+
+
+---
+
+
+
+---
+
+
+
+
+
+---
+
+
+Tool use: **read_file** • Read file: compress_pngs.py
+
+
+
+
+---
+
+
+
+---
+
+
+Adding a time formatting function and improving ETA visibility and accuracy:
+
+
+---
+
+
+
+Tool use: **search_replace** • Edit file: compress_pngs.py
+
+**Chunk 1**
+Lines added: 16, lines removed: undefined
+
+```diff
+@@ -131,4 +131,20 @@
+ return f"{size_bytes:.2f} TB"
+
++ def format_time(seconds):
++ """Format time in human readable format (e.g., '2m 30s' or '1h 15m')."""
++ if seconds < 60:
++ return f"{seconds:.0f}s"
++ elif seconds < 3600:
++ minutes = int(seconds // 60)
++ secs = int(seconds % 60)
++ return f"{minutes}m {secs}s"
++ else:
++ hours = int(seconds // 3600)
++ minutes = int((seconds % 3600) // 60)
++ if minutes > 0:
++ return f"{hours}h {minutes}m"
++ else:
++ return f"{hours}h"
++
+ def main():
+ input_dir = Path('input')
+```
+
+
+
+
+
+---
+
+
+
+Tool use: **search_replace** • Edit file: compress_pngs.py
+
+**Chunk 1**
+Lines added: 6, lines removed: 4
+
+```diff
+@@ -217,15 +217,17 @@
+ rate = processed / elapsed if elapsed > 0 else 0
+ remaining = len(png_files) - processed
+- eta = remaining / rate if rate > 0 else 0
++ eta_seconds = remaining / rate if rate > 0 and remaining > 0 else 0
+
+ total_savings = total_original_size - total_new_size
+ total_savings_pct = (total_savings / total_original_size * 100) if total_original_size > 0 else 0
++
++ eta_str = format_time(eta_seconds) if eta_seconds > 0 else "calculating..."
++ elapsed_str = format_time(elapsed)
+
+ print(f"[{processed:5d}/{len(png_files)}] "
+ f"Compressed: {compressed} | Skipped: {skipped} | "
+ f"Speed: {rate:.1f} files/sec | "
+- f"ETA: {eta:.1f}s | "
+- f"Saved: {format_size(total_savings)} ({total_savings_pct:.1f}%) | "
+- f"Elapsed: {elapsed:.1f}s", end='\r')
++ f"Elapsed: {elapsed_str} | ETA: {eta_str} | "
++ f"Saved: {format_size(total_savings)} ({total_savings_pct:.1f}%)", end='\r')
+ last_update_time = current_time
+ else:
+```
+
+
+
+
+
+---
+
+
+
+Tool use: **search_replace** • Edit file: compress_pngs.py
+
+**Chunk 1**
+Lines added: 1, lines removed: 1
+
+```diff
+@@ -248,5 +248,5 @@
+ print(f"Ignored (JPG/JPEG): {len(jpg_files)} files")
+ print(f"Failed: {failed} files")
+- print(f"Total time: {total_time:.2f} seconds")
++ print(f"Total time: {format_time(total_time)}")
+ print(f"Average speed: {avg_rate:.2f} files/second")
+ print(f"Original size: {format_size(total_original_size)}")
+```
+
+
+
+
+
+---
+
+
+
+---
+
+
+
+
+
+---
+
+
+
+ Tool use: **read_lints** • Read lints for 1 file
+
+Lint paths:
+
+- `compress_pngs.py`
+
+Lint results
+
+
+**No lint errors found**
+
+
+
+---
+
+
+
+---
+
+
+Updated the script to show time remaining (ETA) more clearly:
+
+1. **Added a time formatting function** that converts seconds to readable format:
+ - Less than 60 seconds: "45s"
+ - Less than 1 hour: "2m 30s"
+ - 1 hour or more: "1h 15m"
+
+2. **Made ETA more prominent** in the progress output — it now appears right after elapsed time
+
+3. **Improved time display** — both elapsed time and total time in the summary are now formatted
+
+The progress line now shows something like:
+```
+[ 1234/5000] Compressed: 1200 | Skipped: 34 | Speed: 45.2 files/sec | Elapsed: 27s | ETA: 1m 23s | Saved: 125.50 MB (15.2%)
+```
+
+ETA is calculated based on the current processing rate and updates as the rate changes.
+
+---
+
+_**User (2025-12-11 21:39Z)**_
+
+I'd rather the time format be (HH):MM:SS:MsMs
+
+---
+
+_**Agent (model default, mode Agent)**_
+
+
+
+---
+
+
+Updating the time formatting function to use HH:MM:SS:MsMs:
+
+
+---
+
+
+
+Tool use: **search_replace** • Edit file: compress_pngs.py
+
+**Chunk 1**
+Lines added: 6, lines removed: 14
+
+```diff
+@@ -132,18 +132,10 @@
+
+ def format_time(seconds):
+- """Format time in human readable format (e.g., '2m 30s' or '1h 15m')."""
+- if seconds < 60:
+- return f"{seconds:.0f}s"
+- elif seconds < 3600:
+- minutes = int(seconds // 60)
+- secs = int(seconds % 60)
+- return f"{minutes}m {secs}s"
+- else:
+- hours = int(seconds // 3600)
+- minutes = int((seconds % 3600) // 60)
+- if minutes > 0:
+- return f"{hours}h {minutes}m"
+- else:
+- return f"{hours}h"
++ """Format time as HH:MM:SS:MsMs (hours:minutes:seconds:centiseconds)."""
++ hours = int(seconds // 3600)
++ minutes = int((seconds % 3600) // 60)
++ secs = int(seconds % 60)
++ centiseconds = int((seconds % 1) * 100)
++ return f"{hours:02d}:{minutes:02d}:{secs:02d}:{centiseconds:02d}"
+
+ def main():
+```
+
+
+
+
+
+---
+
+
+
+---
+
+
+
+
+
+---
+
+
+
+ Tool use: **read_lints** • Read lints for 1 file
+
+Lint paths:
+
+- `compress_pngs.py`
+
+Lint results
+
+
+**No lint errors found**
+
+
+
+---
+
+
+
+---
+
+
+Updated the time format to `HH:MM:SS:MsMs`. Times display as:
+- **HH**: Hours (00-99, always 2 digits)
+- **MM**: Minutes (00-59, always 2 digits)
+- **SS**: Seconds (00-59, always 2 digits)
+- **MsMs**: Centiseconds (00-99, always 2 digits, hundredths of a second)
+
+Examples:
+- `00:00:45:23` (45 seconds and 23 centiseconds)
+- `00:02:30:50` (2 minutes, 30 seconds, 50 centiseconds)
+- `01:15:30:75` (1 hour, 15 minutes, 30 seconds, 75 centiseconds)
+
+This format is used for elapsed time, ETA, and total time in the summary.
+
+---
+
diff --git a/compress_pngs.py b/compress_pngs.py
index d1f6b97..233aa27 100644
--- a/compress_pngs.py
+++ b/compress_pngs.py
@@ -130,6 +130,14 @@ def format_size(size_bytes):
size_bytes /= 1024.0
return f"{size_bytes:.2f} TB"
+def format_time(seconds):
+ """Format time as HH:MM:SS:MsMs (hours:minutes:seconds:centiseconds)."""
+ hours = int(seconds // 3600)
+ minutes = int((seconds % 3600) // 60)
+ secs = int(seconds % 60)
+ centiseconds = int((seconds % 1) * 100)
+ return f"{hours:02d}:{minutes:02d}:{secs:02d}:{centiseconds:02d}"
+
def main():
input_dir = Path('input')
output_dir = Path('output')
@@ -200,17 +208,19 @@ def main():
if processed == 1 or time_since_update >= 0.5:
rate = processed / elapsed if elapsed > 0 else 0
remaining = len(png_files) - processed
- eta = remaining / rate if rate > 0 else 0
+ eta_seconds = remaining / rate if rate > 0 and remaining > 0 else 0
total_savings = total_original_size - total_new_size
total_savings_pct = (total_savings / total_original_size * 100) if total_original_size > 0 else 0
+ eta_str = format_time(eta_seconds) if eta_seconds > 0 else "calculating..."
+ elapsed_str = format_time(elapsed)
+
print(f"[{processed:5d}/{len(png_files)}] "
f"Compressed: {compressed} | Skipped: {skipped} | "
f"Speed: {rate:.1f} files/sec | "
- f"ETA: {eta:.1f}s | "
- f"Saved: {format_size(total_savings)} ({total_savings_pct:.1f}%) | "
- f"Elapsed: {elapsed:.1f}s", end='\r')
+ f"Elapsed: {elapsed_str} | ETA: {eta_str} | "
+ f"Saved: {format_size(total_savings)} ({total_savings_pct:.1f}%)", end='\r')
last_update_time = current_time
else:
failed += 1
@@ -229,7 +239,7 @@ def main():
if jpg_files:
print(f"Ignored (JPG/JPEG): {len(jpg_files)} files")
print(f"Failed: {failed} files")
- print(f"Total time: {total_time:.2f} seconds")
+ print(f"Total time: {format_time(total_time)}")
print(f"Average speed: {avg_rate:.2f} files/second")
print(f"Original size: {format_size(total_original_size)}")
print(f"Compressed size: {format_size(total_new_size)}")