├── requirements.txt ├── __init__.py ├── .gitignore ├── pyproject.toml ├── js └── instagram_nodes.js ├── .github └── workflows │ └── publish.yml ├── README.md └── nodes.py /requirements.txt: -------------------------------------------------------------------------------- 1 | instaloader>=4.10.0 -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS 2 | 3 | __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Virtual Environment 24 | venv/ 25 | env/ 26 | ENV/ 27 | 28 | # IDE 29 | .idea/ 30 | .vscode/ 31 | *.swp 32 | *.swo 33 | 34 | # Project specific 35 | output/ 36 | logs/ 37 | *.log -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "instagramdownloader" 3 | description = "A ComfyUI custom node package that allows downloading and organizing Instagram content directly in your ComfyUI Output folder" 4 | version = "1.0.0" 5 | license = {file = "LICENSE"} 6 | dependencies = ["instaloader>=4.10.0"] 7 | 8 | [project.urls] 9 | Repository = "https://github.com/rohitsainier/ComfyUI-InstagramDownloader" 10 | # Used by Comfy Registry https://comfyregistry.org 11 | 12 | [tool.comfy] 13 | PublisherId = "rohitsainier" 14 | DisplayName = "ComfyUI-InstagramDownloader" 15 | Icon = "" 16 | -------------------------------------------------------------------------------- /js/instagram_nodes.js: -------------------------------------------------------------------------------- 1 | import { app } from "../../../scripts/app.js"; 2 | 3 | app.registerExtension({ 4 | name: "Instagram.Downloader", 5 | async beforeRegisterNodeDef(nodeType, nodeData, app) { 6 | if ( 7 | nodeData.name === "InstagramDownloader" || 8 | nodeData.name === "MediaOrganizer" 9 | ) { 10 | const onExecuted = nodeType.prototype.onExecuted; 11 | nodeType.prototype.onExecuted = function (message) { 12 | if (onExecuted) { 13 | onExecuted.apply(this, arguments); 14 | } 15 | this.bgcolor = message.success ? "#353" : "#533"; 16 | this.setDirtyCanvas(true); 17 | }; 18 | } 19 | }, 20 | nodeCategories: { 21 | instagram: { 22 | title: "Instagram", 23 | color: "#5865F2", 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | paths: 9 | - "pyproject.toml" 10 | 11 | jobs: 12 | publish-node: 13 | name: Publish Custom Node to registry 14 | runs-on: ubuntu-latest 15 | # if this is a forked repository. Skipping the workflow. 16 | if: github.event.repository.fork == false 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v4 20 | with: 21 | submodules: true 22 | - name: Publish Custom Node 23 | uses: Comfy-Org/publish-node-action@main 24 | with: 25 | ## Add your own personal access token to your Github Repository secrets and reference it here. 26 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Instagram Downloader for ComfyUI 2 | 3 | A ComfyUI custom node package that allows downloading and organizing Instagram content directly in your ComfyUI workflows. 4 | Screenshot 2025-01-01 at 10 28 10 PM 5 | Screenshot 2025-01-01 at 10 39 16 PM 6 | 7 | 8 | ## Features 9 | 10 | - Download photos and videos from Instagram profiles 11 | - Organize media into separate folders 12 | - Clean up temporary files 13 | - Easy integration with ComfyUI workflows 14 | 15 | ## Installation 16 | 17 | ### Via ComfyUI Manager 18 | 19 | 1. Open ComfyUI Manager 20 | 2. Search for "Instagram Downloader" 21 | 3. Click Install 22 | 23 | ### Manual Installation 24 | 25 | 1. Clone this repository into your `ComfyUI/custom_nodes` directory: 26 | 27 | ```bash 28 | cd custom_nodes 29 | git clone https://github.com/rohitsainier/ComfyUI-InstagramDownloader 30 | ``` 31 | 32 | 2. Install dependencies: 33 | 34 | ```bash 35 | cd ComfyUI-InstagramDownloader 36 | pip install -r requirements.txt 37 | ``` 38 | 39 | ## Usage 40 | 41 | ### Nodes 42 | 43 | 1. **Instagram Downloader** 44 | 45 | - Input: Instagram username 46 | - Output: Download path 47 | - Downloads content from specified Instagram profile 48 | 49 | 2. **Media Organizer** 50 | 51 | - Input: Download path 52 | - Output: Organized media paths 53 | - Separates content into images and videos folders 54 | 55 | 3. **Cleanup** 56 | - Input: Directory path 57 | - Output: Status message 58 | - Cleans up temporary files 59 | 60 | ### Example Workflow 61 | 62 | 1. Add Instagram Downloader node 63 | 2. Connect it to Media Organizer node 64 | 3. Optionally connect to Cleanup node 65 | 4. Configure username and download path 66 | 5. Run workflow 67 | 68 | ## License 69 | 70 | MIT License 71 | 72 | ## Credits 73 | 74 | Created by Rohit Saini 75 | -------------------------------------------------------------------------------- /nodes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import instaloader 4 | from typing import Dict, Any, Tuple 5 | 6 | try: 7 | import folder_paths 8 | except ImportError: 9 | class FolderPaths: 10 | def get_output_directory(self): 11 | return os.path.join(os.path.dirname(__file__), "output") 12 | folder_paths = FolderPaths() 13 | 14 | 15 | class InstagramDownloaderNode: 16 | @classmethod 17 | def INPUT_TYPES(cls): 18 | return { 19 | "required": { 20 | "username": ("STRING", {"default": "", "multiline": False}), 21 | "download_path": ("STRING", {"default": "downloads"}), 22 | "profile_pic_only": ("BOOLEAN", {"default": False}), 23 | } 24 | } 25 | 26 | RETURN_TYPES = ("STRING",) 27 | RETURN_NAMES = ("download_path",) 28 | FUNCTION = "download_profile" 29 | CATEGORY = "instagram" 30 | 31 | def download_profile(self, username: str, download_path: str, profile_pic_only: bool = False) -> Tuple[str]: 32 | try: 33 | # Create download directory 34 | full_path = os.path.join( 35 | folder_paths.get_output_directory(), download_path, username) 36 | os.makedirs(full_path, exist_ok=True) 37 | 38 | # Initialize downloader 39 | loader = instaloader.Instaloader( 40 | dirname_pattern=full_path, 41 | download_pictures=True, 42 | download_videos=True, 43 | download_video_thumbnails=False, 44 | save_metadata=False 45 | ) 46 | 47 | # Download profile content 48 | profile = instaloader.Profile.from_username( 49 | loader.context, username) 50 | loader.download_profile(profile, profile_pic_only=profile_pic_only) 51 | 52 | return (full_path,) 53 | 54 | except Exception as e: 55 | raise RuntimeError(f"Download failed: {str(e)}") 56 | 57 | 58 | class MediaOrganizerNode: 59 | @classmethod 60 | def INPUT_TYPES(cls): 61 | return { 62 | "required": { 63 | "input_path": ("STRING", {"default": ""}), 64 | } 65 | } 66 | 67 | RETURN_TYPES = ("STRING", "STRING", "INT", "INT") 68 | RETURN_NAMES = ("images_path", "videos_path", "image_count", "video_count") 69 | FUNCTION = "organize_media" 70 | CATEGORY = "instagram" 71 | 72 | def organize_media(self, input_path: str) -> Tuple[str, str, int, int]: 73 | try: 74 | # Create media directories 75 | images_path = os.path.join(input_path, "images") 76 | videos_path = os.path.join(input_path, "videos") 77 | os.makedirs(images_path, exist_ok=True) 78 | os.makedirs(videos_path, exist_ok=True) 79 | 80 | # Track counts 81 | image_count = 0 82 | video_count = 0 83 | 84 | # Organize files 85 | for filename in os.listdir(input_path): 86 | source_path = os.path.join(input_path, filename) 87 | 88 | if os.path.isfile(source_path): 89 | if filename.endswith((".jpg", ".png")): 90 | shutil.move(source_path, os.path.join( 91 | images_path, filename)) 92 | image_count += 1 93 | elif filename.endswith(".mp4"): 94 | shutil.move(source_path, os.path.join( 95 | videos_path, filename)) 96 | video_count += 1 97 | 98 | return (images_path, videos_path, image_count, video_count) 99 | 100 | except Exception as e: 101 | raise RuntimeError(f"Organization failed: {str(e)}") 102 | 103 | 104 | NODE_CLASS_MAPPINGS = { 105 | "InstagramDownloader": InstagramDownloaderNode, 106 | "MediaOrganizer": MediaOrganizerNode, 107 | } 108 | 109 | NODE_DISPLAY_NAME_MAPPINGS = { 110 | "InstagramDownloader": "Instagram Downloader", 111 | "MediaOrganizer": "Media Organizer", 112 | } 113 | --------------------------------------------------------------------------------