{"id":51516557,"url":"https://github.com/damoscodehub/tag-suite","last_synced_at":"2026-07-08T12:01:22.812Z","repository":{"id":367420777,"uuid":"1279766355","full_name":"damoscodehub/tag-suite","owner":"damoscodehub","description":"Tag images using AI, copy tags between files, and manage metadata — all from the command line.","archived":false,"fork":false,"pushed_at":"2026-06-25T22:44:06.000Z","size":27,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T00:13:48.454Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/damoscodehub.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-25T02:02:44.000Z","updated_at":"2026-06-25T22:44:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/damoscodehub/tag-suite","commit_stats":null,"previous_names":["damoscodehub/tag-suite"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/damoscodehub/tag-suite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damoscodehub%2Ftag-suite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damoscodehub%2Ftag-suite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damoscodehub%2Ftag-suite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damoscodehub%2Ftag-suite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/damoscodehub","download_url":"https://codeload.github.com/damoscodehub/tag-suite/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damoscodehub%2Ftag-suite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35263922,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-08T12:01:21.991Z","updated_at":"2026-07-08T12:01:22.796Z","avatar_url":"https://github.com/damoscodehub.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tag-suite\n\nCommand-line tools for AI image tagging, tag copying, and tag removal.\n\n`joytag` uses the [JoyTag](https://github.com/fpgaminer/joytag) vision model to automatically tag images with 5000+ tags. `copytag` copies tags between files, `addtag` adds tags manually, and `untag` removes tags.\n\n**Supported formats:** JPEG (`.jpg`, `.jpeg`), PNG (`.png`), WebP (`.webp`), TIFF (`.tiff`, `.tif`).\n\n## Setup\n\nInstall dependencies:\n\n```bash\npip install torch torchvision Pillow safetensors transformers einops\n```\n\nDownload the model from [HuggingFace](https://huggingface.co/fancyfeast/joytag/tree/main) and place it in `models/`. The folder must contain `model.safetensors`, `config.json`, and `top_tags.txt`.\n\nFor convenience, add this repo's folder to your `PATH` to run the commands from anywhere.\n\n## Usage\n\nAll tools accept **`.txt` files as target shortcuts** — each line is treated as a file path. This lets you select files in Explorer (Ctrl+Shift+C), paste the paths into a `.txt`, and pass it to any script.\n\n```bash\npython joytag.py mylist.txt\npython addtag.py targets.txt --tags \"1boy,solo\"\npython untag.py targets.txt --tags \"1boy\"\npython scrubtags.py paths.txt --clean\n```\n\n### Tagging Images\n\n```bash\n# Tag all supported images in the current directory\npython joytag.py\n\n# Tag all supported images in a specific folder\npython joytag.py path/to/images\n\n# Tag a single image\npython joytag.py image.jpg\n\n# Tag multiple specific images (any formats)\npython joytag.py img1.jpg img2.png img3.webp\n\n# Mix files and folders\npython joytag.py img1.jpg path/to/folder\n\n# Overwrite existing tags (instead of merging)\npython joytag.py image.png --overwrite\n\n# Recursively process subdirectories\npython joytag.py path/to/images --recursive\npython joytag.py folder -r\n```\n\nBy default, folders are scanned **non-recursively** (only direct children). Use `--recursive` / `-r` to process subdirectories.\n\nBy default, new tags are **merged** with any existing tags — duplicates are avoided. Use `--overwrite` to replace existing tags with only the predicted ones.\n\nIf the repo folder is in your `PATH`:\n\n```bash\njoytag                  # tag current directory\njoytag folder           # tag a folder\njoytag image.png        # tag a single image\njoytag folder --recursive\n```\n\n### Removing Tags\n\nStrips XMP (keywords) and IPTC metadata from images, leaving image data intact.\n\n```bash\n# Remove all tags from all supported images in current directory\npython untag.py\n\n# Remove all tags from all supported images in a folder\npython untag.py path/to/images\n\n# Remove all tags from a single file\npython untag.py image.png\n\n# Remove all tags from multiple targets (files and/or folders)\npython untag.py img1.jpg img2.webp\npython untag.py img1.png path/to/folder\n\n# Remove only specific tags\npython untag.py image.jpg --tags \"tag1,tag2,tag3\"\n\n# Keep only specific tags, remove everything else\npython untag.py image.webp --preserve \"tag1,tag2\"\n\n# Remove specific tags while preserving others\npython untag.py image.png --tags \"bad1,bad2\" --preserve \"good1\"\n\n# Recursively process subdirectories\npython untag.py path/to/images --recursive\n\n# Via PATH\nuntag\nuntag image.jpg --tags \"1girl,solo\"\nuntag folder --preserve \"photo_(medium)\"\n```\n\n### Copying Tags\n\nCopies tags from one tagged image to other files without running the model — reads existing tags from the source and writes them to targets.\n\n```bash\n# Copy tags from source to one or more targets (any formats)\npython copytag.py source.png target1.jpg target2.webp\n\n# Copy tags to all images in a folder\npython copytag.py source.jpg path/to/folder\n\n# Mix files and folders as targets\npython copytag.py source.png img1.jpg path/to/folder\n\n# Overwrite existing tags on targets (instead of merging)\npython copytag.py source.webp target.jpg --overwrite\n\n# Recursively process target subdirectories\npython copytag.py source.png path/to/images -r\n\n# Via PATH\ncopytag source.png target.jpg\ncopytag source.jpg folder --overwrite\n```\n\nBy default, source tags are **merged** into targets — duplicates are avoided. Use `--overwrite` to replace all tags on targets with only the source's tags.\n\n### Adding Manually Tags\n\nAdd specific tags to images without running the AI model.\n\n```bash\n# Add tags to all images in current directory (interactive prompt)\npython addtag.py\n\n# Add tags to a specific folder or files\npython addtag.py path/to/images --tags \"tag1,tag2,tag3\"\npython addtag.py img1.jpg img2.png --tags \"new_tag,another_tag\"\n\n# Overwrite all existing tags (default is append)\npython addtag.py image.jpg --tags \"tag1,tag2\" --overwrite\npython addtag.py image.png -ow --tags \"tag1\"\n\n# Read tags from a .txt file (one tag per line)\npython addtag.py folder --tags_file mytags.txt\n\n# Use a .txt file as target list\npython addtag.py targets.txt --tags \"tag1,tag2\"\n\n# Recursively scan subdirectories\npython addtag.py path/to/images --tags \"tag1\" -r\n\n# Exclude files matching glob patterns\npython addtag.py path --tags \"tag1\" --exclude \"*thumb*\"\n\n# Via PATH\naddtag\naddtag image.jpg --tags \"1girl,solo\"\naddtag folder --tags_file mytags.txt\n```\n\nIf neither `--tags` nor `--tags_file` is provided, you'll be prompted to enter tags interactively (comma-separated).\n\n| Flag | Short | Description |\n|------|-------|-------------|\n| `--tags` | | Comma-separated tags to add |\n| `--tags_file` | | Read tags from a .txt file (one per line) |\n| `--append` | `-a` | Merge tags with existing (default behavior) |\n| `--overwrite` | `-ow` | Replace all existing tags with the new ones |\n| `--recursive` | `-r` | Process subdirectories |\n| `--exclude` | | Glob patterns to exclude (comma-separated) |\n\n### Replacing Tags\n\nFind and replace tags across images. Renames a tag everywhere it appears without affecting other tags.\n\n```bash\n# Replace a single tag in current directory (interactive prompt)\npython replacetag.py\n\n# Replace tags in a specific folder or files\npython replacetag.py path/to/images --tags \"old_tag=new_tag\"\npython replacetag.py img1.jpg img2.png --tags \"bad_tag=good_tag,old=new\"\n\n# Remove a tag entirely (replace with nothing)\npython replacetag.py image.jpg --tags \"unwanted_tag=\"\n\n# Read replacements from a .txt file (one old=new per line)\npython replacetag.py folder --tags_file repls.txt\n\n# Use a .txt file as target list\npython replacetag.py targets.txt --tags \"old=new\"\n\n# Recursively process subdirectories\npython replacetag.py path/to/images --tags \"old=new\" -r\n\n# Exclude files matching glob patterns\npython replacetag.py path --tags \"old=new\" --exclude \"*thumb*\"\n\n# Via PATH\nreplacetag\nreplacetag image.jpg --tags \"1girl=solo\"\nreplacetag folder --tags_file repls.txt\n```\n\nTo remove a tag without a replacement, use `--tags \"unwanted_tag=\"` (equals sign with empty new name). If neither `--tags` nor `--tags_file` is given, you'll be prompted interactively.\n\n| Flag | Short | Description |\n|------|-------|-------------|\n| `--tags` | | Comma-separated `old=new` replacements |\n| `--tags_file` | | Read replacements from a .txt file (one per line) |\n| `--recursive` | `-r` | Process subdirectories |\n| `--exclude` | | Glob patterns to exclude (comma-separated) |\n\n### Saving Tags (Backup)\n\nExports tags from images to a JSON backup file. Useful for recovery or batch editing tags externally.\n\n```bash\n# Save all tags from current directory\npython savetags.py\n\n# Save tags from a specific folder or files\npython savetags.py path/to/images\npython savetags.py img1.jpg img2.png\n\n# Save to a specific path\npython savetags.py -o mybackup.json\n\n# Auto-sequence (next available number)\npython savetags.py -s\npython savetags.py -o mybackup.json -s\n\n# Overwrite existing\npython savetags.py -ow\npython savetags.py -o mybackup.json -ow\n\n# Append to existing (merge, no duplicates)\npython savetags.py -a\npython savetags.py -o mybackup.json -a\n\n# Rename the new file\npython savetags.py -rn\n\n# Rename the existing file\npython savetags.py -ro\n\n# Exclude files matching glob patterns\npython savetags.py path/to/images --exclude \"*thumb*,*small*\"\n\n# Recursively scan subdirectories\npython savetags.py folder -r\n\n# Via PATH\nsavetags\nsavetags -o mybackup.json -s\n```\n\nIf no mode flag is given and the target file exists, you'll be prompted:\n```\ntags_backup.json already exists.\n[N] Auto-sequence  [RN] Rename new  [RO] Rename old  [OW] Overwrite  [A] Append\nChoose:\n```\n\n| Flag | Short | Description |\n|------|-------|-------------|\n| `--output` | `-o` | Output path (any mode) |\n| `--overwrite` | `-ow` | Replace existing file |\n| `--append` | `-a` | Merge into existing file |\n| `--sequencenew` | `-s` | Pick next available number |\n| `--renamenew` | `-rn` | Choose a new name for the output |\n| `--renameold` | `-ro` | Rename existing file, then write new\n\n### Restoring Tags\n\nReads a JSON backup and writes tags back to matching images. Matches files by relative path first, then falls back to filename.\n\n```bash\n# Restore tags from default tags_backup.json to all images in current directory\npython restoretags.py\n\n# Restore from a specific backup file\npython restoretags.py --backup mybackup.json\n\n# Restore to a specific folder or files\npython restoretags.py path/to/images\npython restoretags.py img1.jpg img2.png\n\n# Append backup tags to existing tags (never duplicates)\npython restoretags.py path/to/images --append\n\n# Exclude files matching glob patterns\npython restoretags.py --backup backup.json --exclude \"*thumb*\"\n\n# Recursively scan subdirectories\npython restoretags.py folder -r\n\n# Via PATH\nrestoretags\nrestoretags --backup mybackup.json\n```\n\nBy default, tags are **overwritten** on the image. Use `--append` / `-a` to merge with any existing tags.\n\n### Scrubbing Suspicious Tags\n\nDetects and removes non-tag data (coordinates, serialized floats, Lightroom parameters) that may have been mistakenly written into the keyword field by buggy metadata parsers.\n\n```bash\n# Dry-run: scan and show suspicious tags without modifying anything\npython scrubtags.py\npython scrubtags.py path/to/images\npython scrubtags.py image.jpg\n\n# Actually remove the suspicious tags\npython scrubtags.py image.jpg --clean\npython scrubtags.py path/to/images --clean\n\n# Recursively scan subdirectories\npython scrubtags.py folder --clean -r\n\n# Exclude files matching glob patterns\npython scrubtags.py path --clean --exclude \"*thumb*,*backup*\"\n\n# Via PATH\nscrubtags\nscrubtags image.jpg --clean\n```\n\nRunning without `--clean` is a safe dry-run — it prints which tags it would remove, along with a full list of all unique suspicious patterns found. Pass `--clean` to actually remove them.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdamoscodehub%2Ftag-suite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdamoscodehub%2Ftag-suite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdamoscodehub%2Ftag-suite/lists"}