Skip to content

Instantly share code, notes, and snippets.

@sankalpsingha
Created December 20, 2024 21:11
Show Gist options
  • Save sankalpsingha/d2adf05940735b49554989b22fd10767 to your computer and use it in GitHub Desktop.
Save sankalpsingha/d2adf05940735b49554989b22fd10767 to your computer and use it in GitHub Desktop.
This script will organize the folder based on the extension
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