A collection of scripts for organizing media files.
Scripts are run with uv:
uv run --project ~/code/scripts python ~/code/scripts/<script>.pyScripts operate on the current working directory (and subdirectories recursively where applicable). They refuse to run from ~ as a safety measure.
Renames photos and videos to YYYYMMDD-HHMMSS<ext> (normalizing .jpeg to .jpg). The new filename depends only on the capture timestamp and file extension.
Supported extensions:
- Images:
.jpg,.jpeg,.png,.heic 8058 code> - Videos:
.mp4,.mov,.mkv,.avi
How timestamps are resolved (decision flow):
- Embedded metadata (preferred):
- Images: EXIF
DateTimeOriginal->DateTimeDigitized->ModifyDate(via Pillow +pillow-heif). - Videos:
creation_time->com.apple.quicktime.creationdate(viaffprobe; preserves embedded timezone offset if present, or converts UTC using local timezone rules at the capture date).
- Images: EXIF
- WeChat image filename:
- If an image has no EXIF and is named
mmexport<13-digit-unix-ms>, converts the millisecond timestamp to local time and writes the originalmmexport...filename to the image's EXIFCommenttag (viaexiftool) before renaming.
- If an image has no EXIF and is named
- Filename fallback (when metadata is missing):
- Looks for the first valid
YYYYMMDDdate in the filename:- Date + time found (e.g.
Screenshot_20260512-143022.pngor20260512143022.jpg): renames toYYYYMMDD-HHMMSS<ext>. - Date only, time unknown (e.g.
IMG-20260512-WA0001.jpgor20260512_photo.jpg): uses999999as the time placeholder (YYYYMMDD-999999<ext>), sorting at the end of that day.
- Date + time found (e.g.
- Looks for the first valid
- No usable date found:
- Moves the file into an
unchanged/subfolder for manual review (does not fall back to file modification time).
- Moves the file into an
Duplicate handling: When multiple files share the same second, appends a–z after HHMMSS (20260512-143022.jpg, 20260512-143022a.jpg, ..., 20260512-143022z.jpg). Beyond 27 files in the same second, keeps the trailing z and appends another letter (za–zz, zza–zzz, etc.) so alphabetical order is preserved.
Dry run: Add --dry-run (or -n) to preview changes without modifying or moving files. Any numeric parts in the new filename that weren't in the original filename (such as EXIF HHMMSS or 999999) are highlighted in blue.
Recursively renames files to lowercase and replaces spaces with hyphens. Operates on the current working directory and subdirectories.
Merges video segments from the current directory in natural filename order, so part2.mp4 comes before part10.mp4.
Supported extensions: .mp4, .mov, .mkv, .avi, .m4v
Output: writes merged-YYYYMMDD-HHMMSS<ext> using the first input file's extension. Existing merged-* files are excluded from inputs.
Merge mode: uses ffmpeg concat demuxer with -c copy for a lossless merge. The script checks stream compatibility first with ffprobe and fails clearly if segments have different codecs, stream counts, video dimensions, pixel formats, audio layout, sample rates, or time bases.
Dry run: add --dry-run (or -n) to print the merge order, output filename, and compatibility result without writing output.
Extension filter: add --include .mp4 to only include a specific extension. Repeat the flag to include multiple extensions.
Renames image files (.png, .jpg, .jpeg) in the current directory to sequential numbers, then writes an index.html displaying them in order.
Resizes one or more images proportionally so their heights match fg_height, lays them out horizontally with 100px padding, and centers the composite on a pure black background of bg_width by bg_height. Saves the result in the same directory as the first input image with -pad appended to the filename stem.
Defaults: bg_width=3440, bg_height=1440, fg_height=720, padding=100px
Usage:
# Single or multiple images using default dimensions
uv run --project ~/code/scripts python ~/code/scripts/pad-image.py <image1> [image2 ...]
# With custom dimensions
uv run --project ~/code/scripts python ~/code/scripts/pad-image.py <image1> [image2 ...] [bg_width] [bg_height] [fg_height]Moves the mouse cursor by one pixel in a random direction every ~5 minutes to prevent the screen from sleeping.
Managed by uv. See pyproject.toml. System dependencies: ffmpeg/ffprobe for video metadata and exiftool for preserving WeChat filenames in image comments.