├── 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 |
5 |
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 |
--------------------------------------------------------------------------------