Created
December 20, 2024 21:11
-
-
Save sankalpsingha/d2adf05940735b49554989b22fd10767 to your computer and use it in GitHub Desktop.
This script will organize the folder based on the extension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import shutil | |
from pathlib import Path | |
import logging | |
from typing import Dict, List, Set | |
# Configure logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
# File type categories and their extensions | |
FILE_CATEGORIES: Dict[str, List[str]] = { | |
'Images': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.ico', '.svg', '.webp'], | |
'Documents': ['.pdf', '.doc', '.docx', '.txt', '.rtf', '.odt', '.xlsx', '.xls', '.pptx', '.ppt', '.csv'], | |
'Audio': ['.mp3', '.wav', '.flac', '.m4a', '.aac', '.wma', '.m4b'], | |
'Video': ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm', '.m4v', '.mpg', '.mpeg'], | |
'Archives': ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2'], | |
'Code': ['.py', '.js', '.html', '.css', '.java', '.cpp', '.h', '.jsx', '.ts', '.tsx', '.json', '.yml', '.sql'], | |
'Executables': ['.exe', '.msi', '.app', '.dmg', '.pkg'], | |
'Torrents': ['.torrent'], | |
'eBooks': ['.epub', '.mobi', '.azw', '.azw3'], | |
'Fonts': ['.ttf', '.otf', '.woff', '.woff2'], | |
'Others': [] # For files that don't match any category | |
} | |
class FileOrganizer: | |
def __init__(self, downloads_path: str, dry_run: bool = False): | |
self.downloads_path = Path(downloads_path) | |
self.ignored_names: Set[str] = {'.DS_Store', 'desktop.ini'} | |
self.processed_files = 0 | |
self.skipped_files = 0 | |
self.created_folders: Set[str] = set() | |
self.dry_run = dry_run | |
self.would_move: Dict[str, List[str]] = {} | |
def create_category_folders(self) -> None: | |
"""Create folders for each category if they don't exist.""" | |
for category in FILE_CATEGORIES.keys(): | |
category_path = self.downloads_path / category | |
if not category_path.exists(): | |
category_path.mkdir(exist_ok=True) | |
self.created_folders.add(category) | |
logging.info(f"Created folder: {category}") | |
def get_file_category(self, file_extension: str) -> str: | |
"""Determine the category for a given file extension.""" | |
for category, extensions in FILE_CATEGORIES.items(): | |
if file_extension.lower() in extensions: | |
return category | |
return 'Others' | |
def organize_files(self) -> None: | |
"""Organize files in the downloads folder into their respective categories.""" | |
try: | |
if not self.dry_run: | |
self.create_category_folders() | |
# Initialize would_move dictionary for each category | |
if self.dry_run: | |
for category in FILE_CATEGORIES.keys(): | |
self.would_move[category] = [] | |
for item in self.downloads_path.iterdir(): | |
if item.is_file() and item.name not in self.ignored_names: | |
# Skip if the file is in a category folder | |
if item.parent.name in FILE_CATEGORIES: | |
continue | |
file_extension = item.suffix.lower() | |
category = self.get_file_category(file_extension) | |
destination = self.downloads_path / category / item.name | |
try: | |
# Create numbered filename if a file with same name exists | |
if destination.exists() and not self.dry_run: | |
base = destination.stem | |
counter = 1 | |
while destination.exists(): | |
new_name = f"{base}_{counter}{destination.suffix}" | |
destination = destination.parent / new_name | |
counter += 1 | |
if self.dry_run: | |
self.would_move[category].append(item.name) | |
self.processed_files += 1 | |
else: | |
shutil.move(str(item), str(destination)) | |
self.processed_files += 1 | |
logging.info(f"Moved: {item.name} -> {category}/") | |
except (PermissionError, OSError) as e: | |
self.skipped_files += 1 | |
if not self.dry_run: | |
logging.error(f"Error moving {item.name}: {str(e)}") | |
except Exception as e: | |
logging.error(f"An error occurred: {str(e)}") | |
raise | |
def print_summary(self) -> None: | |
"""Print a summary of the organization process.""" | |
if self.dry_run: | |
logging.info("\nDry Run Summary - These actions would be taken:") | |
for category, files in self.would_move.items(): | |
if files: # Only show categories that have files | |
logging.info(f"\n{category}:") | |
for file in sorted(files): | |
logging.info(f" • Would move: {file}") | |
logging.info(f"\nTotal files that would be moved: {self.processed_files}") | |
logging.info(f"Files that would be skipped: {self.skipped_files}") | |
else: | |
logging.info("\nOrganization Summary:") | |
logging.info(f"Total files processed: {self.processed_files}") | |
logging.info(f"Files skipped: {self.skipped_files}") | |
if self.created_folders: | |
logging.info(f"New folders created: {', '.join(self.created_folders)}") | |
def main(): | |
import argparse | |
# Set up argument parser | |
parser = argparse.ArgumentParser( | |
description='Organize files in a directory into categorized folders.' | |
) | |
parser.add_argument( | |
'-p', '--path', | |
type=str, | |
default=str(Path.home() / "Downloads"), | |
help='Path to the directory to organize (default: Downloads folder)' | |
) | |
parser.add_argument( | |
'-d', '--dry-run', | |
action='store_true', | |
help='Show what would be done without making actual changes' | |
) | |
args = parser.parse_args() | |
# Verify the path exists | |
if not os.path.exists(args.path): | |
logging.error(f"The specified path does not exist: {args.path}") | |
return | |
# Create and run the organizer | |
organizer = FileOrganizer(args.path, args.dry_run) | |
if args.dry_run: | |
logging.info(f"Dry run mode - no files will be moved") | |
logging.info(f"Would organize files in: {args.path}") | |
logging.info(f"Starting file organization in: {args.path}") | |
organizer.organize_files() | |
organizer.print_summary() | |
logging.info("File organization completed!") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment