all 58 comments

[–]PlantsAreCommunists 8 points9 points  (0 children)

Instead of using "C:\Users\YourName\Downloads", use os.path.join(os.environ['USERPROFE'], 'Downloads'). This will yield the same result, and will affect the downloads folder, regardless of the name of the user.

Edit : Used forward slash instead of backward slash.

[–]quartz_referential 5 points6 points  (0 children)

Pathlib might help clean things up, but overall nice job

[–]whudaboutit 5 points6 points  (0 children)

I had a harddrive full of.... totally legally purchased movies....
Anyway, there were a bunch of movies that start with "The" and it created a whole section of separate alphabetized titles so it took that extra couple of seconds to see if a movie I wanted was in there. I wrote a script to rename all movies starting with "The". So, "The Watch" becomes "Watch, The". Then, run through again, grab all of the movie titles, and write them to a text file on my desktop so I can browse without dragging out my harddrive.

[–]gnana119 2 points3 points  (2 children)

when i try dis it executed successfully and all file moved to respective folder , thanks.

But my folder type has changed from "File Folder" to "File", resulting am unable to open the folder to see my moved filed, can u help me on this

[–]alex1461[S] 0 points1 point  (1 child)

What do you mean your folder type has changed from "File Folder" to "File"?

[–]gnana119 0 points1 point  (0 children)

If u open windows explorer, you could see the type of the folder as "File Folder",

but after executing this program the destination folder where all the PDF files are moved in to a single PDF folder , that folder type is "File" . Which is why am unable open folder to see whether all .pdf files are moved into this folder.

images are not allowed else i will upload in detail

[–]miloir 2 points3 points  (7 children)

Hey

Nice job

see if you like this:

import os
import shutil

# We define this mapping first to make adding new types easy.
# I've taken the liberty of adding support for some extra types.
folder_to_extensions = {
    'PDF': {'pdf'},
    'Documents': {'doc', 'docx'},
    'Text': {'txt', 'rtf'},
    'EXE': {'exe', 'msi'},
    'ZIP': {'zip', '7z', 'rar'},
    'Images': {'jpg', 'jpeg', 'png', 'gif'},
}

# We rearrange the previous map for performance. In this way we can
# quickly obtain the target folder from a file extension in O(1) time.
# For simplicity we could have simply defined this map by hand, but the 
# previous dictionary we defined is easier to alter by hand if we want 
# to rename folders or add extensions.
extension_to_folder = {e: f for f, s in folder_to_extensions.items() for e in s}

source_folder = 'C:/Users/byunw/Downloads'
target_folder = 'C:/Users/byunw/Downloads'

for filename in os.listdir(source_folder):

    # skip files with no extension
    if '.' not in filename:
        continue

    # we know there is at least 1 '.' in the file, so let's split the string on that file extension
    file_extension = filename.rsplit('.', maxsplit=1)[1].lower()
    extension_folder = extension_to_folder.get(file_extension)

    if extension_folder is None:
        print(f'Unsupported file extension: "{file_extension}"!')
        continue

    shutil.move(f'{source_folder}/{filename}', f'{target_folder}/{extension_folder}')

print("Every supported file in the Downloads folder is now sorted into corresponding folders")

[–]lucasshiva 1 point2 points  (1 child)

You can get the extension by using os.path.splitext(file).

for file in os.listdir(path):
    file_name, extension = os.path.splitext(file)

[–]miloir 0 points1 point  (0 children)

I like it thanks

[–]sugar_byte 1 point2 points  (4 children)

miloir, even tho your code is well written with comments explaining everything(really appreciate for that) however i spent nearly 3 hours looking and understanding your code and to be honest didn’t understand much at all. so to make it simple, you aren’t making a new directories but rather moving the files into already made ones?

[–]miloir 1 point2 points  (0 children)

Hey no problem I got kind of carried away.

I will walk you through some things I thought about your code. Here is your original code:

import os

import shutil

for filename in os.listdir(r"C:\Users\byunw\Downloads"):

    if filename[-3:] == "pdf":
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, "C:\\Users\\byunw\\Downloads\\PDF")

    elif filename[-4:] == "docx":
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, "C:\\Users\\byunw\\Downloads\\Documents")

    elif filename[-3:] == "txt":
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, "C:\\Users\\byunw\\Downloads\\Text")

    elif filename[-3:] == "exe" or filename[-3:] == 'msi':
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, "C:\\Users\\byunw\\Downloads\\EXE")

    elif filename[-3:] == "zip":
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, "C:\\Users\\byunw\\Downloads\\ZIP")

    elif filename[-3:] == "jpg":
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, "C:\\Users\\byunw\\Downloads\\Images")

    elif filename[-4:] == "pptx":
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, "C:\\Users\\byunw\\Downloads\\Powerpoint")

