Skip to content

Instantly share code, notes, and snippets.

@introt
Last active January 15, 2022 17:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save introt/b7f36474d7699bdaa2607879753ef421 to your computer and use it in GitHub Desktop.
Save introt/b7f36474d7699bdaa2607879753ef421 to your computer and use it in GitHub Desktop.
Add event summaries to ISO 8601 -like filenames from an ICS calendar
#!/usr/bin/env python3
"""
timely_rename.py
Prepend ISO 8601 -like filenames
with event summaries from an ICS calendar
(c) 2021 introt
License: Apache 2.0
"""
import re
from os import rename
from ics import Calendar
# needs 0.8.0-dev or later: https://github.com/ics-py/ics-py
from datetime import datetime as dt
# eg. 2021-11-19-Note-08-15.xopp -> ['2021', '11', '19', '08', '15']
date_pat = re.compile(r'(\d{4})-(\d{2})-(\d{2}).*?(\d{2}).(\d{2})')
def get_timely_name(filename, calendar):
try:
g = date_pat.match(filename).groups()
candidates = calendar.timeline.at(dt(int(g[0]), int(g[1]), int(g[2]), int(g[3]), int(g[4])))
name = None
for c in candidates:
if not name:
name = c.summary
else:
print("WARNING: multiple events: choosing", name, "over", c.summary)
return "{}-{}".format(name, filename).replace(' ', '_').replace('/', '-')
except Exception:
# return unchanged filename
return filename
def timely_rename(file, verbose=True, dry_run=True):
new = get_timely_name(file, c)
if verbose or dry_run:
print('"{}" -> "{}"'.format(file, new))
if not dry_run:
rename(file, new)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Prepend ISO 8601 -like filenames with event summaries from an ICS calendar')
parser.add_argument('files', metavar='FILE', type=str, nargs='+', help='file(s) to rename')
parser.add_argument('--calendar', metavar='ICSFILE', type=str, nargs=1, help='ics filename/path')
parser.add_argument('--dry_run', help='print new filenames without renaming', action='store_true')
parser.add_argument('--verbose', help='print old and new filenames (implied by --dry-run)', action='store_true')
args=parser.parse_args()
c = Calendar(''.join(open(args.calendar[0], 'r').readlines()))
for file in args.files:
timely_rename(file, verbose=args.verbose, dry_run=args.dry_run)
@introt
Copy link
Author

introt commented Nov 19, 2021

This has been an exercise in dealing with both feature creep and perfectionism - there are so many things that could be done better or made configurable, but here's a MVP for y'all to play with.

Feel free to fork! Below are some features I'd add if I had the time:

  • proper filename sanitation
  • configurable filename format (so one can append the event summary instead of prepending or get rid of the original name altogether)
  • configurable regex
  • configurable offset (eg. for cameras with misset clocks)
  • get date from file metadata instead of filename
  • support multiple (online) calendars
  • prioritization criteria for overlapping events

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment