|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# Required parameters: |
| 4 | +# @raycast.schemaVersion 1 |
| 5 | +# @raycast.title Rename Video |
| 6 | +# @raycast.mode fullOutput |
| 7 | + |
| 8 | +# Optional parameters: |
| 9 | +# @raycast.icon 📂 |
| 10 | +# @raycast.argument1 { "type": "text", "placeholder": "Directory's absolute path", "optional": false } |
| 11 | +# @raycast.argument2 { "type": "text", "placeholder": "Type (video/image/all)", "optional": true } |
| 12 | +# @raycast.argument3 { "type": "text", "placeholder": "Dry Run (true/false)", "optional": true } |
| 13 | + |
| 14 | +# Documentation: |
| 15 | +# @raycast.description This is a simple Python script for recursively renaming video and picture files within a directory. Type the root directory's absolute path, and it will scan all the video and picture files in it and rename them according to the folder where they are located as the format `<folder_name>-<current_date (MMDD)>-<incremental_number>`. |
| 16 | +# @raycast.author StepaniaH |
| 17 | +# @raycast.authorURL https://github.com/StepaniaH |
| 18 | + |
| 19 | +import os |
| 20 | +import sys |
| 21 | +import datetime |
| 22 | +import argparse |
| 23 | +import re |
| 24 | + |
| 25 | +class RenameFilesAsDate: |
| 26 | + def __init__(self, root_directory): |
| 27 | + self.root_directory = os.path.expanduser(root_directory) |
| 28 | + self.error_files = {} |
| 29 | + self.video_extensions = ('.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v', '.3gp') |
| 30 | + self.image_extensions = ('.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff', '.svg', '.heic') |
| 31 | + |
| 32 | + def rename_files(self, file_type='all', dry_run=False): |
| 33 | + current_date = datetime.datetime.now().strftime("%m%d") |
| 34 | + self._process_directory(self.root_directory, current_date, file_type, dry_run) |
| 35 | + |
| 36 | + if self.error_files: |
| 37 | + print("\nThe following files could not be renamed:") |
| 38 | + for original_path, error in self.error_files.items(): |
| 39 | + print(f"{original_path}: {error}") |
| 40 | + |
| 41 | + def _is_already_renamed(self, filename, current_date): |
| 42 | + """Check if the file has been named according to the target format""" |
| 43 | + base_name = os.path.splitext(filename)[0] |
| 44 | + pattern = re.compile(f'^.*-{current_date}-\\d+$') |
| 45 | + return bool(pattern.match(base_name)) |
| 46 | + |
| 47 | + def _get_max_sequence_number(self, directory, current_date): |
| 48 | + """Get the largest serial number existing in the current directory""" |
| 49 | + max_seq = 0 |
| 50 | + pattern = re.compile(f'^.*-{current_date}-(\\d+)$') |
| 51 | + |
| 52 | + for entry in os.listdir(directory): |
| 53 | + match = pattern.match(os.path.splitext(entry)[0]) |
| 54 | + if match: |
| 55 | + seq_num = int(match.group(1)) |
| 56 | + max_seq = max(max_seq, seq_num) |
| 57 | + return max_seq |
| 58 | + |
| 59 | + def _process_directory(self, directory, current_date, file_type, dry_run): |
| 60 | + folder_name = os.path.basename(directory) |
| 61 | + supported_extensions = self._get_supported_extensions(file_type) |
| 62 | + |
| 63 | + # First collect the files that need to be renamed |
| 64 | + files_to_rename = [] |
| 65 | + for entry in os.listdir(directory): |
| 66 | + entry_path = os.path.join(directory, entry) |
| 67 | + |
| 68 | + if os.path.isfile(entry_path): |
| 69 | + if entry.lower().endswith(supported_extensions): |
| 70 | + if not self._is_already_renamed(entry, current_date): |
| 71 | + files_to_rename.append(entry) |
| 72 | + elif os.path.isdir(entry_path): |
| 73 | + self._process_directory(entry_path, current_date, file_type, dry_run) |
| 74 | + |
| 75 | + if files_to_rename: |
| 76 | + # Get the largest serial number existing in the current directory |
| 77 | + count = self._get_max_sequence_number(directory, current_date) + 1 |
| 78 | + total_files = len(files_to_rename) |
| 79 | + num_digits = len(str(total_files + count - 1)) |
| 80 | + |
| 81 | + # Rename collected files |
| 82 | + for entry in sorted(files_to_rename): # Sort to ensure consistent rename order |
| 83 | + entry_path = os.path.join(directory, entry) |
| 84 | + new_name = f"{folder_name}-{current_date}-{count:0{num_digits}d}{os.path.splitext(entry)[1].lower()}" |
| 85 | + new_path = os.path.join(directory, new_name) |
| 86 | + |
| 87 | + if dry_run: |
| 88 | + print(f"Would rename: {entry} -> {new_name}") |
| 89 | + else: |
| 90 | + try: |
| 91 | + os.rename(entry_path, new_path) |
| 92 | + print(f"Renamed: {entry} -> {new_name}") |
| 93 | + except OSError as e: |
| 94 | + self.error_files[entry_path] = f"Rename failed: {str(e)}" |
| 95 | + continue |
| 96 | + count += 1 |
| 97 | + |
| 98 | + def _get_supported_extensions(self, file_type): |
| 99 | + if file_type == 'video': |
| 100 | + return self.video_extensions |
| 101 | + elif file_type == 'image': |
| 102 | + return self.image_extensions |
| 103 | + return self.video_extensions + self.image_extensions |
| 104 | + |
| 105 | +def main(): |
| 106 | + args = sys.argv[1:] |
| 107 | + |
| 108 | + directory = None |
| 109 | + file_type = 'all' |
| 110 | + dry_run = False |
| 111 | + |
| 112 | + if len(args) >= 1: |
| 113 | + directory = args[0] |
| 114 | + if len(args) >= 2 and args[1]: |
| 115 | + file_type = args[1] |
| 116 | + if len(args) >= 3 and args[2]: |
| 117 | + dry_run = args[2].lower() == 'true' |
| 118 | + |
| 119 | + if not directory: |
| 120 | + print("Error: Directory argument is required.") |
| 121 | + sys.exit(1) |
| 122 | + |
| 123 | + renamer = RenameFilesAsDate(directory) |
| 124 | + renamer.rename_files(file_type, dry_run) |
| 125 | + |
| 126 | +if __name__ == '__main__': |
| 127 | + main() |
0 commit comments