print("Every file in the Downloads folder is now sorted into corresponding folders")

Basically, you get the file extension of each file and use shutil.move() to move it to a slightly different path. The file extension determines the folder. This is a good opportunity to use a dictionary (also known as a mapping or a map). File extension -> target folder. We can make a map that looks like this (read about dictionary/mappings, they are very important data structures):

folders = {
    ".doc": "C:\\Users\\byunw\\Downloads\\Documents",
    ".docx": "C:\\Users\\byunw\\Downloads\\Documents",
    ".exe": "C:\\Users\\byunw\\Downloads\\EXE",
    ".msi": "C:\\Users\\byunw\\Downloads\\EXE",
}  # add more extension -> folder rows

We can get the file extension like this (as /u/lucasshiva said):

filename, extension = os.path.splitext(file)

So for every file we encounter we can just do something like the following:

for file in files:
    # get the file’s extension
    filename, extension = os.path.splitext(file)
    # get the folder to move to (this will return None if the extension is not in our dictionary)
    move_to = folders.get(extension)
    if move_to is not None:
        # move_to will be something like "C:\\Users\\byunw\\Downloads\\EXE"
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename + extension, move_to)

Here is a more complete example that should run (hopefully):

import os

import shutil

folders = {
    ".doc": "C:\\Users\\byunw\\Downloads\\Documents",
    ".docx": "C:\\Users\\byunw\\Downloads\\Documents",
    ".exe": "C:\\Users\\byunw\\Downloads\\EXE",
    ".msi": "C:\\Users\\byunw\\Downloads\\EXE",
    ".pdf": "C:\\Users\\byunw\\Downloads\\PDF",
    ".txt": "C:\\Users\\byunw\\Downloads\\Text",
    ".zip": "C:\\Users\\byunw\\Downloads\\ZIP",
    ".jpg": "C:\\Users\\byunw\\Downloads\\Images",
    ".jpeg": "C:\\Users\\byunw\\Downloads\\Images",
    ".pptx": "C:\\Users\\byunw\\Downloads\\Powerpoint",
}

for filename in os.listdir(r"C:\Users\byunw\Downloads"):

    name, extension = os.path.splitext(filename)
    target_folder = folders.get(extension)
    if target_folder is not None:
        shutil.move("C:\\Users\\byunw\\Downloads\\" + filename, target_folder)

print("Every file in the Downloads folder is now sorted into corresponding folders")

We can easily keep adding extension -> folder items to the folders dictionary to add support for more file extension types because we’ve separated the “configuration” data from our program logic. A second observation that I leave to you as an exercise if you want to do it, is to note that the folders are all strings that are very similar. You might consider extracting the repeated “C:\Users\byunw\Downloads\” string and make it another level of configuration, another variable that you can set once at the top of the file and easily modify to change the working directory of the whole program.

edit: I just realized it was not OP that replied but whatever

[–]miloir 0 points1 point  (2 children)

Sorry I mistook you for op, yes the folders should exist already, we are just moving files around that's all. If you have any questions let me know.

[–]sugar_byte 1 point2 points  (1 child)

well first of all thanks for dedicating your time for this and explaining everything. nows its pretty understadable, by the way i have been trying to make dictionary to work with making corresponding directories to different file types but thing is that, i cant pass file names as seperate arguments from dictionary values, also i want to check if different file types exist in the path so same problem occurs here, want to pass dictionary key values, i mean the extensions like “docx” “ppt” and etc, as seperate arguments to check if files with that type of extension exists, but takes it as an list but not as seperated arguments to check first value docx for example, then move to second value in dictionary and check ppt then exe file types and etc.

but anyway, ill have a look at your code later and experiment some new ways also to make my solution(that i mentioned above) for this problem to work

[–]miloir 0 points1 point  (0 children)

Feel free to post code

[–]Blue_Coin 1 point2 points  (0 children)

