Add numbering mode to nn
This commit is contained in:
84
bin/nn
84
bin/nn
@@ -100,6 +100,35 @@ def plan_parse_rename(filepath):
|
|||||||
return new_name, STATUS_RENAME
|
return new_name, STATUS_RENAME
|
||||||
|
|
||||||
|
|
||||||
|
def get_date_for_file(filepath, parse_mode):
|
||||||
|
"""Resolve a date string for a file, or None if unavailable."""
|
||||||
|
if parse_mode:
|
||||||
|
date_str = None
|
||||||
|
if shutil.which('exiftool') is not None:
|
||||||
|
date_str = get_exif_date(filepath)
|
||||||
|
if not date_str:
|
||||||
|
date_str = get_file_date(filepath)
|
||||||
|
return date_str
|
||||||
|
|
||||||
|
# Default pattern mode: extract date from filename
|
||||||
|
stem = filepath.stem
|
||||||
|
|
||||||
|
# Check if already normalized (e.g. "2024-06-01 breakfast.jpg")
|
||||||
|
norm_match = re.match(r'^(\d{4})-(\d{2})-(\d{2}) ', filepath.name)
|
||||||
|
if norm_match:
|
||||||
|
return f'{norm_match.group(1)}-{norm_match.group(2)}-{norm_match.group(3)}'
|
||||||
|
|
||||||
|
# Try the standard pattern
|
||||||
|
match = PATTERN.match(stem)
|
||||||
|
if not match:
|
||||||
|
match = PATTERN.match(filepath.name)
|
||||||
|
if match:
|
||||||
|
year, month, day, _ = match.groups()
|
||||||
|
return f'{year}-{month}-{day}'
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def collect_files(paths):
|
def collect_files(paths):
|
||||||
"""Resolve the list of files to process."""
|
"""Resolve the list of files to process."""
|
||||||
if not paths:
|
if not paths:
|
||||||
@@ -144,6 +173,49 @@ def build_plan(files, parse_mode):
|
|||||||
return plan
|
return plan
|
||||||
|
|
||||||
|
|
||||||
|
def build_number_plan(files, parse_mode):
|
||||||
|
"""Build a rename plan using sequential numbering per day."""
|
||||||
|
entries = []
|
||||||
|
for file_path in files:
|
||||||
|
date_str = get_date_for_file(file_path, parse_mode)
|
||||||
|
entries.append((file_path, date_str))
|
||||||
|
|
||||||
|
day_counter = {}
|
||||||
|
plan = []
|
||||||
|
seen_targets = {}
|
||||||
|
|
||||||
|
for file_path, date_str in entries:
|
||||||
|
if date_str is None:
|
||||||
|
plan.append((file_path, None, STATUS_NO_MATCH))
|
||||||
|
continue
|
||||||
|
|
||||||
|
day_counter[date_str] = day_counter.get(date_str, 0) + 1
|
||||||
|
seq = day_counter[date_str]
|
||||||
|
ext = file_path.suffix
|
||||||
|
new_name = f'{date_str} {seq:02d}{ext}'
|
||||||
|
|
||||||
|
if new_name == file_path.name:
|
||||||
|
plan.append((file_path, None, STATUS_ALREADY))
|
||||||
|
continue
|
||||||
|
|
||||||
|
status = STATUS_RENAME
|
||||||
|
target = file_path.parent / new_name
|
||||||
|
target_key = str(target).casefold()
|
||||||
|
|
||||||
|
if target.exists() and target.resolve() != file_path.resolve():
|
||||||
|
status = STATUS_CONFLICT
|
||||||
|
elif target_key in seen_targets:
|
||||||
|
prev_idx = seen_targets[target_key]
|
||||||
|
plan[prev_idx] = (plan[prev_idx][0], plan[prev_idx][1], STATUS_CONFLICT)
|
||||||
|
status = STATUS_CONFLICT
|
||||||
|
else:
|
||||||
|
seen_targets[target_key] = len(plan)
|
||||||
|
|
||||||
|
plan.append((file_path, new_name, status))
|
||||||
|
|
||||||
|
return plan
|
||||||
|
|
||||||
|
|
||||||
def print_preview(plan):
|
def print_preview(plan):
|
||||||
"""Print a formatted preview table."""
|
"""Print a formatted preview table."""
|
||||||
if not plan:
|
if not plan:
|
||||||
@@ -194,6 +266,15 @@ def main():
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Use EXIF data or file dates instead of pattern matching.',
|
help='Use EXIF data or file dates instead of pattern matching.',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-n',
|
||||||
|
'--number',
|
||||||
|
action='store_true',
|
||||||
|
help=(
|
||||||
|
'Discard original filename stems and number files sequentially '
|
||||||
|
'per day. Combines with --parse to select the date source.'
|
||||||
|
),
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'files',
|
'files',
|
||||||
nargs='*',
|
nargs='*',
|
||||||
@@ -207,6 +288,9 @@ def main():
|
|||||||
print('No files found.')
|
print('No files found.')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.number:
|
||||||
|
plan = build_number_plan(files, args.parse)
|
||||||
|
else:
|
||||||
plan = build_plan(files, args.parse)
|
plan = build_plan(files, args.parse)
|
||||||
print_preview(plan)
|
print_preview(plan)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user