├── .env.example ├── .gitignore ├── README.md ├── main.py └── requirements.txt /.env.example: -------------------------------------------------------------------------------- 1 | export REPLICATE_API_TOKEN='' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | venv/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Namedrop Ollama 2 | 3 | Writes nice file names for your images using ollama/llava. 4 | 5 | Demo: 6 | https://x.com/charliebholtz/status/1737667912784134344?s=20 7 | 8 | Run it in the background to never have to see `` again. 9 | 10 | Namedrop also adds a meta attribute to your filename so you don't have to worry about it running twice on the same file. 11 | 12 | ## Setup 13 | 14 | Clone this repo, and setup and activate a virtualenv: 15 | 16 | ```bash 17 | python3 -m pip install virtualenv 18 | python3 -m virtualenv venv 19 | source venv/bin/activate 20 | ``` 21 | 22 | Then, install the dependencies: 23 | `pip install -r requirements.txt` 24 | 25 | Run [ollama](https://ollama.ai) on your machine and pull the model using `ollama pull llava:13b` 26 | 27 | 28 | ## Run it! 29 | 30 | In on terminal, run `python main.py `. namedrop 31 | will then watch that directory for changes and rename any image file. 32 | 33 | For example, I run it on my desktop with: 34 | 35 | ```bash 36 | python main.py ~/Desktop 37 | ``` 38 | 39 | You can also run namedrop on a specific file: 40 | 41 | ```bash 42 | python main.py 43 | ``` 44 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | import argparse 4 | import imghdr 5 | import xattr 6 | import time 7 | import requests 8 | from watchdog.observers import Observer 9 | from watchdog.events import FileSystemEventHandler 10 | 11 | class ImageHandler(FileSystemEventHandler): 12 | def on_created(self, event): 13 | if event.is_directory: 14 | return None 15 | elif event.src_path.lower().endswith(('.png', '.jpg', '.jpeg')): 16 | rename_images_in_dir(event.src_path) 17 | 18 | 19 | def encode_image(image_path): 20 | """Encode an image into URI""" 21 | with open(image_path, "rb") as image_file: 22 | encoded_string = base64.b64encode(image_file.read()).decode("utf-8") 23 | return encoded_string 24 | 25 | def rename_file(path): 26 | # Check if path is a file and exists 27 | if not os.path.isfile(path) or not os.path.exists(path): 28 | return None 29 | 30 | # Check if file is already renamed 31 | try: 32 | if xattr.getxattr(path, 'user.renamed'): 33 | print(f"Skipping {path}, already renamed.") 34 | return os.path.split(path)[1] 35 | except OSError: 36 | pass # Attribute does not exist, continue with renaming 37 | 38 | 39 | uri = encode_image(path) 40 | response = requests.post("http://localhost:11434/api/generate", json={ 41 | "model": "llava:13b", 42 | "prompt": "Can you create a filename for this image based what's in it? Just give me the filename, no pre-amble, and no extension.", 43 | "stream": False, 44 | "images": [uri] 45 | }) 46 | output = response.json()['response'] 47 | 48 | # get extension from original filename 49 | _directory, filename = os.path.split(path) 50 | _filename, extension = os.path.splitext(filename) 51 | 52 | # add hyphens between spaces and lowercase, remove . 53 | output = output.replace(" ", "-").lower().replace(".", "") 54 | 55 | # replace .png, .jpg, .jpeg in output with nothing 56 | output = output.replace(".png", "").replace(".jpg", "").replace(".jpeg", "") 57 | 58 | # After renaming the file, set the 'user.renamed' attribute 59 | xattr.setxattr(path, 'user.renamed', b'true') 60 | 61 | print(f"Renaming {filename} to {output}{extension}") 62 | return output + extension 63 | 64 | 65 | def rename_images_in_dir(path): 66 | if os.path.isdir(path): 67 | for filename in os.listdir(path): 68 | file_path = os.path.join(path, filename) 69 | if not os.path.isfile(file_path): 70 | continue 71 | if imghdr.what(file_path) in ['jpeg', 'png']: 72 | new_name = rename_file(os.path.join(path, filename)) 73 | os.rename(os.path.join(path, filename), os.path.join(path, new_name)) 74 | elif os.path.isfile(path) and path.lower().endswith(('.png', '.jpg', '.jpeg')): 75 | directory, filename = os.path.split(path) 76 | new_name = rename_file(path) 77 | os.rename(path, os.path.join(directory, new_name)) 78 | 79 | 80 | if __name__ == "__main__": 81 | parser = argparse.ArgumentParser(description='Rename images in a directory or a single file.') 82 | parser.add_argument('path', help='The directory or file to rename images in.') 83 | args = parser.parse_args() 84 | 85 | rename_images_in_dir(args.path) 86 | 87 | event_handler = ImageHandler() 88 | observer = Observer() 89 | observer.schedule(event_handler, path=args.path, recursive=False) 90 | observer.start() 91 | 92 | try: 93 | while True: 94 | time.sleep(1) 95 | except KeyboardInterrupt: 96 | observer.stop() 97 | observer.join() 98 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.6.0 2 | anyio==4.2.0 3 | certifi==2023.11.17 4 | cffi==1.16.0 5 | charset-normalizer==3.3.2 6 | exceptiongroup==1.2.0 7 | h11==0.14.0 8 | httpcore==1.0.2 9 | httpx==0.26.0 10 | idna==3.6 11 | packaging==23.2 12 | pycparser==2.21 13 | pydantic==2.5.2 14 | pydantic_core==2.14.5 15 | requests==2.31.0 16 | sniffio==1.3.0 17 | typing_extensions==4.9.0 18 | urllib3==2.1.0 19 | watchdog==3.0.0 20 | xattr==1.0.0 21 | --------------------------------------------------------------------------------