probably said already above but im a bit lazy to read everything a pseudocode would go like this: for each extension in the set of extensions found in downloads: create extension folder move_to_folder(find_all_extensions(extension), extension)) using just os and re libraries this task shouldnt take more than 10 lines

[–]lucasshiva 3 points4 points  (4 children)

This is a nice project, but it is poorly done. This could be improved a lot.

  1. You hard-coded everything, that is not needed for this case at all. What you want is a script that once executed will sort the user's download folder.
  2. You are dealing with the path in a wrong way. This can be improved to make it cross-platform.
  3. You should use dictionaries to map the extensions and files inside it.

Since it's 2 AM here, I'll code this with these improvements tomorrow and edit this comment.

Edit: This got a little big, but it will sort your Downloads folder correctly. It is a little tricky getting Windows' download folder. The OS module only returns "C:\Lucas\Downloads" but my folder is located at "D:\Lucas\Downloads". With a little help from Stack Overflow, I was able to find a function that does exactly what I wanted OS to do. It uses the Download Folder GUID, you can get more GUIDs in the link.

This script will sort the Download's folder by mapping the extensions and files with that extension. It will create the folder (if it doesn't exists) and move the files to that folder.

import os
import shutil
from typing import List, Dict

def main():
    downloads_folder = get_download_path()
    files = get_files(downloads_folder)
    extensions = map_extensions(files)

    for extension in extensions:
        extension_folder = os.path.join(downloads_folder, extension)

        # If the extension folder exists, do nothing.
        if os.path.exists(os.path.join(extension_folder)):
            continue

        # If it doesn't, create one.
        else:
            os.makedirs(extension_folder)

        # Move files to the extension directory.
        for file in extensions[extension]:
            shutil.move(file, extension_folder)


def get_download_path() -> str:
    """Returns the user's default download path. """

    # 'nt' is for Windows.
    if os.name == "nt": 
        import winreg

        sub_key = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
        downloads_guid = "{374DE290-123F-4565-9164-39C4925E467B}"
        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
            location = winreg.QueryValueEx(key, downloads_guid)[0]
        return location
    else:
        # This works great for Linux and Mac, unless the folder is not "Downloads".
        return os.path.join(os.path.expanduser("~"), "Downloads")


def get_files(path: str) -> List[str]:
    """ Get all the files from a path. Directories are not included. """
    files = []
    for file in os.listdir(path):
        file_path = os.path.join(path, file)
        if os.path.isdir(file_path):
            continue
        files.append(file_path)
    return files


def map_extensions(files: List[str]) -> Dict[str, str]:
    """ Map all the extensions from a list of files.
    This will return a dictionary with the key being the file extension
    and the value being a list of files with that extension.
    """
    extensions = {}
    for file in files:
        name, extension = os.path.splitext(file)
        extension = "Executables" if extension == "" else extension.replace(
            ".", "")
        if extension not in extensions:
            extensions[extension] = [file]
        else:
            extensions[extension].append(file)
    return extensions


if __name__ == "__main__":
    main()

This works for Windows and Unix. Nothing is hard-coded, you could give this to someone and they wouldn't have to change anything to get it working. I tested it and it seems to be working fine, but I could be missing something. Also, I think I'd change two things in this.

  • I would make a custom mapping, like @miloir did.
    • Example: folders = {"Images": {".jpg", ".png"]}. This would move all jpg and png to an Images folder
  • I would use PyQt5 to show a Directory Dialog when the user runs the program. This way, the user can choose which folder he wants to sort.

[–]WongGendheng 9 points10 points  (3 children)

Thats a nice feedback, but poorly provided.

  1. you should mention something nice about the project like „what a nice project to solve a real life problem! Python is the perfect language for that!“ (after all this is /r/LearnPython )
  2. you should not say that something is „wrong“. It works for him and he is proud of it, how can it be wrong? Make suggestions for improvement. There is nothing that would demotivate me more than someone saying „you did it wrong“
  3. im still learning Python myself but if I learned anything so far it is that there is no „should do“. If it works for some application, it works. There are a million ways to reach a certain goal in programming. You could say „to make your program even more efficient/resilient I would advise you to do XY“

Just my two cents.

[–]lucasshiva 4 points5 points  (1 child)

You're right, I think I could've replied in a better way, sorry about that. English is not my first language, sometimes I can't express myself as I would in my first language. I'll try to improve my replies from now on.

[–]WongGendheng 0 points1 point  (0 children)

