Created
August 21, 2026 15:12
-
-
Save benoit74/6c8f5cce216f3922b2a2c49d69a1324e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """List media entries in a single local ZIM file. | |
| Prints a summary of media counts grouped by mimetype on the console, | |
| and writes the full list of media entries (path, mimetype, size) to | |
| a CSV file. | |
| Usage: | |
| ./zim_media_list_local.py <path-to-zim> [--output FILE] | |
| """ | |
| import argparse | |
| import csv | |
| import os | |
| import sys | |
| from collections import defaultdict | |
| from zimscraperlib.zim.archive import Archive | |
| MEDIA_MIME_PREFIXES = ("image/", "audio/", "video/") | |
| def check_local_file(path): | |
| if not os.path.isfile(path): | |
| return False, f"file not found: {path}" | |
| if not os.access(path, os.R_OK): | |
| return False, f"file not readable: {path}" | |
| size = os.path.getsize(path) | |
| return True, f"File found locally ({size:,} bytes)" | |
| def collect_media_entries(archive): | |
| """List of (path, mimetype, size) for every non-redirect media entry.""" | |
| entries = [] | |
| for entry_id in range(archive.all_entry_count): | |
| entry = archive.get_entry_by_id(entry_id) | |
| if entry.is_redirect: | |
| continue | |
| item = entry.get_item() | |
| mimetype = item.mimetype | |
| if not mimetype.startswith(MEDIA_MIME_PREFIXES): | |
| continue | |
| entries.append((entry.path, mimetype, item.size)) | |
| return entries | |
| def print_summary(entries): | |
| counts = defaultdict(int) | |
| sizes = defaultdict(int) | |
| for _path, mimetype, size in entries: | |
| counts[mimetype] += 1 | |
| sizes[mimetype] += size | |
| print(f"\nTotal media entries: {len(entries):,}") | |
| print(f"{'mimetype':<60} {'count':>10} {'total size':>15}") | |
| for mimetype in sorted(counts, key=lambda m: -counts[m]): | |
| print(f"{mimetype:<60} {counts[mimetype]:>10,} {sizes[mimetype]:>15,}") | |
| def write_csv(output_path, entries): | |
| with open(output_path, "w", newline="") as f: | |
| writer = csv.writer(f) | |
| writer.writerow(["path", "mimetype", "size"]) | |
| for path, mimetype, size in sorted(entries): | |
| writer.writerow([path, mimetype, size]) | |
| print(f"\nFull media list written to {output_path}") | |
| def default_output_path(zim_path): | |
| stem = os.path.splitext(os.path.basename(zim_path))[0] | |
| return f"{stem}_media.csv" | |
| def parse_args(): | |
| parser = argparse.ArgumentParser( | |
| description="List media entries (by mimetype) found in a local ZIM file." | |
| ) | |
| parser.add_argument("zim_path", help="Local path to the ZIM file") | |
| parser.add_argument( | |
| "--output", | |
| metavar="FILE", | |
| help="CSV output path (default: <zim-name>_media.csv in the current directory)", | |
| ) | |
| return parser.parse_args() | |
| def main(): | |
| args = parse_args() | |
| ok, msg = check_local_file(args.zim_path) | |
| print(f"ZIM: {args.zim_path}") | |
| print(f" {'OK ' if ok else 'FAIL'} - {msg}") | |
| if not ok: | |
| print(f"\nError: {msg}", file=sys.stderr) | |
| return 1 | |
| output_path = args.output or default_output_path(args.zim_path) | |
| print("\nScanning entries (this can take a little while)...") | |
| with Archive(args.zim_path) as archive: | |
| native_media_count = archive.media_count | |
| entries = collect_media_entries(archive) | |
| print(f"\nNative media_count: {native_media_count:,}") | |
| print_summary(entries) | |
| write_csv(output_path, entries) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment