IMG_20261001_4821.jpg → Goa_2026-10-01_001.jpg, for every photo at once.
/build/project-photo-renamer with the starter and the rubric already in it.Read a folder your phone filled with camera names, work out each photo's date from its name, plan readable new names and rename the files for real, leaving videos and notes alone.
Renaming files is the first automation most people actually need. You'll slice strings, filter with a rule, number things with enumerate and touch real files with the os module.
You can turn a repetitive computer chore into a script that does it safely in one go.
Each step is one function in rename_photos.py. In the workspace, press Check next to a step: it runs your file and tells you exactly what is still wrong. Step 1 is free; the rest are part of Pro or your 7-day free trial.
Build against this exact list — it is the spec your published project is judged on.
Copy this into a new file — or open it in a workspace as rename_photos.py. The TODO parts are yours to fill.
"""
Photo renamer.
Turns camera names like IMG_20261001_4821.jpg into Goa_2026-10-01_001.jpg
for every photo in a folder, and leaves other files alone. Fill in one
function per step, press Run, then press "Check" on that step.
"""
import os
TRIP = "Goa"
# What the phone dumped into the folder
FILES = [
"IMG_20261002_0007.jpg",
"IMG_20261001_4821.jpg",
"notes.txt",
"IMG_20261001_4828.JPG",
"VID_20261002_0003.mp4",
"IMG_20261003_1102.jpeg",
"IMG_20261002_0011.png",
]
def date_from_name(name: str) -> str:
# TODO: "IMG_20261001_4821.jpg" -> "2026-10-01".
# The 8 digits after "IMG_" are the date: slice them out, then add dashes.
raise NotImplementedError("step 1, date_from_name(): pull the date out of the name")
def new_name(trip: str, date: str, n: int, ext: str) -> str:
# TODO: ("Goa", "2026-10-01", 7, ".jpg") -> "Goa_2026-10-01_007.jpg"
# The number always has 3 digits: {n:03}
raise NotImplementedError("step 2, new_name(): build the new file name")
def is_photo(name: str) -> bool:
# TODO: True for camera photos: starts with IMG_ and ends with .jpg,
# .jpeg or .png, in any capitals (.JPG counts). False for anything else.
raise NotImplementedError("step 3, is_photo(): photos only")
def plan_renames(names: list[str], trip: str) -> list[tuple[str, str]]:
# TODO: a list of (old name, new name) for the photos only, in sorted
# order, numbered from 1. Keep each file's extension, in lower case.
# Hint: os.path.splitext("a.JPG") -> ("a", ".JPG")
raise NotImplementedError("step 4, plan_renames(): old name -> new name for every photo")
def apply_renames(folder: str, plan: list[tuple[str, str]]) -> int:
# TODO: rename every file in the plan inside `folder` with os.rename,
# then return how many files you renamed.
raise NotImplementedError("step 5, apply_renames(): rename the files for real")
def main() -> None:
sample = FILES[1]
print(f"Example: {sample} → {new_name(TRIP, date_from_name(sample), 1, '.jpg')}")
print(f"{sum(is_photo(f) for f in FILES)} of {len(FILES)} files are photos")
# Given: make a folder with the phone's files in it.
folder = "/tmp/trip-photos"
os.makedirs(folder, exist_ok=True)
for f in os.listdir(folder):
os.remove(os.path.join(folder, f))
for name in FILES:
open(os.path.join(folder, name), "w").close()
plan = plan_renames(os.listdir(folder), TRIP)
done = apply_renames(folder, plan)
# TODO: print one line per rename, "old → new" (loop over plan), then
# "Renamed 5 photos, left 2 other files alone" using done.
raise NotImplementedError("step 6, main(): print what was renamed")
if __name__ == "__main__":
try:
main()
except NotImplementedError as todo:
# A fresh starter is SUPPOSED to stop here. Say which step is next
# instead of printing a traceback that looks like a bug.
print(f"Not built yet: {todo}")
print("Write that function, then press Run again. Each step you finish moves this message forward.")
rename_photos.py and a README.md holding all 5 rubric items as a checklist.pyrun.in/u/<handle>/w/project-photo-renamer.pyrun.in/u/<handle> portfolio page.Your workspace saves to this browser only. Sign in to keep it across devices and to publish it.
Flip the workspace to Public and paste its URL below, or push the file to a GitHub gist or repo.
A published PyRun workspace URL (pyrun.in/u/<handle>/w/project-photo-renamer) works as the public URL too.