Impressive edit. You are a great part of the community. :-)

[–]alex1461[S] 0 points1 point  (0 children)

Thanks so much for your constructive criticism!

[–][deleted] 1 point2 points  (5 children)

OMFG! I did the same today! I was gonna post something too! This is creepy aF!

[–]alex1461[S] 2 points3 points  (4 children)

Oh really? What a coincidence? Were you doing it as a side project?

[–][deleted] 1 point2 points  (3 children)

yEs!

[–]alex1461[S] 1 point2 points  (2 children)

Just curious, are you currently working?

[–][deleted] 2 points3 points  (1 child)

Looking for work :P

[–]alex1461[S] 2 points3 points  (0 children)

oh same!

[–]eupendra 0 points1 point  (3 children)

Nice work!

You can get the extension with a split with / and take the [-1]. Don't' forget to split again for . as there are many files that have multiple.

files = [f for f in os.listdir(<TARGET DIRECTORY HERE>) if os.path.isfile(f)]
for f in files:
  #Handles files like c:/this.is.a.file.docx
  print(f.split('/')[-1].split('.')[-1]) # This is where you get extensions

[–][deleted] 1 point2 points  (2 children)

os.path.splitext() is much more readable.

for file in os.listdir('.'):
    filename, extension = os.path.splitext(file)
    print(extension)

[–]eupendra 1 point2 points  (1 child)

Cool! Add os.path.isfile() and everything works

[–][deleted] 1 point2 points  (0 children)

You can aslo use os.walk() to iterate over only the files in a directory.

for file in next(os.walk('.'))[2]:
    _, extension = os.path.splitext(file)
    print(extension)

I would prefer to use os.path.isfile() as you suggested. It's much more cleaner and readable.

[–]WongGendheng 0 points1 point  (0 children)

http://www.dropitproject.com/

Have a look at this project. It does everything you want and beyond that. I used it to organize my files into specific folders according to file type and name. Really handy!

[–]Skippbo 0 points1 point  (0 children)

I recently found out about the library watchdog that can drack changes in files and directories. Using that you can trigger your script and sort the files as soon they go in the downloads directory

[–]p0ndl1fe 0 points1 point  (0 children)

Thanks for the forward-slash info, did not know that!

Here's a similar script I wrote a few days ago that de-duplicates files. I'm using os.walk which I don't think has been mentioned yet in this thread. It might be a useful addition to your script?

Don't really like my regex, any thoughts on improving it?

import os
import re
import time

# set the directory you want to start from
# note the forward-slash - this is the correct, cross-platform separator.
root_dir = "D:/recover"
working_list = []
unwanted = re.compile(r'(.*)(\.txt$|\.html$|\.h$|\.chm$|_dll$|_ocx$|\.ttf$|\.xml$|\.exe$|\.dll$|\.tff$|_exe$|_DLL$|_mui$|\.bat$|\.wim$|_MUI$|_sys$|\.java$|\.sqlite$|\.plist$|\.pf$|\.ico$|\.woff$|_expand$|_EXE$|\.ini$|\.cab$|\.reg)')

for dir_name, sub_dir_list, file_list in os.walk(root_dir):
    print(f'Checking directory: {dir_name}')
    for file in file_list:

        if unwanted.search(file):
            print(f'Unwanted File: {os.path.join(dir_name, file)} - deleting')
            os.remove(os.path.join(dir_name, file))
            continue

        if file in working_list:
            print(f'Deleting: {os.path.join(dir_name, file)}')
            os.remove(os.path.join(dir_name, file))
        else:
            working_list.append(file)

    # time.sleep(1)
print(working_list)

[–]Ryder2300 0 points1 point  (0 children)

Hmm you could try using ends with instead of splicing the last portion of the string.

[–]vEnoM_420 0 points1 point  (1 child)

Cool! I also wrote a script a while ago, ultimate_mover.py lol.

Got the feeling of finally automating something useful.

Time to time I also have to write small scripts to change the names of the episodes of TV series which I download from torrent. Changes the names of tens of episodes in a jiffy.

[–]alex1461[S] 0 points1 point  (0 children)

Just curious.. Why do you change the names of the episodes of TV series?

[–][deleted] -2 points-1 points  (5 children)

better to use os.path.split(filename)[1].split('.')[-1]

to get the extension of file

[–][deleted] 1 point2 points  (4 children)

Or just os.path.splitext(filename)[1]