Python script to view largest sub-directories using ncurses

This simple Python script will show you the largest sub-directories (not files). When Enter is pressed, the selected directory will be opened in the default file manager.

It’s a special utility that can be used to quickly filter through large directories of photos, videos, or other files.

Python largest subdirectory

#!/usr/bin/env python3

import curses
import os
import sys
from pathlib import Path
import subprocess

def format_size(size_bytes):
    if size_bytes >= 1024*1024*1024:
        return f"{size_bytes/(1024*1024*1024):.1f}GB"
    return f"{size_bytes/(1024*1024):.1f}MB"

def get_dir_sizes(parent_dir):
    dir_sizes = []
    try:
        with os.scandir(parent_dir) as entries:
            for entry in entries:
                if entry.is_dir():
                    size = sum(f.stat().st_size for f in Path(entry.path).glob('*') if f.is_file())
                    dir_sizes.append((entry.path, size))
    except PermissionError:
        return []
    return sorted(dir_sizes, key=lambda x: x[1], reverse=True)[:30]

def main(stdscr, parent_dir):
    curses.start_color()
    curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)  # selected
    curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)  # even
    curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)    # odd
    curses.curs_set(0)
    
    dirs = get_dir_sizes(parent_dir)
    current_pos = 0
    
    while True:
        stdscr.clear()
        height, width = stdscr.getmaxyx()
        
        # Header
        header = f"Directory overview for: {parent_dir} (Press 'q' to quit)"
        stdscr.addstr(0, 0, header[:width-1])
        
        # Directory listing
        for idx, (path, size) in enumerate(dirs):
            if idx >= height-2:  # Leave room for header
                break
                
            name = os.path.basename(path)
            size_str = format_size(size)
            display = f"{name:<{width-15}} {size_str:>10}"
            
            if idx == current_pos:
                stdscr.attron(curses.color_pair(1))
                stdscr.addstr(idx+2, 0, display[:width-1])
                stdscr.attroff(curses.color_pair(1))
            else:
                color = curses.color_pair(2) if idx % 2 == 0 else curses.color_pair(3)
                stdscr.attron(color)
                stdscr.addstr(idx+2, 0, display[:width-1])
                stdscr.attroff(color)
        
        stdscr.refresh()
        
        key = stdscr.getch()
        if key == ord('q'):
            break
        elif key == curses.KEY_UP and current_pos > 0:
            current_pos -= 1
        elif key == curses.KEY_DOWN and current_pos < len(dirs) - 1:
            current_pos += 1
        elif key == ord('\n') and dirs:
            curses.endwin()
            subprocess.run(['xdg-open', dirs[current_pos][0]])

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: dir_size_browser.py <directory>")
        sys.exit(1)
    
    parent_dir = os.path.abspath(sys.argv[1])
    if not os.path.isdir(parent_dir):
        print("Error: Not a directory")
        sys.exit(1)
        
    curses.wrapper(main, parent_dir)