Auto-Renaming My Untitled.ipynb Files With Gemini 1.5 Flash

by Audrey M. Roy Greenfeld | Sat, Feb 1, 2025

I'm starting to accumulate many UntitledX.ipynb files. Here I use the Gemini 1.5 Flash language model from Google to rename each one based on its contents.


from datetime import datetime
from execnb.nbio import read_nb
from fastcore.utils import *
import google.generativeai as genai
from pathlib import Path

Desired File Format

YYYY-MM-DD-Title-of-Notebook-in-TitleCase-With-Hyphens.ipynb

Get the Untitled Notebooks

nbs = L(Path().glob("Untitled*.ipynb"))
nbs
nb = nbs[0]

Get the Last Modified Date

To get each file's prefix, I get the file modified stats:

Path(nb).stat().st_mtime

This returns a Unix timestamp. To get a more readable datetime:

last_modified = datetime.fromtimestamp(Path(nb).stat().st_mtime)
last_modified
last_modified.strftime('%Y-%m-%d')

Check for an Existing Title

It would be in the first cell:

cells = L(read_nb(nb).cells)
cells[0]

Ask Gemini to Title the Notebook

model = genai.GenerativeModel('gemini-1.5-flash-latest')
model
with open(x) as f:
    nb = f.read()
def generate_title_part(nb):
    prompt = f"""Given this Jupyter notebook, create a filename following these EXACT steps:
1. Extract the title from the first cell if it starts with '#'. In this case it's: "FastHTML By Example, Part 2"
2. Convert to the format: Words-In-Title-Case-With-Hyphens.ipynb
3. Remove any special characters (like commas)
4. If the filename sounds repetitive, simplify it.
5. If the first cell does not contain a title, create one based on the entire notebook's contents.

<notebook>
{nb}
</notebook>

Return ONLY the filename, nothing else."""
    safety_settings = [
        {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE",},
        {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE",},
        {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE",},
        {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE",},
    ]
    response = model.generate_content(prompt, safety_settings=safety_settings, request_options = {"timeout": 1000})
    try:
        return response.text
    except Exception as ex:
        raise ex
result = generate_title_part(nb)
print(result)

Prefix the Title With the Date

Putting the full title together:

full_title = f"{last_modified.strftime('%Y-%m-%d')}-{result.strip()}"
print(full_title)

Rename the File

x
new_path = Path(full_title)
if new_path.exists():
    print(f"Warning: {new_path} already exists")
else:
    x.rename(new_path)
    print(f"Renamed {x} to {new_path}")

Make This All a Function

Let's put this all together into a function that we can call on several files.

def rename_notebook(nb_path):
    """Rename an untitled notebook based on its contents and modification date"""
    date = datetime.fromtimestamp(Path(nb_path).stat().st_mtime).strftime('%Y-%m-%d')
    with open(nb_path) as f: nb = f.read()
    
    title_part = generate_title_part(nb)
    
    new_name = f"{date}-{title_part.strip()}"
    new_path = Path(new_name)
    
    if new_path.exists():
        print(f"Warning: {new_path} already exists")
        return nb_path
    else:
        nb_path.rename(new_path)
        print(f"Renamed {nb_path} to {new_path}")
        return new_path
nbs = L(Path().glob("Untitled*.ipynb"))
nbs
new_paths = nbs.map(rename_notebook)

Several of my notebooks were renamed successfully! I ran out of quota, though. I was probably hitting the Gemini API too fast. Let's see where we are and try again.

nbs = L(Path().glob("Untitled*.ipynb"))
nbs
new_paths = nbs.map(rename_notebook)

Mostly done! The 2 warnings sound like I have duplicates.

nbs = L(Path().glob("Untitled*.ipynb"))
nbs

Finally, I'm checking those remaining notebooks. It appears those are variations on the ones that exist. I can manually merge those.