11

How to set the first picture of every folder as its folder icon?

The question linked above has an answer consisting a script which has been working for me. It just needs a little improvement.

What does it do?

It finds files with .jpg, .jpeg, .png, .gif, .icns, .ico extensions and sets them as the folder icon of the folder in which the file was found. It works on multiple folders, recursively. Basically it tries to find an image file inside the folder, and the first image that it finds is set as a folder icon. It works great for many scenarios, and setting up this script is usually the first thing I do after fresh install (because it's amazing).

What's the problem?

There might be a few directories which contain a of lot of image files and the first image file in that directory might not be well suited to being the folder icon.

What should it do?

Instead of being extension based, if it became filename based and targeted one (for example, folder.png) or multiple (eg albumart.png cover.png) filenames then this problem could be solved.

or better yet make both approaches work in a single script

  • Find predefined filenames
  • If found set it as folder icon and move to next folder
  • If NOT found then find predefined extension and set it as folder icon and move to next folder
1

1 Answer 1

10
+100

I might still "elegant it up a bit" but below are the edited versions of the linked ones.

What is the difference?

I added a predefined list to the head section:

specs = ["folder.png", "cover.png", "monkey.png"]

and I replaced:

try:
    first = min(p for p in os.listdir(folder) 
                if p.split(".")[-1].lower() in ext)
except ValueError:
    pass

by:

fls = os.listdir(folder)
try:
    first = [p for p in fls if p in specs]
    first = first[0] if first else min(
        p for p in fls if p.split(".")[-1].lower() in ext
        )
except ValueError:
    pass

so that the script first tries to find (file) matches in the list specs, (only) if there are no, it jumps to searching for matching extension, and does the trick if it finds a suitable image.


1. The basic version

To be used with the targeted directory as argument:

#!/usr/bin/env python3
import subprocess
import os
import sys

# --- set the list of valid extensions below (lowercase)
# --- use quotes, *don't* include the dot!
ext = ["jpg", "jpeg", "png", "gif", "icns", "ico"]
# --- set the list of preferred filenames
# --- use quotes
specs = ["folder.png", "cover.png", "monkey.png"]
# ---

# retrieve the path of the targeted folder
dr = sys.argv[1]

for root, dirs, files in os.walk(dr):
    for directory in dirs:
        folder = os.path.join(root, directory)
        try:
            fls = os.listdir(folder)
            first = [p for p in fls if p in specs]
            first = first[0] if first else min(
                p for p in fls if p.split(".")[-1].lower() in ext
                )
        except (ValueError, PermissionError):
            pass

        else:
            subprocess.Popen([
                "gvfs-set-attribute", "-t", "string",
                os.path.abspath(folder), "metadata::custom-icon",
                "file://"+os.path.abspath(os.path.join(folder, first))
                ])

How to use

  1. Copy the script into an empty file, save it as change_icon.py
  2. In the head of the script, edit, if you like, the list of extensions to be used as valid icon images. Also set the preferred list of filenames.
  3. Run it with the targeted directory as an argument:

    python3 /path/to/change_icon.py <targeted_directory>
    

That's it!


2. The edited right-click option, to be used as a nautilus (right-click) script

#!/usr/bin/env python3
import subprocess
import os

# --- set the list of valid extensions below (lowercase)
# --- use quotes, *don't* include the dot!
ext = ["jpg", "jpeg", "png", "gif", "icns", "ico"]
# --- set the list of preferred filenames
# --- use quotes
specs = ["folder.png", "cover.png", "aap.png"]
# ---

def fix(path):
    for c in [("%23", "#"), ("%5D", "]"), ("%5E", "^"),
              ("file://", ""), ("%20", " ")]:
        path = path.replace(c[0], c[1])
    return path

# retrieve the path of the targeted folder
current = fix(os.getenv("NAUTILUS_SCRIPT_CURRENT_URI"))
dr = os.path.realpath(current)

for root, dirs, files in os.walk(dr):
    for directory in dirs:
        folder = os.path.join(root, directory)
        try:
            fls = os.listdir(folder)
            first = [p for p in fls if p in specs]
            first = first[0] if first else min(
                p for p in fls if p.split(".")[-1].lower() in ext
                )
        except (ValueError, PermissionError):
            pass

        else:
            subprocess.Popen([
                "gvfs-set-attribute", "-t", "string",
                os.path.abspath(folder), "metadata::custom-icon",
                "file://"+os.path.abspath(os.path.join(folder, first))
                ])

To use

  1. Create, if it doesn't exist yet, the directory

    ~/.local/share/nautilus/scripts
    
  2. Copy the script into an empty file, save it in ~/.local/share/nautilus/scripts as set_foldericons (no extension!), and make it executable.

  3. In the head of the script, edit, if you like, the list of extensions to be used as valid icon images. Also set the preferred list of filenames.
  4. Log out and back in, and it works.

If, for some reason you'd like to reset the icons inside a folder to their default icon(s), use the script here

6
  • 2
    You should verify that the URI from Nautilus actually begins with file://. Instead of replace("%20", " ") you should use proper URI decoding (e. g. urllib.parse.unquote) and later encoding (urllib.parse.quote). May 24, 2017 at 13:49
  • @DavidFoerster is this gonna improve the performance of the script is anyway? I tried to change subprocess.Popen to subprocess.call but on huge directories with almost 700+ sub's not all folder icons chnage. May 25, 2017 at 4:07
  • @DavidFoerster thanks! but urllib.parse.quote works fine on a "dry" test, not in the script. Need to find out why, but leaving the working version of the script for now. May 25, 2017 at 6:35
  • @SumeetDeshmukh: No, but it's going to make the script work with URLs that contain other "special" characters aside of the space character (U+0020). Additionally it would be come more robust against mistaken or bogus input and catch it early on. May 25, 2017 at 9:11
  • @JacobVlijm: What happens when you try to use it in the script? Error message? Unexpected results? Does unquote work as expected? May 25, 2017 at 9:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .